diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 8f34f42e4f9..67fbfafbc28 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -1615,6 +1615,14 @@ public: static already_AddRefed LayerManagerForDocument(nsIDocument *aDoc); + /** + * Determine whether a content node is focused or not, + * + * @param aContent the content node to check + * @return true if the content node is focused, false otherwise. + */ + static PRBool IsFocusedContent(nsIContent *aContent); + private: static PRBool InitializeEventTable(); diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 966ae184a4f..0b529518c9e 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -178,6 +178,8 @@ static NS_DEFINE_CID(kXTFServiceCID, NS_XTFSERVICE_CID); #include "nsLayoutUtils.h" #include "nsFrameManager.h" #include "BasicLayers.h" +#include "nsFocusManager.h" +#include "nsTextEditorState.h" #ifdef IBMBIDI #include "nsIBidiKeyboard.h" @@ -1047,6 +1049,8 @@ nsContentUtils::Shutdown() NS_IF_RELEASE(sSameOriginChecker); nsAutoGCRoot::Shutdown(); + + nsTextEditorState::ShutDown(); } // static @@ -5953,6 +5957,15 @@ mozAutoRemovableBlockerRemover::~mozAutoRemovableBlockerRemover() } } +// static +PRBool +nsContentUtils::IsFocusedContent(nsIContent* aContent) +{ + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + + return fm && fm->GetFocusedContent() == aContent; +} + void nsContentUtils::RemoveNewlines(nsString &aString) { // strip CR/LF and null diff --git a/content/html/content/public/nsITextControlElement.h b/content/html/content/public/nsITextControlElement.h index 6d2af224d6b..de30a84d47c 100644 --- a/content/html/content/public/nsITextControlElement.h +++ b/content/html/content/public/nsITextControlElement.h @@ -40,32 +40,174 @@ #define nsITextControlElement_h___ #include "nsISupports.h" +class nsIContent; class nsAString; -class nsITextControlFrame; +class nsIEditor; +class nsISelectionController; +class nsFrameSelection; +class nsTextControlFrame; // IID for the nsITextControl interface #define NS_ITEXTCONTROLELEMENT_IID \ -{ 0x8c22af1e, 0x1dd2, 0x11b2, \ - { 0x9d, 0x72, 0xb4, 0xc1, 0x53, 0x68, 0xdc, 0xa1 } } +{ 0x66545dde, 0x3f4a, 0x49fd, \ + { 0x82, 0x73, 0x69, 0x7e, 0xab, 0x54, 0x06, 0x0a } } /** - * This interface is used for the text control frame to store its value away - * into the content. + * 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: NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEXTCONTROLELEMENT_IID) - /** - * Set the control's value without security checks - */ - NS_IMETHOD TakeTextFrameValue(const nsAString& aValue) = 0; - /** * Tell the control that value has been deliberately changed (or not). */ NS_IMETHOD SetValueChanged(PRBool changed) = 0; + + /** + * Find out whether this is a single line text control. (text or password) + * @return whether this is a single line text control + */ + NS_IMETHOD_(PRBool) IsSingleLineTextControl() const = 0; + + /** + * Find out whether this control is a textarea. + * @return whether this is a textarea text control + */ + NS_IMETHOD_(PRBool) IsTextArea() const = 0; + + /** + * Find out whether this control edits plain text. (Currently always true.) + * @return whether this is a plain text control + */ + NS_IMETHOD_(PRBool) IsPlainTextControl() const = 0; + + /** + * Find out whether this is a password control (input type=password) + * @return whether this is a password ontrol + */ + NS_IMETHOD_(PRBool) IsPasswordTextControl() const = 0; + + /** + * Get the cols attribute (if textarea) or a default + * @return the number of columns to use + */ + NS_IMETHOD_(PRInt32) GetCols() = 0; + + /** + * Get the column index to wrap at, or -1 if we shouldn't wrap + */ + NS_IMETHOD_(PRInt32) GetWrapCols() = 0; + + /** + * Get the rows attribute (if textarea) or a default + * @return the number of rows to use + */ + NS_IMETHOD_(PRInt32) GetRows() = 0; + + /** + * Get the default value of the text control + */ + NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue) = 0; + + /** + * Return true if the value of the control has been changed. + */ + NS_IMETHOD_(PRBool) ValueChanged() const = 0; + + /** + * Get the current value of the text editor. + * + * @param aValue the buffer to retrieve the value in + * @param aIgnoreWrap whether to ignore the text wrapping behavior specified + * for the element. + */ + NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, PRBool aIgnoreWrap) const = 0; + + /** + * Set the current value of the text editor. + * + * @param aValue the new value for the text control. + * @param aUserInput whether this value is coming from user input. + */ + NS_IMETHOD_(void) SetTextEditorValue(const nsAString& aValue, PRBool aUserInput) = 0; + + /** + * Get the editor object associated with the text editor. + * The return value is null if the control does not support an editor + * (for example, if it is a checkbox.) + */ + NS_IMETHOD_(nsIEditor*) GetTextEditor() = 0; + + /** + * Get the selection controller object associated with the text editor. + * The return value is null if the control does not support an editor + * (for example, if it is a checkbox.) + */ + NS_IMETHOD_(nsISelectionController*) GetSelectionController() = 0; + + NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() = 0; + + /** + * Binds a frame to the text control. This is performed when a frame + * is created for the content node. + */ + NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) = 0; + + /** + * Unbinds a frame from the text control. This is performed when a frame + * belonging to a content node is destroyed. + */ + NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) = 0; + + /** + * Creates an editor for the text control. This should happen when + * a frame has been created for the text control element, but the created + * editor may outlive the frame itself. + */ + NS_IMETHOD CreateEditor() = 0; + + /** + * Get the anonymous root node for the text control. + */ + NS_IMETHOD_(nsIContent*) GetRootEditorNode() = 0; + + /** + * Get the placeholder anonymous node for the text control. + */ + NS_IMETHOD_(nsIContent*) GetPlaceholderNode() = 0; + + /** + * Initialize the keyboard event listeners. + */ + NS_IMETHOD_(void) InitializeKeyboardEventListeners() = 0; + + /** + * Notify the text control that the placeholder text needs to be updated. + */ + NS_IMETHOD_(void) UpdatePlaceholderText(PRBool aNotify) = 0; + + /** + * Show/hide the placeholder for the control. + */ + NS_IMETHOD_(void) SetPlaceholderClass(PRBool aVisible, PRBool aNotify) = 0; + + static const PRInt32 DEFAULT_COLS = 20; + static const PRInt32 DEFAULT_ROWS = 1; + static const PRInt32 DEFAULT_ROWS_TEXTAREA = 2; + static const PRInt32 DEFAULT_UNDO_CAP = 1000; + + // wrap can be one of these three values. + typedef enum { + eHTMLTextWrap_Off = 1, // "off" + eHTMLTextWrap_Hard = 2, // "hard" + eHTMLTextWrap_Soft = 3 // the default + } nsHTMLTextWrap; + + static PRBool + GetWrapPropertyEnum(nsIContent* aContent, nsHTMLTextWrap& aWrapProp); }; NS_DEFINE_STATIC_IID_ACCESSOR(nsITextControlElement, diff --git a/content/html/content/src/Makefile.in b/content/html/content/src/Makefile.in index 6ea5a54503b..0464a2d9ee4 100644 --- a/content/html/content/src/Makefile.in +++ b/content/html/content/src/Makefile.in @@ -58,6 +58,7 @@ CPPSRCS = \ nsGenericHTMLElement.cpp \ nsFormSubmission.cpp \ nsImageMapUtils.cpp \ + nsTextEditorState.cpp \ nsHTMLAnchorElement.cpp \ nsHTMLAreaElement.cpp \ nsHTMLBRElement.cpp \ @@ -124,6 +125,7 @@ INCLUDES += \ -I$(srcdir)/../../../base/src \ -I$(srcdir)/../../../events/src \ -I$(srcdir)/../../../xbl/src \ + -I$(srcdir)/../../../../layout/forms \ -I$(srcdir)/../../../../layout/style \ -I$(srcdir)/../../../../layout/tables \ -I$(srcdir)/../../../../layout/xul/base/src \ diff --git a/content/html/content/src/nsGenericHTMLElement.cpp b/content/html/content/src/nsGenericHTMLElement.cpp index 89586d92c6b..6facd8863bc 100644 --- a/content/html/content/src/nsGenericHTMLElement.cpp +++ b/content/html/content/src/nsGenericHTMLElement.cpp @@ -110,6 +110,7 @@ #include "nsContentCreatorFunctions.h" #include "mozAutoDocUpdate.h" #include "nsHtml5Module.h" +#include "nsITextControlElement.h" #include "nsThreadUtils.h" @@ -2707,14 +2708,20 @@ nsGenericHTMLFormElement::IsTextControl(PRBool aExcludePassword) const type == NS_FORM_TEXTAREA; } +PRBool +nsGenericHTMLFormElement::IsSingleLineTextControlInternal(PRBool aExcludePassword, + PRInt32 aType) const +{ + return aType == NS_FORM_INPUT_TEXT || + aType == NS_FORM_INPUT_SEARCH || + aType == NS_FORM_INPUT_TEL || + (!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD); +} + PRBool nsGenericHTMLFormElement::IsSingleLineTextControl(PRBool aExcludePassword) const { - PRInt32 type = GetType(); - return type == NS_FORM_INPUT_TEXT || - type == NS_FORM_INPUT_SEARCH || - type == NS_FORM_INPUT_TEL || - (!aExcludePassword && type == NS_FORM_INPUT_PASSWORD); + return IsSingleLineTextControlInternal(aExcludePassword, GetType()); } PRBool @@ -3166,12 +3173,10 @@ nsGenericHTMLElement::GetEditorInternal(nsIEditor** aEditor) { *aEditor = nsnull; - nsIFormControlFrame *fcFrame = GetFormControlFrame(PR_FALSE); - if (fcFrame) { - nsITextControlFrame *textFrame = do_QueryFrame(fcFrame); - if (textFrame) { - return textFrame->GetEditor(aEditor); - } + nsCOMPtr textCtrl = do_QueryInterface(this); + if (textCtrl) { + *aEditor = textCtrl->GetTextEditor(); + NS_IF_ADDREF(*aEditor); } return NS_OK; diff --git a/content/html/content/src/nsGenericHTMLElement.h b/content/html/content/src/nsGenericHTMLElement.h index cca6ea35271..211515f478d 100644 --- a/content/html/content/src/nsGenericHTMLElement.h +++ b/content/html/content/src/nsGenericHTMLElement.h @@ -863,6 +863,8 @@ protected: void UpdateEditableFormControlState(); + PRBool IsSingleLineTextControlInternal(PRBool aExcludePassword, PRInt32 mType) const; + // The focusability state of this form control. eUnfocusable means that it // shouldn't be focused at all, eInactiveWindow means it's in an inactive // window, eActiveWindow means it's in an active window. diff --git a/content/html/content/src/nsHTMLInputElement.cpp b/content/html/content/src/nsHTMLInputElement.cpp index b4f89c107ee..8adc6172999 100644 --- a/content/html/content/src/nsHTMLInputElement.cpp +++ b/content/html/content/src/nsHTMLInputElement.cpp @@ -112,6 +112,8 @@ #include "nsTextEditRules.h" +#include "nsTextEditorState.h" + // XXX align=left, hspace, vspace, border? other nav4 attrs static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); @@ -308,9 +310,30 @@ public: virtual PRInt32 IntrinsicState() const; // nsITextControlElement - NS_IMETHOD TakeTextFrameValue(const nsAString& aValue); NS_IMETHOD SetValueChanged(PRBool aValueChanged); - + NS_IMETHOD_(PRBool) IsSingleLineTextControl() const; + NS_IMETHOD_(PRBool) IsTextArea() const; + NS_IMETHOD_(PRBool) IsPlainTextControl() const; + NS_IMETHOD_(PRBool) IsPasswordTextControl() const; + NS_IMETHOD_(PRInt32) GetCols(); + NS_IMETHOD_(PRInt32) GetWrapCols(); + NS_IMETHOD_(PRInt32) GetRows(); + NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue); + NS_IMETHOD_(PRBool) ValueChanged() const; + NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, PRBool aIgnoreWrap) const; + NS_IMETHOD_(void) SetTextEditorValue(const nsAString& aValue, PRBool aUserInput); + NS_IMETHOD_(nsIEditor*) GetTextEditor(); + NS_IMETHOD_(nsISelectionController*) GetSelectionController(); + NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection(); + NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame); + NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame); + NS_IMETHOD CreateEditor(); + NS_IMETHOD_(nsIContent*) GetRootEditorNode(); + NS_IMETHOD_(nsIContent*) GetPlaceholderNode(); + NS_IMETHOD_(void) UpdatePlaceholderText(PRBool aNotify); + NS_IMETHOD_(void) SetPlaceholderClass(PRBool aVisible, PRBool aNotify); + NS_IMETHOD_(void) InitializeKeyboardEventListeners(); + // nsIFileControlElement virtual void GetDisplayFileName(nsAString& aFileName); virtual void GetFileArray(nsCOMArray &aFile); @@ -341,9 +364,12 @@ public: void MaybeLoadImage(); protected: + // Pull IsSingleLineTextControl into our scope, otherwise it'd be hidden + // by the nsITextControlElement version. + using nsGenericHTMLFormElement::IsSingleLineTextControl; + // Helper method nsresult SetValueInternal(const nsAString& aValue, - nsITextControlFrame* aFrame, PRBool aUserInput); void ClearFileNames() { @@ -455,6 +481,9 @@ protected: */ PRBool NeedToInitializeEditorForEvent(nsEventChainPreVisitor& aVisitor) const; + void FreeData(); + nsTextEditorState *GetEditorState() const; + nsCOMPtr mControllers; /** @@ -467,10 +496,24 @@ protected: * @see GET_BOOLBIT / SET_BOOLBIT macros and BF_* field identifiers */ PRInt16 mBitField; - /** - * The current value of the input if it has been changed from the default + /* + * In mInputData, the mState field is used if IsSingleLineTextControl returns + * true and mValue is used otherwise. We have to be careful when handling it + * on a type change. + * + * Accessing the mState member should be done using the GetEditorState function, + * which returns null if the state is not present. */ - char* mValue; + union InputData { + /** + * The current value of the input if it has been changed from the default + */ + char* mValue; + /** + * The state of the text editor associated with the text/password input + */ + nsTextEditorState* mState; + } mInputData; /** * The value of the input if it is a file input. This is the list of filenames * used when uploading a file. It is vital that this is kept separate from @@ -503,20 +546,43 @@ nsHTMLInputElement::nsHTMLInputElement(nsINodeInfo *aNodeInfo, PRBool aFromParser) : nsGenericHTMLFormElement(aNodeInfo), mType(kInputDefaultType->value), - mBitField(0), - mValue(nsnull) + mBitField(0) { SET_BOOLBIT(mBitField, BF_PARSER_CREATING, aFromParser); + mInputData.mState = new nsTextEditorState(this); + NS_ADDREF(mInputData.mState); } nsHTMLInputElement::~nsHTMLInputElement() { DestroyImageLoadingContent(); - if (mValue) { - nsMemory::Free(mValue); + FreeData(); +} + +void +nsHTMLInputElement::FreeData() +{ + if (!IsSingleLineTextControl(PR_FALSE)) { + nsMemory::Free(mInputData.mValue); + mInputData.mValue = nsnull; + } else { + NS_IF_RELEASE(mInputData.mState); } } +nsTextEditorState* +nsHTMLInputElement::GetEditorState() const +{ + if (!IsSingleLineTextControl(PR_FALSE)) { + return nsnull; + } + + NS_ASSERTION(mInputData.mState, + "Single line text controls need to have a state associated with them"); + + return mInputData.mState; +} + // nsISupports @@ -524,6 +590,9 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLInputElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLInputElement, nsGenericHTMLFormElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mControllers) + if (tmp->IsSingleLineTextControl(PR_FALSE)) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mInputData.mState, nsTextEditorState) + } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ADDREF_INHERITED(nsHTMLInputElement, nsGenericElement) @@ -578,7 +647,7 @@ nsHTMLInputElement::Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const nsAutoString value; const_cast(this)->GetValue(value); // SetValueInternal handles setting the VALUE_CHANGED bit for us - it->SetValueInternal(value, nsnull, PR_FALSE); + it->SetValueInternal(value, PR_FALSE); } break; case NS_FORM_INPUT_FILE: @@ -707,20 +776,17 @@ nsHTMLInputElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, mType = kInputDefaultType->value; } - // If we are changing type from File/Text/Tel/Passwd - // to other input types we need save the mValue into value attribute - if (mValue && + // If we are changing type from File/Text/Tel/Passwd to other input types + // we need save the mValue into value attribute + if (mInputData.mValue && mType != NS_FORM_INPUT_TEXT && mType != NS_FORM_INPUT_SEARCH && mType != NS_FORM_INPUT_PASSWORD && mType != NS_FORM_INPUT_TEL && mType != NS_FORM_INPUT_FILE) { SetAttr(kNameSpaceID_None, nsGkAtoms::value, - NS_ConvertUTF8toUTF16(mValue), PR_FALSE); - if (mValue) { - nsMemory::Free(mValue); - mValue = nsnull; - } + NS_ConvertUTF8toUTF16(mInputData.mValue), PR_FALSE); + FreeData(); } if (mType != NS_FORM_INPUT_IMAGE) { @@ -889,40 +955,9 @@ nsHTMLInputElement::SetSize(PRUint32 aValue) NS_IMETHODIMP nsHTMLInputElement::GetValue(nsAString& aValue) { - if (IsSingleLineTextControl(PR_FALSE)) { - // No need to flush here, if there's no frame created for this - // input yet, there won't be a value in it (that we don't already - // have) even if we force it to be created - nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE); - - PRBool frameOwnsValue = PR_FALSE; - if (formControlFrame) { - nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame); - if (textControlFrame) { - textControlFrame->OwnsValue(&frameOwnsValue); - } else { - // We assume if it's not a text control frame that it owns the value - frameOwnsValue = PR_TRUE; - } - } - - if (frameOwnsValue) { - formControlFrame->GetFormProperty(nsGkAtoms::value, aValue); - } else { - if (!GET_BOOLBIT(mBitField, BF_VALUE_CHANGED) || !mValue) { - GetDefaultValue(aValue); - } else { - CopyUTF8toUTF16(mValue, aValue); - } - - // If the value is not owned by the frame, then we should handle any - // exiting newline characters inside it, instead of relying on the - // editor to do it for us. - nsString value(aValue); - nsTextEditRules::HandleNewLines(value, -1); - aValue.Assign(value); - } - + nsTextEditorState* state = GetEditorState(); + if (state) { + state->GetValue(aValue, PR_TRUE); return NS_OK; } @@ -979,7 +1014,7 @@ nsHTMLInputElement::SetValue(const nsAString& aValue) } } else { - SetValueInternal(aValue, nsnull, PR_FALSE); + SetValueInternal(aValue, PR_FALSE); } return NS_OK; @@ -1045,21 +1080,99 @@ nsHTMLInputElement::SetUserInput(const nsAString& aValue) { SetSingleFileName(aValue); } else { - SetValueInternal(aValue, nsnull, PR_TRUE); + SetValueInternal(aValue, PR_TRUE); } return NS_OK; } -NS_IMETHODIMP -nsHTMLInputElement::TakeTextFrameValue(const nsAString& aValue) +NS_IMETHODIMP_(nsIEditor*) +nsHTMLInputElement::GetTextEditor() { - if (mValue) { - nsMemory::Free(mValue); + nsTextEditorState *state = GetEditorState(); + if (state) { + return state->GetEditor(); + return nsnull; +} + +NS_IMETHODIMP_(nsISelectionController*) +nsHTMLInputElement::GetSelectionController() +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + return state->GetSelectionController(); + return nsnull; +} + +nsFrameSelection* +nsHTMLInputElement::GetConstFrameSelection() +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + return state->GetConstFrameSelection(); + return nsnull; +} + +NS_IMETHODIMP +nsHTMLInputElement::BindToFrame(nsTextControlFrame* aFrame) +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + return state->BindToFrame(aFrame); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP_(void) +nsHTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame) +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + state->UnbindFromFrame(aFrame); + } +} + +NS_IMETHODIMP +nsHTMLInputElement::CreateEditor() +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + return state->PrepareEditor(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP_(nsIContent*) +nsHTMLInputElement::GetRootEditorNode() +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + return state->GetRootNode(); + return nsnull; +} + +NS_IMETHODIMP_(nsIContent*) +nsHTMLInputElement::GetPlaceholderNode() +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + return state->GetPlaceholderNode(); + return nsnull; +} + +NS_IMETHODIMP_(void) +nsHTMLInputElement::UpdatePlaceholderText(PRBool aNotify) +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + state->UpdatePlaceholderText(aNotify); + } +} + +NS_IMETHODIMP_(void) +nsHTMLInputElement::SetPlaceholderClass(PRBool aVisible, PRBool aNotify) +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + state->SetPlaceholderClass(aVisible, aNotify); } - nsString value(aValue); - nsContentUtils::PlatformToDOMLineBreaks(value); - mValue = ToNewUTF8String(value); - return NS_OK; } void @@ -1158,32 +1271,19 @@ nsHTMLInputElement::UpdateFileList() nsresult nsHTMLInputElement::SetValueInternal(const nsAString& aValue, - nsITextControlFrame* aFrame, PRBool aUserInput) { NS_PRECONDITION(mType != NS_FORM_INPUT_FILE, "Don't call SetValueInternal for file inputs"); if (IsSingleLineTextControl(PR_FALSE)) { - nsIFormControlFrame* formControlFrame = aFrame; - if (!formControlFrame) { - // No need to flush here, if there's no frame at this point we - // don't need to force creation of one just to tell it about this - // new value. - formControlFrame = GetFormControlFrame(PR_FALSE); - } - - if (formControlFrame) { - // Always set the value in the frame. If the frame does not own the - // value yet (per OwnsValue()), it will turn around and call - // TakeTextFrameValue() on us, but will update its display with the new - // value if needed. - return formControlFrame->SetFormProperty( - aUserInput ? nsGkAtoms::userInput : nsGkAtoms::value, aValue); - } - + // Need to set the value changed flag here, so that + // nsTextControlFrame::UpdateValueDisplay retrieves the correct value + // if needed. SetValueChanged(PR_TRUE); - return TakeTextFrameValue(aValue); + mInputData.mState->SetValue(aValue, aUserInput); + + return NS_OK; } if (mType == NS_FORM_INPUT_FILE) { @@ -1210,9 +1310,8 @@ nsHTMLInputElement::SetValueChanged(PRBool aValueChanged) { SET_BOOLBIT(mBitField, BF_VALUE_CHANGED, aValueChanged); if (!aValueChanged) { - if (mValue) { - nsMemory::Free(mValue); - mValue = nsnull; + if (!IsSingleLineTextControl(PR_FALSE)) { + FreeData(); } } return NS_OK; @@ -2281,6 +2380,19 @@ nsHTMLInputElement::ParseAttribute(PRInt32 aNamespaceID, ClearFileNames(); } + // Only single line text inputs have a text editor state. + PRBool isNewTypeSingleLine = + IsSingleLineTextControlInternal(PR_FALSE, newType); + PRBool isCurrentTypeSingleLine = + IsSingleLineTextControl(PR_FALSE); + if (isNewTypeSingleLine && !isCurrentTypeSingleLine) { + FreeData(); + mInputData.mState = new nsTextEditorState(this); + NS_ADDREF(mInputData.mState); + } else if (isCurrentTypeSingleLine && !isNewTypeSingleLine) { + FreeData(); + } + mType = newType; } @@ -2909,7 +3021,7 @@ nsHTMLInputElement::RestoreState(nsPresState* aState) case NS_FORM_INPUT_TEL: case NS_FORM_INPUT_HIDDEN: { - SetValueInternal(inputState->GetValue(), nsnull, PR_FALSE); + SetValueInternal(inputState->GetValue(), PR_FALSE); break; } case NS_FORM_INPUT_FILE: @@ -3286,3 +3398,98 @@ NS_GetRadioGetCheckedChangedVisitor(PRBool* aCheckedChanged, return NS_OK; } +NS_IMETHODIMP_(PRBool) +nsHTMLInputElement::IsSingleLineTextControl() const +{ + return IsSingleLineTextControl(PR_FALSE); +} + +NS_IMETHODIMP_(PRBool) +nsHTMLInputElement::IsTextArea() const +{ + return PR_FALSE; +} + +NS_IMETHODIMP_(PRBool) +nsHTMLInputElement::IsPlainTextControl() const +{ + // need to check our HTML attribute and/or CSS. + return PR_TRUE; +} + +NS_IMETHODIMP_(PRBool) +nsHTMLInputElement::IsPasswordTextControl() const +{ + return mType == NS_FORM_INPUT_PASSWORD; +} + +NS_IMETHODIMP_(PRInt32) +nsHTMLInputElement::GetCols() +{ + // Else we know (assume) it is an input with size attr + const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size); + if (attr && attr->Type() == nsAttrValue::eInteger) { + PRInt32 cols = attr->GetIntegerValue(); + if (cols > 0) { + return cols; + } + } + + return DEFAULT_COLS; +} + +NS_IMETHODIMP_(PRInt32) +nsHTMLInputElement::GetWrapCols() +{ + return -1; // only textarea's can have wrap cols +} + +NS_IMETHODIMP_(PRInt32) +nsHTMLInputElement::GetRows() +{ + return DEFAULT_ROWS; +} + +NS_IMETHODIMP_(void) +nsHTMLInputElement::GetDefaultValueFromContent(nsAString& aValue) +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + GetDefaultValue(aValue); + } +} + +NS_IMETHODIMP_(PRBool) +nsHTMLInputElement::ValueChanged() const +{ + return GET_BOOLBIT(mBitField, BF_VALUE_CHANGED); +} + +NS_IMETHODIMP_(void) +nsHTMLInputElement::GetTextEditorValue(nsAString& aValue, + PRBool aIgnoreWrap) const +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + state->GetValue(aValue, aIgnoreWrap); + } +} + +NS_IMETHODIMP_(void) +nsHTMLInputElement::SetTextEditorValue(const nsAString& aValue, + PRBool aUserInput) +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + state->SetValue(aValue, aUserInput); + } +} + +NS_IMETHODIMP_(void) +nsHTMLInputElement::InitializeKeyboardEventListeners() +{ + nsTextEditorState *state = GetEditorState(); + if (state) { + state->InitializeKeyboardEventListeners(); + } +} diff --git a/content/html/content/src/nsHTMLTextAreaElement.cpp b/content/html/content/src/nsHTMLTextAreaElement.cpp index 056d5913fe6..0d3f2d5eed7 100644 --- a/content/html/content/src/nsHTMLTextAreaElement.cpp +++ b/content/html/content/src/nsHTMLTextAreaElement.cpp @@ -77,6 +77,8 @@ #include "mozAutoDocUpdate.h" #include "nsISupportsPrimitives.h" +#include "nsTextEditorState.h" + static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); #define NS_NO_CONTENT_DISPATCH (1 << 0) @@ -90,7 +92,6 @@ class nsHTMLTextAreaElement : public nsGenericHTMLFormElement, { public: nsHTMLTextAreaElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE); - virtual ~nsHTMLTextAreaElement(); // nsISupports NS_DECL_ISUPPORTS_INHERITED @@ -126,8 +127,29 @@ public: virtual PRBool RestoreState(nsPresState* aState); // nsITextControlElemet - NS_IMETHOD TakeTextFrameValue(const nsAString& aValue); NS_IMETHOD SetValueChanged(PRBool aValueChanged); + NS_IMETHOD_(PRBool) IsSingleLineTextControl() const; + NS_IMETHOD_(PRBool) IsTextArea() const; + NS_IMETHOD_(PRBool) IsPlainTextControl() const; + NS_IMETHOD_(PRBool) IsPasswordTextControl() const; + NS_IMETHOD_(PRInt32) GetCols(); + NS_IMETHOD_(PRInt32) GetWrapCols(); + NS_IMETHOD_(PRInt32) GetRows(); + NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue); + NS_IMETHOD_(PRBool) ValueChanged() const; + NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, PRBool aIgnoreWrap) const; + NS_IMETHOD_(void) SetTextEditorValue(const nsAString& aValue, PRBool aUserInput); + NS_IMETHOD_(nsIEditor*) GetTextEditor(); + NS_IMETHOD_(nsISelectionController*) GetSelectionController(); + NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection(); + NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame); + NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame); + NS_IMETHOD CreateEditor(); + NS_IMETHOD_(nsIContent*) GetRootEditorNode(); + NS_IMETHOD_(nsIContent*) GetPlaceholderNode(); + NS_IMETHOD_(void) UpdatePlaceholderText(PRBool aNotify); + NS_IMETHOD_(void) SetPlaceholderClass(PRBool aVisible, PRBool aNotify); + NS_IMETHOD_(void) InitializeKeyboardEventListeners(); // nsIContent virtual PRBool ParseAttribute(PRInt32 aNamespaceID, @@ -172,9 +194,9 @@ public: nsGenericHTMLFormElement) protected: + using nsGenericHTMLFormElement::IsSingleLineTextControl; // get rid of the compiler warning + nsCOMPtr mControllers; - /** The current value. This is null if the frame owns the value. */ - char* mValue; /** Whether or not the value has changed since its default value was given. */ PRPackedBool mValueChanged; /** Whether or not we are already handling select event. */ @@ -184,6 +206,8 @@ protected: PRPackedBool mDoneAddingChildren; /** Whether our disabled state has changed from the default **/ PRPackedBool mDisabledChanged; + /** The state of the text editor (selection controller and the editor) **/ + nsRefPtr mState; NS_IMETHOD SelectAll(nsPresContext* aPresContext); /** @@ -196,7 +220,6 @@ protected: void GetValueInternal(nsAString& aValue, PRBool aIgnoreWrap); nsresult SetValueInternal(const nsAString& aValue, - nsITextControlFrame* aFrame, PRBool aUserInput); nsresult GetSelectionRange(PRInt32* aSelectionStart, PRInt32* aSelectionEnd); @@ -223,22 +246,15 @@ NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(TextArea) nsHTMLTextAreaElement::nsHTMLTextAreaElement(nsINodeInfo *aNodeInfo, PRBool aFromParser) : nsGenericHTMLFormElement(aNodeInfo), - mValue(nsnull), mValueChanged(PR_FALSE), mHandlingSelect(PR_FALSE), mDoneAddingChildren(!aFromParser), - mDisabledChanged(PR_FALSE) + mDisabledChanged(PR_FALSE), + mState(new nsTextEditorState(this)) { AddMutationObserver(this); } -nsHTMLTextAreaElement::~nsHTMLTextAreaElement() -{ - if (mValue) { - nsMemory::Free(mValue); - } -} - NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLTextAreaElement) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLTextAreaElement, @@ -248,6 +264,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLTextAreaElement, nsGenericHTMLFormElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mControllers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mState, nsTextEditorState) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ADDREF_INHERITED(nsHTMLTextAreaElement, nsGenericElement) @@ -397,79 +414,78 @@ nsHTMLTextAreaElement::GetValue(nsAString& aValue) void nsHTMLTextAreaElement::GetValueInternal(nsAString& aValue, PRBool aIgnoreWrap) { - // Get the frame. - // No need to flush here, if there is no frame yet for this textarea - // there won't be a value in it we don't already have even if we - // force the frame to be created. - nsIFrame* primaryFrame = GetPrimaryFrame(); - nsITextControlFrame* textControlFrame = nsnull; - if (primaryFrame) { - textControlFrame = do_QueryFrame(primaryFrame); - } + mState->GetValue(aValue, aIgnoreWrap); +} - // If the frame exists and owns the value, get it from the frame. Otherwise - // get it from content. - PRBool frameOwnsValue = PR_FALSE; - if (textControlFrame) { - textControlFrame->OwnsValue(&frameOwnsValue); - } - if (frameOwnsValue) { - textControlFrame->GetValue(aValue, aIgnoreWrap); - } else { - if (!mValueChanged || !mValue) { - GetDefaultValue(aValue); - } else { - CopyUTF8toUTF16(mValue, aValue); - } - } +NS_IMETHODIMP_(nsIEditor*) +nsHTMLTextAreaElement::GetTextEditor() +{ + return mState->GetEditor(); +} + +NS_IMETHODIMP_(nsISelectionController*) +nsHTMLTextAreaElement::GetSelectionController() +{ + return mState->GetSelectionController(); +} + +NS_IMETHODIMP_(nsFrameSelection*) +nsHTMLTextAreaElement::GetConstFrameSelection() +{ + return mState->GetConstFrameSelection(); } NS_IMETHODIMP -nsHTMLTextAreaElement::TakeTextFrameValue(const nsAString& aValue) +nsHTMLTextAreaElement::BindToFrame(nsTextControlFrame* aFrame) { - if (mValue) { - nsMemory::Free(mValue); - } - nsString value(aValue); - nsContentUtils::PlatformToDOMLineBreaks(value); - mValue = ToNewUTF8String(value); - return NS_OK; + return mState->BindToFrame(aFrame); +} + +NS_IMETHODIMP_(void) +nsHTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame* aFrame) +{ + mState->UnbindFromFrame(aFrame); +} + +NS_IMETHODIMP +nsHTMLTextAreaElement::CreateEditor() +{ + return mState->PrepareEditor(); +} + +NS_IMETHODIMP_(nsIContent*) +nsHTMLTextAreaElement::GetRootEditorNode() +{ + return mState->GetRootNode(); +} + +NS_IMETHODIMP_(nsIContent*) +nsHTMLTextAreaElement::GetPlaceholderNode() +{ + return mState->GetPlaceholderNode(); +} + +NS_IMETHODIMP_(void) +nsHTMLTextAreaElement::UpdatePlaceholderText(PRBool aNotify) +{ + mState->UpdatePlaceholderText(aNotify); +} + +NS_IMETHODIMP_(void) +nsHTMLTextAreaElement::SetPlaceholderClass(PRBool aVisible, PRBool aNotify) +{ + mState->SetPlaceholderClass(aVisible, aNotify); } nsresult nsHTMLTextAreaElement::SetValueInternal(const nsAString& aValue, - nsITextControlFrame* aFrame, PRBool aUserInput) { - nsITextControlFrame* textControlFrame = aFrame; - nsIFormControlFrame* formControlFrame = textControlFrame; - if (!textControlFrame) { - // No need to flush here, if there is no frame for this yet forcing - // creation of one will not do us any good - formControlFrame = GetFormControlFrame(PR_FALSE); - - if (formControlFrame) { - textControlFrame = do_QueryFrame(formControlFrame); - } - } - - PRBool frameOwnsValue = PR_FALSE; - if (textControlFrame) { - textControlFrame->OwnsValue(&frameOwnsValue); - } - if (frameOwnsValue) { - formControlFrame->SetFormProperty( - aUserInput ? nsGkAtoms::userInput : nsGkAtoms::value, aValue); - } - else { - if (mValue) { - nsMemory::Free(mValue); - } - mValue = ToNewUTF8String(aValue); - NS_ENSURE_TRUE(mValue, NS_ERROR_OUT_OF_MEMORY); - - SetValueChanged(PR_TRUE); - } + // Need to set the value changed flag here, so that + // nsTextControlFrame::UpdateValueDisplay retrieves the correct value + // if needed. + SetValueChanged(PR_TRUE); + mState->SetValue(aValue, aUserInput); return NS_OK; } @@ -477,7 +493,7 @@ nsHTMLTextAreaElement::SetValueInternal(const nsAString& aValue, NS_IMETHODIMP nsHTMLTextAreaElement::SetValue(const nsAString& aValue) { - return SetValueInternal(aValue, nsnull, PR_FALSE); + return SetValueInternal(aValue, PR_FALSE); } NS_IMETHODIMP @@ -486,7 +502,7 @@ nsHTMLTextAreaElement::SetUserInput(const nsAString& aValue) if (!nsContentUtils::IsCallerTrustedForWrite()) { return NS_ERROR_DOM_SECURITY_ERR; } - SetValueInternal(aValue, nsnull, PR_TRUE); + SetValueInternal(aValue, PR_TRUE); return NS_OK; } @@ -494,9 +510,8 @@ NS_IMETHODIMP nsHTMLTextAreaElement::SetValueChanged(PRBool aValueChanged) { mValueChanged = aValueChanged; - if (!aValueChanged && mValue) { - nsMemory::Free(mValue); - mValue = nsnull; + if (!aValueChanged && !mState->IsEmpty()) { + mState->EmptyValue(); } return NS_OK; } @@ -1003,3 +1018,101 @@ nsHTMLTextAreaElement::CopyInnerTo(nsGenericElement* aDest) const return NS_OK; } +NS_IMETHODIMP_(PRBool) +nsHTMLTextAreaElement::IsSingleLineTextControl() const +{ + return PR_FALSE; +} + +NS_IMETHODIMP_(PRBool) +nsHTMLTextAreaElement::IsTextArea() const +{ + return PR_TRUE; +} + +NS_IMETHODIMP_(PRBool) +nsHTMLTextAreaElement::IsPlainTextControl() const +{ + // need to check our HTML attribute and/or CSS. + return PR_TRUE; +} + +NS_IMETHODIMP_(PRBool) +nsHTMLTextAreaElement::IsPasswordTextControl() const +{ + return PR_FALSE; +} + +NS_IMETHODIMP_(PRInt32) +nsHTMLTextAreaElement::GetCols() +{ + const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::cols); + if (attr) { + PRInt32 cols = attr->Type() == nsAttrValue::eInteger ? + attr->GetIntegerValue() : 0; + // XXX why a default of 1 char, why hide it + return (cols <= 0) ? 1 : cols; + } + + return DEFAULT_COLS; +} + +NS_IMETHODIMP_(PRInt32) +nsHTMLTextAreaElement::GetWrapCols() +{ + // wrap=off means -1 for wrap width no matter what cols is + nsHTMLTextWrap wrapProp; + nsITextControlElement::GetWrapPropertyEnum(this, wrapProp); + if (wrapProp == nsITextControlElement::eHTMLTextWrap_Off) { + // do not wrap when wrap=off + return -1; + } + + // Otherwise we just wrap at the given number of columns + return GetCols(); +} + + +NS_IMETHODIMP_(PRInt32) +nsHTMLTextAreaElement::GetRows() +{ + const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::rows); + if (attr && attr->Type() == nsAttrValue::eInteger) { + PRInt32 rows = attr->GetIntegerValue(); + return (rows <= 0) ? DEFAULT_ROWS_TEXTAREA : rows; + } + + return DEFAULT_ROWS_TEXTAREA; +} + +NS_IMETHODIMP_(void) +nsHTMLTextAreaElement::GetDefaultValueFromContent(nsAString& aValue) +{ + GetDefaultValue(aValue); +} + +NS_IMETHODIMP_(PRBool) +nsHTMLTextAreaElement::ValueChanged() const +{ + return mValueChanged; +} + +NS_IMETHODIMP_(void) +nsHTMLTextAreaElement::GetTextEditorValue(nsAString& aValue, + PRBool aIgnoreWrap) const +{ + mState->GetValue(aValue, aIgnoreWrap); +} + +NS_IMETHODIMP_(void) +nsHTMLTextAreaElement::SetTextEditorValue(const nsAString& aValue, + PRBool aUserInput) +{ + mState->SetValue(aValue, aUserInput); +} + +NS_IMETHODIMP_(void) +nsHTMLTextAreaElement::InitializeKeyboardEventListeners() +{ + mState->InitializeKeyboardEventListeners(); +} diff --git a/content/html/content/src/nsTextEditorState.cpp b/content/html/content/src/nsTextEditorState.cpp new file mode 100644 index 00000000000..295e67190f0 --- /dev/null +++ b/content/html/content/src/nsTextEditorState.cpp @@ -0,0 +1,1890 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 et tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla.org client code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ehsan Akhgari (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsCOMPtr.h" +#include "nsIPresShell.h" +#include "nsIView.h" +#include "nsCaret.h" +#include "nsEditorCID.h" +#include "nsLayoutCID.h" +#include "nsITextControlFrame.h" +#include "nsIPlaintextEditor.h" +#include "nsIDOMDocument.h" +#include "nsContentCreatorFunctions.h" +#include "nsTextControlFrame.h" +#include "nsIControllers.h" +#include "nsIDOMNSHTMLInputElement.h" +#include "nsIDOMNSHTMLTextAreaElement.h" +#include "nsITransactionManager.h" +#include "nsIControllerContext.h" +#include "nsAttrValue.h" +#include "nsGenericHTMLElement.h" +#include "nsIDOMKeyListener.h" +#include "nsIEditorObserver.h" +#include "nsINativeKeyBindings.h" +#include "nsIDocumentEncoder.h" +#include "nsISelectionPrivate.h" +#include "nsPIDOMWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsIDOMEventGroup.h" +#include "nsIEditor.h" +#include "nsTextEditRules.h" + +#include "nsTextEditorState.h" + +static NS_DEFINE_CID(kTextEditorCID, NS_TEXTEDITOR_CID); +static NS_DEFINE_CID(kFrameSelectionCID, NS_FRAMESELECTION_CID); + +static nsINativeKeyBindings *sNativeInputBindings = nsnull; +static nsINativeKeyBindings *sNativeTextAreaBindings = nsnull; + +/*static*/ +PRBool +nsITextControlElement::GetWrapPropertyEnum(nsIContent* aContent, + nsITextControlElement::nsHTMLTextWrap& aWrapProp) +{ + // soft is the default; "physical" defaults to soft as well because all other + // browsers treat it that way and there is no real reason to maintain physical + // and virtual as separate entities if no one else does. Only hard and off + // do anything different. + aWrapProp = eHTMLTextWrap_Soft; // the default + + nsAutoString wrap; + if (aContent->IsHTML()) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::HARD, &nsGkAtoms::OFF, nsnull}; + + switch (aContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::wrap, + strings, eIgnoreCase)) { + case 0: aWrapProp = eHTMLTextWrap_Hard; break; + case 1: aWrapProp = eHTMLTextWrap_Off; break; + } + + return PR_TRUE; + } + + return PR_FALSE; +} + +static PRBool +SuppressEventHandlers(nsPresContext* aPresContext) +{ + PRBool suppressHandlers = PR_FALSE; + + if (aPresContext) + { + // Right now we only suppress event handlers and controller manipulation + // when in a print preview or print context! + + // In the current implementation, we only paginate when + // printing or in print preview. + + suppressHandlers = aPresContext->IsPaginated(); + } + + return suppressHandlers; +} + +class nsAnonDivObserver : public nsStubMutationObserver +{ +public: + nsAnonDivObserver(nsTextEditorState* aTextEditorState) + : mTextEditorState(aTextEditorState) {} + NS_DECL_ISUPPORTS + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + +private: + nsTextEditorState* mTextEditorState; +}; + +class nsTextInputSelectionImpl : public nsSupportsWeakReference + , public nsISelectionController +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController) + + nsTextInputSelectionImpl(nsFrameSelection *aSel, nsIPresShell *aShell, nsIContent *aLimiter); + ~nsTextInputSelectionImpl(){} + + void SetScrollableFrame(nsIScrollableFrame *aScrollableFrame); + nsFrameSelection* GetConstFrameSelection() + { return mFrameSelection; } + + //NSISELECTIONCONTROLLER INTERFACES + NS_IMETHOD SetDisplaySelection(PRInt16 toggle); + NS_IMETHOD GetDisplaySelection(PRInt16 *_retval); + NS_IMETHOD SetSelectionFlags(PRInt16 aInEnable); + NS_IMETHOD GetSelectionFlags(PRInt16 *aOutEnable); + NS_IMETHOD GetSelection(PRInt16 type, nsISelection **_retval); + NS_IMETHOD ScrollSelectionIntoView(PRInt16 aType, PRInt16 aRegion, PRBool aIsSynchronous); + NS_IMETHOD RepaintSelection(PRInt16 type); + NS_IMETHOD RepaintSelection(nsPresContext* aPresContext, SelectionType aSelectionType); + NS_IMETHOD SetCaretEnabled(PRBool enabled); + NS_IMETHOD SetCaretReadOnly(PRBool aReadOnly); + NS_IMETHOD GetCaretEnabled(PRBool *_retval); + NS_IMETHOD GetCaretVisible(PRBool *_retval); + NS_IMETHOD SetCaretVisibilityDuringSelection(PRBool aVisibility); + NS_IMETHOD CharacterMove(PRBool aForward, PRBool aExtend); + NS_IMETHOD CharacterExtendForDelete(); + NS_IMETHOD WordMove(PRBool aForward, PRBool aExtend); + NS_IMETHOD WordExtendForDelete(PRBool aForward); + NS_IMETHOD LineMove(PRBool aForward, PRBool aExtend); + NS_IMETHOD IntraLineMove(PRBool aForward, PRBool aExtend); + NS_IMETHOD PageMove(PRBool aForward, PRBool aExtend); + NS_IMETHOD CompleteScroll(PRBool aForward); + NS_IMETHOD CompleteMove(PRBool aForward, PRBool aExtend); + NS_IMETHOD ScrollPage(PRBool aForward); + NS_IMETHOD ScrollLine(PRBool aForward); + NS_IMETHOD ScrollHorizontal(PRBool aLeft); + NS_IMETHOD SelectAll(void); + NS_IMETHOD CheckVisibility(nsIDOMNode *node, PRInt16 startOffset, PRInt16 EndOffset, PRBool *_retval); + +private: + nsCOMPtr mFrameSelection; + nsCOMPtr mLimiter; + nsIScrollableFrame *mScrollFrame; + nsWeakPtr mPresShellWeak; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController) +NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController) +NS_INTERFACE_TABLE_HEAD(nsTextInputSelectionImpl) + NS_INTERFACE_TABLE3(nsTextInputSelectionImpl, + nsISelectionController, + nsISelectionDisplay, + nsISupportsWeakReference) + NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsTextInputSelectionImpl) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_2(nsTextInputSelectionImpl, mFrameSelection, mLimiter) + + +// BEGIN nsTextInputSelectionImpl + +nsTextInputSelectionImpl::nsTextInputSelectionImpl(nsFrameSelection *aSel, + nsIPresShell *aShell, + nsIContent *aLimiter) + : mScrollFrame(nsnull) +{ + if (aSel && aShell) + { + mFrameSelection = aSel;//we are the owner now! + mLimiter = aLimiter; + mFrameSelection->Init(aShell, mLimiter); + mPresShellWeak = do_GetWeakReference(aShell); + } +} + +void +nsTextInputSelectionImpl::SetScrollableFrame(nsIScrollableFrame *aScrollableFrame) +{ + mScrollFrame = aScrollableFrame; + if (!mScrollFrame && mFrameSelection) { + mFrameSelection->DisconnectFromPresShell(); + mFrameSelection = nsnull; + } +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetDisplaySelection(PRInt16 aToggle) +{ + if (!mFrameSelection) + return NS_ERROR_NULL_POINTER; + + mFrameSelection->SetDisplaySelection(aToggle); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetDisplaySelection(PRInt16 *aToggle) +{ + if (!mFrameSelection) + return NS_ERROR_NULL_POINTER; + + *aToggle = mFrameSelection->GetDisplaySelection(); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetSelectionFlags(PRInt16 aToggle) +{ + return NS_OK;//stub this out. not used in input +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetSelectionFlags(PRInt16 *aOutEnable) +{ + *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT; + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetSelection(PRInt16 type, nsISelection **_retval) +{ + if (!mFrameSelection) + return NS_ERROR_NULL_POINTER; + + *_retval = mFrameSelection->GetSelection(type); + + if (!(*_retval)) + return NS_ERROR_FAILURE; + + NS_ADDREF(*_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollSelectionIntoView(PRInt16 aType, PRInt16 aRegion, PRBool aIsSynchronous) +{ + if (!mFrameSelection) + return NS_ERROR_FAILURE; + + return mFrameSelection->ScrollSelectionIntoView(aType, aRegion, aIsSynchronous); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::RepaintSelection(PRInt16 type) +{ + if (!mFrameSelection) + return NS_ERROR_FAILURE; + + return mFrameSelection->RepaintSelection(type); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::RepaintSelection(nsPresContext* aPresContext, SelectionType aSelectionType) +{ + if (!mFrameSelection) + return NS_ERROR_FAILURE; + + return mFrameSelection->RepaintSelection(aSelectionType); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetCaretEnabled(PRBool enabled) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr shell = do_QueryReferent(mPresShellWeak); + if (!shell) return NS_ERROR_FAILURE; + + // tell the pres shell to enable the caret, rather than settings its visibility directly. + // this way the presShell's idea of caret visibility is maintained. + nsCOMPtr selCon = do_QueryInterface(shell); + if (!selCon) return NS_ERROR_NO_INTERFACE; + selCon->SetCaretEnabled(enabled); + + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetCaretReadOnly(PRBool aReadOnly) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + nsRefPtr caret = shell->GetCaret(); + if (caret) { + nsISelection* domSel = mFrameSelection-> + GetSelection(nsISelectionController::SELECTION_NORMAL); + if (domSel) + caret->SetCaretReadOnly(aReadOnly); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetCaretEnabled(PRBool *_retval) +{ + return GetCaretVisible(_retval); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetCaretVisible(PRBool *_retval) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + nsRefPtr caret = shell->GetCaret(); + if (caret) { + nsISelection* domSel = mFrameSelection-> + GetSelection(nsISelectionController::SELECTION_NORMAL); + if (domSel) + return caret->GetCaretVisible(_retval); + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(PRBool aVisibility) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + nsRefPtr caret = shell->GetCaret(); + if (caret) { + nsISelection* domSel = mFrameSelection-> + GetSelection(nsISelectionController::SELECTION_NORMAL); + if (domSel) + caret->SetVisibilityDuringSelection(aVisibility); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CharacterMove(PRBool aForward, PRBool aExtend) +{ + if (mFrameSelection) + return mFrameSelection->CharacterMove(aForward, aExtend); + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CharacterExtendForDelete() +{ + if (mFrameSelection) + return mFrameSelection->CharacterExtendForDelete(); + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::WordMove(PRBool aForward, PRBool aExtend) +{ + if (mFrameSelection) + return mFrameSelection->WordMove(aForward, aExtend); + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::WordExtendForDelete(PRBool aForward) +{ + if (mFrameSelection) + return mFrameSelection->WordExtendForDelete(aForward); + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::LineMove(PRBool aForward, PRBool aExtend) +{ + if (mFrameSelection) + { + nsresult result = mFrameSelection->LineMove(aForward, aExtend); + if (NS_FAILED(result)) + result = CompleteMove(aForward,aExtend); + return result; + } + return NS_ERROR_NULL_POINTER; +} + + +NS_IMETHODIMP +nsTextInputSelectionImpl::IntraLineMove(PRBool aForward, PRBool aExtend) +{ + if (mFrameSelection) + return mFrameSelection->IntraLineMove(aForward, aExtend); + return NS_ERROR_NULL_POINTER; +} + + +NS_IMETHODIMP +nsTextInputSelectionImpl::PageMove(PRBool aForward, PRBool aExtend) +{ + // expected behavior for PageMove is to scroll AND move the caret + // and to remain relative position of the caret in view. see Bug 4302. + if (mScrollFrame) + { + mFrameSelection->CommonPageMove(aForward, aExtend, mScrollFrame); + } + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_FOCUS_REGION, PR_TRUE); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CompleteScroll(PRBool aForward) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), + nsIScrollableFrame::WHOLE, + nsIScrollableFrame::INSTANT); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CompleteMove(PRBool aForward, PRBool aExtend) +{ + // grab the parent / root DIV for this text widget + nsIContent* parentDIV = mFrameSelection->GetLimiter(); + if (!parentDIV) + return NS_ERROR_UNEXPECTED; + + // make the caret be either at the very beginning (0) or the very end + PRInt32 offset = 0; + nsFrameSelection::HINT hint = nsFrameSelection::HINTLEFT; + if (aForward) + { + offset = parentDIV->GetChildCount(); + + // Prevent the caret from being placed after the last + // BR node in the content tree! + + if (offset > 0) + { + nsIContent *child = parentDIV->GetChildAt(offset - 1); + + if (child->Tag() == nsGkAtoms::br) + { + --offset; + hint = nsFrameSelection::HINTRIGHT; // for Bug 106855 + } + } + } + + mFrameSelection->HandleClick(parentDIV, offset, offset, aExtend, + PR_FALSE, hint); + + // if we got this far, attempt to scroll no matter what the above result is + return CompleteScroll(aForward); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollPage(PRBool aForward) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), + nsIScrollableFrame::PAGES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollLine(PRBool aForward) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), + nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollHorizontal(PRBool aLeft) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mScrollFrame->ScrollBy(nsIntPoint(aLeft ? -1 : 1, 0), + nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SelectAll() +{ + if (mFrameSelection) + return mFrameSelection->SelectAll(); + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CheckVisibility(nsIDOMNode *node, PRInt16 startOffset, PRInt16 EndOffset, PRBool *_retval) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + return shell->CheckVisibility(node,startOffset,EndOffset, _retval); + } + return NS_ERROR_FAILURE; + +} + +class nsTextInputListener : public nsISelectionListener, + public nsIDOMKeyListener, + public nsIEditorObserver, + public nsSupportsWeakReference +{ +public: + /** the default constructor + */ + explicit nsTextInputListener(nsITextControlElement* aTxtCtrlElement); + /** the default destructor. virtual due to the possibility of derivation. + */ + virtual ~nsTextInputListener(); + + /** SetEditor gives an address to the editor that will be accessed + * @param aEditor the editor this listener calls for editing operations + */ + void SetFrame(nsTextControlFrame *aFrame){mFrame = aFrame;} + + NS_DECL_ISUPPORTS + + NS_DECL_NSISELECTIONLISTENER + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent); + + // nsIDOMKeyListener + NS_IMETHOD KeyDown(nsIDOMEvent *aKeyEvent); + NS_IMETHOD KeyPress(nsIDOMEvent *aKeyEvent); + NS_IMETHOD KeyUp(nsIDOMEvent *aKeyEvent); + + NS_DECL_NSIEDITOROBSERVER + +protected: + + nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate); + + NS_HIDDEN_(nsINativeKeyBindings*) GetKeyBindings(); + +protected: + + nsTextControlFrame* mFrame; // weak reference + + nsITextControlElement* const mTxtCtrlElement; + + PRPackedBool mSelectionWasCollapsed; + /** + * Whether we had undo items or not the last time we got EditAction() + * notification (when this state changes we update undo and redo menus) + */ + PRPackedBool mHadUndoItems; + /** + * Whether we had redo items or not the last time we got EditAction() + * notification (when this state changes we update undo and redo menus) + */ + PRPackedBool mHadRedoItems; +}; + + +/* + * nsTextInputListener implementation + */ + +nsTextInputListener::nsTextInputListener(nsITextControlElement* aTxtCtrlElement) +: mFrame(nsnull) +, mTxtCtrlElement(aTxtCtrlElement) +, mSelectionWasCollapsed(PR_TRUE) +, mHadUndoItems(PR_FALSE) +, mHadRedoItems(PR_FALSE) +{ +} + +nsTextInputListener::~nsTextInputListener() +{ +} + +NS_IMPL_ADDREF(nsTextInputListener) +NS_IMPL_RELEASE(nsTextInputListener) + +NS_INTERFACE_MAP_BEGIN(nsTextInputListener) + NS_INTERFACE_MAP_ENTRY(nsISelectionListener) + NS_INTERFACE_MAP_ENTRY(nsIEditorObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMKeyListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMKeyListener) +NS_INTERFACE_MAP_END + +// BEGIN nsIDOMSelectionListener + +NS_IMETHODIMP +nsTextInputListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel, PRInt16 aReason) +{ + PRBool collapsed; + if (!mFrame || !aDoc || !aSel || NS_FAILED(aSel->GetIsCollapsed(&collapsed))) + return NS_OK; + + // Fire the select event + // The specs don't exactly say when we should fire the select event. + // IE: Whenever you add/remove a character to/from the selection. Also + // each time for select all. Also if you get to the end of the text + // field you will get new event for each keypress or a continuous + // stream of events if you use the mouse. IE will fire select event + // when the selection collapses to nothing if you are holding down + // the shift or mouse button. + // Mozilla: If we have non-empty selection we will fire a new event for each + // keypress (or mouseup) if the selection changed. Mozilla will also + // create the event each time select all is called, even if everything + // was previously selected, becase technically select all will first collapse + // and then extend. Mozilla will never create an event if the selection + // collapses to nothing. + if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON | + nsISelectionListener::KEYPRESS_REASON | + nsISelectionListener::SELECTALL_REASON))) + { + nsIContent* content = mFrame->GetContent(); + if (content) + { + nsCOMPtr doc = content->GetDocument(); + if (doc) + { + nsCOMPtr presShell = doc->GetPrimaryShell(); + if (presShell) + { + nsEventStatus status = nsEventStatus_eIgnore; + nsEvent event(PR_TRUE, NS_FORM_SELECTED); + + presShell->HandleEventWithTarget(&event, mFrame, content, &status); + } + } + } + } + + // if the collapsed state did not change, don't fire notifications + if (collapsed == mSelectionWasCollapsed) + return NS_OK; + + mSelectionWasCollapsed = collapsed; + + if (!mFrame || !nsContentUtils::IsFocusedContent(mFrame->GetContent())) + return NS_OK; + + return UpdateTextInputCommands(NS_LITERAL_STRING("select")); +} + +// END nsIDOMSelectionListener + +// BEGIN nsIDOMKeyListener + +NS_IMETHODIMP +nsTextInputListener::HandleEvent(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + +static void +DoCommandCallback(const char *aCommand, void *aData) +{ + nsTextControlFrame *frame = static_cast(aData); + nsIContent *content = frame->GetContent(); + + nsCOMPtr controllers; + nsCOMPtr input = do_QueryInterface(content); + if (input) { + input->GetControllers(getter_AddRefs(controllers)); + } else { + nsCOMPtr textArea = + do_QueryInterface(content); + + if (textArea) { + textArea->GetControllers(getter_AddRefs(controllers)); + } + } + + if (!controllers) { + NS_WARNING("Could not get controllers"); + return; + } + + nsCOMPtr controller; + controllers->GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (controller) { + controller->DoCommand(aCommand); + } +} + + +NS_IMETHODIMP +nsTextInputListener::KeyDown(nsIDOMEvent *aDOMEvent) +{ + nsCOMPtr keyEvent(do_QueryInterface(aDOMEvent)); + NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG); + + nsNativeKeyEvent nativeEvent; + nsINativeKeyBindings *bindings = GetKeyBindings(); + if (bindings && + nsContentUtils::DOMEventToNativeKeyEvent(keyEvent, &nativeEvent, PR_FALSE)) { + if (bindings->KeyDown(nativeEvent, DoCommandCallback, mFrame)) { + aDOMEvent->PreventDefault(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputListener::KeyPress(nsIDOMEvent *aDOMEvent) +{ + nsCOMPtr keyEvent(do_QueryInterface(aDOMEvent)); + NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG); + + nsNativeKeyEvent nativeEvent; + nsINativeKeyBindings *bindings = GetKeyBindings(); + if (bindings && + nsContentUtils::DOMEventToNativeKeyEvent(keyEvent, &nativeEvent, PR_TRUE)) { + if (bindings->KeyPress(nativeEvent, DoCommandCallback, mFrame)) { + aDOMEvent->PreventDefault(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputListener::KeyUp(nsIDOMEvent *aDOMEvent) +{ + nsCOMPtr keyEvent(do_QueryInterface(aDOMEvent)); + NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG); + + nsNativeKeyEvent nativeEvent; + nsINativeKeyBindings *bindings = GetKeyBindings(); + if (bindings && + nsContentUtils::DOMEventToNativeKeyEvent(keyEvent, &nativeEvent, PR_FALSE)) { + if (bindings->KeyUp(nativeEvent, DoCommandCallback, mFrame)) { + aDOMEvent->PreventDefault(); + } + } + + return NS_OK; +} +// END nsIDOMKeyListener + +// BEGIN nsIEditorObserver + +NS_IMETHODIMP +nsTextInputListener::EditAction() +{ + // + // Update the undo / redo menus + // + nsCOMPtr editor; + mFrame->GetEditor(getter_AddRefs(editor)); + + nsCOMPtr manager; + editor->GetTransactionManager(getter_AddRefs(manager)); + NS_ENSURE_TRUE(manager, NS_ERROR_FAILURE); + + // Get the number of undo / redo items + PRInt32 numUndoItems = 0; + PRInt32 numRedoItems = 0; + manager->GetNumberOfUndoItems(&numUndoItems); + manager->GetNumberOfRedoItems(&numRedoItems); + if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) || + (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) { + // Modify the menu if undo or redo items are different + UpdateTextInputCommands(NS_LITERAL_STRING("undo")); + + mHadUndoItems = numUndoItems != 0; + mHadRedoItems = numRedoItems != 0; + } + + // Make sure we know we were changed (do NOT set this to false if there are + // no undo items; JS could change the value and we'd still need to save it) + mFrame->SetValueChanged(PR_TRUE); + + // Fire input event + mFrame->FireOnInput(); + + return NS_OK; +} + +// END nsIEditorObserver + + +nsresult +nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate) +{ + NS_ENSURE_STATE(mFrame); + + nsIContent* content = mFrame->GetContent(); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + nsCOMPtr doc = content->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsPIDOMWindow *domWindow = doc->GetWindow(); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + + return domWindow->UpdateCommands(commandsToUpdate); +} + +nsINativeKeyBindings* +nsTextInputListener::GetKeyBindings() +{ + if (mTxtCtrlElement->IsTextArea()) { + static PRBool sNoTextAreaBindings = PR_FALSE; + + if (!sNativeTextAreaBindings && !sNoTextAreaBindings) { + CallGetService(NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "textarea", + &sNativeTextAreaBindings); + + if (!sNativeTextAreaBindings) { + sNoTextAreaBindings = PR_TRUE; + } + } + + return sNativeTextAreaBindings; + } + + static PRBool sNoInputBindings = PR_FALSE; + if (!sNativeInputBindings && !sNoInputBindings) { + CallGetService(NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "input", + &sNativeInputBindings); + + if (!sNativeInputBindings) { + sNoInputBindings = PR_TRUE; + } + } + + return sNativeInputBindings; +} + +// END nsTextInputListener + +// nsTextEditorState + +nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement) + : mTextCtrlElement(aOwningElement), + mBoundFrame(nsnull), + mTextListener(nsnull), + mEditorInitialized(PR_FALSE) +{ + MOZ_COUNT_CTOR(nsTextEditorState); +} + +nsTextEditorState::~nsTextEditorState() +{ + MOZ_COUNT_DTOR(nsTextEditorState); + Clear(); +} + +void +nsTextEditorState::Clear() +{ + if (mBoundFrame) { + // Oops, we still have a frame! + // This should happen when the type of a text input control is being changed + // to something which is not a text control. In this case, we should pretend + // that a frame is being destroyed, and clean up after ourselves properly. + UnbindFromFrame(mBoundFrame); + mEditor = nsnull; + } else { + // If we have a bound frame around, UnbindFromFrame will call DestroyEditor + // for us. + DestroyEditor(); + } + NS_IF_RELEASE(mTextListener); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsTextEditorState) +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTextEditorState, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTextEditorState, Release) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(nsTextEditorState) + tmp->Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(nsTextEditorState) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mSelCon, nsISelectionController) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mEditor) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRootNode) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPlaceholderDiv) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +nsFrameSelection* +nsTextEditorState::GetConstFrameSelection() { + if (mSelCon) + return mSelCon->GetConstFrameSelection(); + return nsnull; +} + +nsIEditor* +nsTextEditorState::GetEditor() +{ + if (!mEditor) { + nsresult rv = PrepareEditor(); + NS_ENSURE_SUCCESS(rv, nsnull); + } + return mEditor; +} + +nsISelectionController* +nsTextEditorState::GetSelectionController() const +{ + return mSelCon; +} + +nsresult +nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame) +{ + NS_ASSERTION(aFrame, "The frame to bind to should be valid"); + NS_ENSURE_ARG_POINTER(aFrame); + + NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first"); + NS_ENSURE_TRUE(!mBoundFrame, NS_ERROR_FAILURE); + + // If we'll need to transfer our current value to the editor, save it before + // binding to the frame. + nsAutoString currentValue; + if (mEditor) { + GetValue(currentValue, PR_TRUE); + } + + mBoundFrame = aFrame; + + nsIContent *rootNode = GetRootNode(); + + nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell(); + NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); + + // Create selection + nsresult rv; + nsCOMPtr frameSel; + frameSel = do_CreateInstance(kFrameSelectionCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a SelectionController + mSelCon = new nsTextInputSelectionImpl(frameSel, shell, rootNode); + NS_ENSURE_TRUE(mSelCon, NS_ERROR_OUT_OF_MEMORY); + mTextListener = new nsTextInputListener(mTextCtrlElement); + NS_ENSURE_TRUE(mTextListener, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(mTextListener); + + mTextListener->SetFrame(mBoundFrame); + mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); + + // Get the caret and make it a selection listener. + nsRefPtr domSelection; + if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection))) && + domSelection) { + nsCOMPtr selPriv(do_QueryInterface(domSelection)); + nsRefPtr caret = shell->GetCaret(); + nsCOMPtr listener; + if (caret) { + listener = do_QueryInterface(caret); + if (listener) { + selPriv->AddSelectionListener(listener); + } + } + + selPriv->AddSelectionListener(static_cast + (mTextListener)); + } + + // If an editor exists from before, prepare it for usage + if (mEditor) { + class PrepareEditorEvent : public nsRunnable { + public: + PrepareEditorEvent(nsTextEditorState &aState, + nsIContent *aOwnerContent, + const nsAString &aCurrentValue) + : mState(aState) + , mOwnerContent(aOwnerContent) + , mCurrentValue(aCurrentValue) + { + } + + NS_IMETHOD Run() { + // Transfer the saved value to the editor if we have one + const nsAString *value = nsnull; + if (!mCurrentValue.IsEmpty()) { + value = &mCurrentValue; + } + + mState.PrepareEditor(value); + + return NS_OK; + } + + private: + nsTextEditorState &mState; + nsCOMPtr mOwnerContent; // strong reference + nsAutoString mCurrentValue; + }; + + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + if (!nsContentUtils::AddScriptRunner( + new PrepareEditorEvent(*this, content, currentValue))) + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult +nsTextEditorState::PrepareEditor(const nsAString *aValue) +{ + if (!mBoundFrame) { + // Cannot create an editor without a bound frame. + // Don't return a failure code, because js callers can't handle that. + return NS_OK; + } + + if (mEditorInitialized) { + // Do not initialize the editor multiple times. + return NS_OK; + } + + // Note that we don't check mEditor here, because we might already have one + // around, in which case we don't create a new one, and we'll just tie the + // required machinery to it. + + nsPresContext *presContext = mBoundFrame->PresContext(); + nsIPresShell *shell = presContext->GetPresShell(); + + // Setup the editor flags + PRUint32 editorFlags = 0; + if (IsPlainTextControl()) + editorFlags |= nsIPlaintextEditor::eEditorPlaintextMask; + if (IsSingleLineTextControl()) + editorFlags |= nsIPlaintextEditor::eEditorSingleLineMask; + if (IsPasswordTextControl()) + editorFlags |= nsIPlaintextEditor::eEditorPasswordMask; + + // All nsTextControlFrames are widgets + editorFlags |= nsIPlaintextEditor::eEditorWidgetMask; + + // Use async reflow and painting for text widgets to improve + // performance. + + // XXX: Using editor async updates exposes bugs 158782, 151882, + // and 165130, so we're disabling it for now, until they + // can be addressed. + // editorFlags |= nsIPlaintextEditor::eEditorUseAsyncUpdatesMask; + + PRBool shouldInitializeEditor = PR_FALSE; + nsCOMPtr newEditor; // the editor that we might create + nsresult rv; + if (!mEditor) { + shouldInitializeEditor = PR_TRUE; + + // Create an editor + newEditor = do_CreateInstance(kTextEditorCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure we clear out the non-breaking space before we initialize the editor + rv = mBoundFrame->UpdateValueDisplay(PR_FALSE, PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + } else { + if (aValue) { + // Set the correct value in the root node + rv = mBoundFrame->UpdateValueDisplay(PR_FALSE, PR_FALSE, aValue); + NS_ENSURE_SUCCESS(rv, rv); + } + + newEditor = mEditor; // just pretend that we have a new editor! + } + + if (!mEditorInitialized) { + // Now initialize the editor. + // + // NOTE: Conversion of '\n' to
happens inside the + // editor's Init() call. + + // Get the DOM document + nsCOMPtr domdoc = do_QueryInterface(shell->GetDocument()); + if (!domdoc) + return NS_ERROR_FAILURE; + + // What follows is a bit of a hack. The editor uses the public DOM APIs + // for its content manipulations, and it causes it to fail some security + // checks deep inside when initializing. So we push a null JSContext + // on the JS stack here to make it clear that we're native code. + // Note that any script that's directly trying to access our value + // has to be going through some scriptable object to do that and that + // already does the relevant security checks. + nsCxPusher pusher; + pusher.PushNull(); + + rv = newEditor->Init(domdoc, shell, GetRootNode(), mSelCon, editorFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Initialize the controller for the editor + + if (!SuppressEventHandlers(presContext)) { + nsCOMPtr controllers; + nsCOMPtr inputElement = + do_QueryInterface(mTextCtrlElement); + if (inputElement) { + rv = inputElement->GetControllers(getter_AddRefs(controllers)); + } else { + nsCOMPtr textAreaElement = + do_QueryInterface(mTextCtrlElement); + + if (!textAreaElement) + return NS_ERROR_FAILURE; + + rv = textAreaElement->GetControllers(getter_AddRefs(controllers)); + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (controllers) { + PRUint32 numControllers; + PRBool found = PR_FALSE; + rv = controllers->GetControllerCount(&numControllers); + for (PRUint32 i = 0; i < numControllers; i ++) { + nsCOMPtr controller; + rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); + if (NS_SUCCEEDED(rv) && controller) { + nsCOMPtr editController = + do_QueryInterface(controller); + if (editController) { + editController->SetCommandContext(newEditor); + found = PR_TRUE; + } + } + } + if (!found) + rv = NS_ERROR_FAILURE; + } + } + + if (shouldInitializeEditor) { + // Initialize the plaintext editor + nsCOMPtr textEditor(do_QueryInterface(newEditor)); + if (textEditor) { + // Set up wrapping + textEditor->SetWrapColumn(GetWrapCols()); + + // Set max text field length + PRInt32 maxLength; + if (GetMaxLength(&maxLength)) { + textEditor->SetMaxTextLength(maxLength); + } + } + } + + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + if (content) { + rv = newEditor->GetFlags(&editorFlags); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if the readonly attribute is set. + if (content->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) + editorFlags |= nsIPlaintextEditor::eEditorReadonlyMask; + + // Check if the disabled attribute is set. + if (content->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) + editorFlags |= nsIPlaintextEditor::eEditorDisabledMask; + + // Disable the selection if necessary. + if (editorFlags & nsIPlaintextEditor::eEditorDisabledMask) + mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_OFF); + + newEditor->SetFlags(editorFlags); + } + + // Get the current value of the textfield from the content. + // Note that if we've created a new editor, mEditor is null at this stage, + // so we will get the real value from the content. + nsAutoString defaultValue; + if (aValue) { + defaultValue = *aValue; + } else { + GetValue(defaultValue, PR_TRUE); + } + + if (shouldInitializeEditor) { + // Hold on to the newly created editor + mEditor = newEditor; + } + + // If we have a default value, insert it under the div we created + // above, but be sure to use the editor so that '*' characters get + // displayed for password fields, etc. SetValue() will call the + // editor for us. + + if (!defaultValue.IsEmpty()) { + // Avoid causing reentrant painting and reflowing by telling the editor + // that we don't want it to force immediate view refreshes or force + // immediate reflows during any editor calls. + + rv = newEditor->SetFlags(editorFlags | + nsIPlaintextEditor::eEditorUseAsyncUpdatesMask); + NS_ENSURE_SUCCESS(rv, rv); + + // Now call SetValue() which will make the necessary editor calls to set + // the default value. Make sure to turn off undo before setting the default + // value, and turn it back on afterwards. This will make sure we can't undo + // past the default value. + + rv = newEditor->EnableUndo(PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + SetValue(defaultValue, PR_FALSE); + + rv = newEditor->EnableUndo(PR_TRUE); + NS_ASSERTION(NS_SUCCEEDED(rv),"Transaction Manager must have failed"); + + // Now restore the original editor flags. + rv = newEditor->SetFlags(editorFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr transMgr; + newEditor->GetTransactionManager(getter_AddRefs(transMgr)); + NS_ENSURE_TRUE(transMgr, NS_ERROR_FAILURE); + + transMgr->SetMaxTransactionCount(nsITextControlElement::DEFAULT_UNDO_CAP); + + if (IsPasswordTextControl()) { + // Disable undo for password textfields. Note that we want to do this at + // the very end of InitEditor, so the calls to EnableUndo when setting the + // 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(); + mEditorInitialized = PR_TRUE; + } + + if (mTextListener) + newEditor->AddEditorObserver(mTextListener); + + return rv; +} + +void +nsTextEditorState::DestroyEditor() +{ + // notify the editor that we are going away + if (mEditorInitialized) { + if (mTextListener) + mEditor->RemoveEditorObserver(mTextListener); + + mEditor->PreDestroy(PR_TRUE); + mEditorInitialized = PR_FALSE; + } +} + +void +nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame) +{ + NS_ASSERTION(mBoundFrame, "Can't be unbound without being bound originally"); + NS_ENSURE_TRUE(mBoundFrame, ); + + // If it was, however, it should be unbounded from the same frame. + NS_ASSERTION(aFrame == mBoundFrame, "Unbinding from the wrong frame"); + NS_ENSURE_TRUE(aFrame == mBoundFrame, ); + + // We need to start storing the value outside of the editor if we're not + // going to use it anymore, so retrieve it for now. + nsAutoString value; + GetValue(value, PR_TRUE); + + // Destroy our editor + DestroyEditor(); + + // Clean up the controller + if (!SuppressEventHandlers(mBoundFrame->PresContext())) + { + nsCOMPtr controllers; + nsCOMPtr inputElement = + do_QueryInterface(mTextCtrlElement); + if (inputElement) + inputElement->GetControllers(getter_AddRefs(controllers)); + else + { + nsCOMPtr textAreaElement = + do_QueryInterface(mTextCtrlElement); + if (textAreaElement) { + textAreaElement->GetControllers(getter_AddRefs(controllers)); + } + } + + if (controllers) + { + PRUint32 numControllers; + nsresult rv = controllers->GetControllerCount(&numControllers); + NS_ASSERTION((NS_SUCCEEDED(rv)), "bad result in gfx text control destructor"); + for (PRUint32 i = 0; i < numControllers; i ++) + { + nsCOMPtr controller; + rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); + if (NS_SUCCEEDED(rv) && controller) + { + nsCOMPtr editController = do_QueryInterface(controller); + if (editController) + { + editController->SetCommandContext(nsnull); + } + } + } + } + } + + if (mSelCon) { + if (mTextListener) { + nsRefPtr domSelection; + if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection))) && + domSelection) { + nsCOMPtr selPriv(do_QueryInterface(domSelection)); + + selPriv->RemoveSelectionListener(static_cast + (mTextListener)); + } + } + + mSelCon->SetScrollableFrame(nsnull); + mSelCon = nsnull; + } + + if (mTextListener) + { + mTextListener->SetFrame(nsnull); + + nsCOMPtr systemGroup; + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + content->GetSystemEventGroup(getter_AddRefs(systemGroup)); + nsCOMPtr dom3Targ = do_QueryInterface(mTextCtrlElement); + if (dom3Targ) { + // cast because of ambiguous base + nsIDOMEventListener *listener = static_cast + (mTextListener); + + dom3Targ->RemoveGroupedEventListener(NS_LITERAL_STRING("keydown"), + listener, PR_FALSE, systemGroup); + dom3Targ->RemoveGroupedEventListener(NS_LITERAL_STRING("keypress"), + listener, PR_FALSE, systemGroup); + dom3Targ->RemoveGroupedEventListener(NS_LITERAL_STRING("keyup"), + listener, PR_FALSE, systemGroup); + } + + NS_RELEASE(mTextListener); + mTextListener = nsnull; + } + + mBoundFrame = nsnull; + + // Now that we don't have a frame any more, store the value in the text buffer. + SetValue(value, PR_FALSE); + + if (mRootNode && mMutationObserver) { + mRootNode->RemoveMutationObserver(mMutationObserver); + mMutationObserver = nsnull; + } + + // Unbind the anonymous content from the tree. + // We actually hold a reference to the content nodes so that + // they're not actually destroyed. + nsContentUtils::DestroyAnonymousContent(&mRootNode); + nsContentUtils::DestroyAnonymousContent(&mPlaceholderDiv); +} + +nsresult +nsTextEditorState::CreateRootNode() +{ + NS_ENSURE_TRUE(!mRootNode, NS_ERROR_UNEXPECTED); + NS_ENSURE_ARG_POINTER(mBoundFrame); + + nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell(); + NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); + + nsIDocument *doc = shell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + // Now create a DIV and add it to the anonymous content child list. + nsCOMPtr nodeInfo; + nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nsnull, + kNameSpaceID_XHTML); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = NS_NewHTMLElement(getter_AddRefs(mRootNode), nodeInfo, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + // Set the necessary classes on the text control. We use class values + // instead of a 'style' attribute so that the style comes from a user-agent + // style sheet and is still applied even if author styles are disabled. + nsAutoString classValue; + classValue.AppendLiteral("anonymous-div"); + PRInt32 wrapCols = GetWrapCols(); + if (wrapCols >= 0) { + classValue.AppendLiteral(" wrap"); + } + if (!IsSingleLineTextControl()) { + // We can't just inherit the overflow because setting visible overflow will + // crash when the number of lines exceeds the height of the textarea and + // setting -moz-hidden-unscrollable overflow (NS_STYLE_OVERFLOW_CLIP) + // doesn't paint the caret for some reason. + const nsStyleDisplay* disp = mBoundFrame->GetStyleDisplay(); + if (disp->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE && + disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) { + classValue.AppendLiteral(" inherit-overflow"); + } + + mMutationObserver = new nsAnonDivObserver(this); + NS_ENSURE_TRUE(mMutationObserver, NS_ERROR_OUT_OF_MEMORY); + mRootNode->AddMutationObserver(mMutationObserver); + } + rv = mRootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, + classValue, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mBoundFrame->UpdateValueDisplay(PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +nsresult +nsTextEditorState::CreatePlaceholderNode() +{ + NS_ENSURE_TRUE(!mPlaceholderDiv, NS_ERROR_UNEXPECTED); + NS_ENSURE_ARG_POINTER(mBoundFrame); + + nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell(); + NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); + + nsIDocument *doc = shell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsNodeInfoManager* pNodeInfoManager = doc->NodeInfoManager(); + NS_ENSURE_TRUE(pNodeInfoManager, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv; + nsCOMPtr placeholderText; + + // Create a DIV for the placeholder + // and add it to the anonymous content child list + nsCOMPtr nodeInfo; + nodeInfo = pNodeInfoManager->GetNodeInfo(nsGkAtoms::div, nsnull, + kNameSpaceID_XHTML); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + + rv = NS_NewHTMLElement(getter_AddRefs(mPlaceholderDiv), nodeInfo, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the text node for the placeholder text before doing anything else + rv = NS_NewTextNode(getter_AddRefs(placeholderText), pNodeInfoManager); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mPlaceholderDiv->AppendChildTo(placeholderText, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + // initialize the text + UpdatePlaceholderText(PR_FALSE); + + return NS_OK; +} + +PRBool +nsTextEditorState::GetMaxLength(PRInt32* aMaxLength) +{ + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + NS_ENSURE_TRUE(content, PR_FALSE); + nsGenericHTMLElement* element = nsGenericHTMLElement::FromContent(content); + NS_ENSURE_TRUE(element, PR_FALSE); + + const nsAttrValue* attr = element->GetParsedAttr(nsGkAtoms::maxlength); + if (attr && attr->Type() == nsAttrValue::eInteger) { + *aMaxLength = attr->GetIntegerValue(); + + return PR_TRUE; + } + + return PR_FALSE; +} + +void +nsTextEditorState::GetValue(nsAString& aValue, PRBool aIgnoreWrap) const +{ + if (mEditor && mBoundFrame) { + PRBool canCache = aIgnoreWrap && !IsSingleLineTextControl(); + if (canCache && !mCachedValue.IsEmpty()) { + aValue = mCachedValue; + return; + } + + aValue.Truncate(); // initialize out param + + PRUint32 flags = (nsIDocumentEncoder::OutputLFLineBreak | + nsIDocumentEncoder::OutputPreformatted | + nsIDocumentEncoder::OutputPersistNBSP); + + if (IsPlainTextControl()) + { + flags |= nsIDocumentEncoder::OutputBodyOnly; + } + + if (!aIgnoreWrap) { + nsITextControlElement::nsHTMLTextWrap wrapProp; + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + if (content && + nsITextControlElement::GetWrapPropertyEnum(content, wrapProp) && + wrapProp == nsITextControlElement::eHTMLTextWrap_Hard) { + flags |= nsIDocumentEncoder::OutputWrap; + } + } + + // What follows is a bit of a hack. The problem is that we could be in + // this method because we're being destroyed for whatever reason while + // script is executing. If that happens, editor will run with the + // privileges of the executing script, which means it may not be able to + // access its own DOM nodes! Let's try to deal with that by pushing a null + // JSContext on the JSContext stack to make it clear that we're native + // code. Note that any script that's directly trying to access our value + // has to be going through some scriptable object to do that and that + // already does the relevant security checks. + // XXXbz if we could just get the textContent of our anonymous content (eg + // if plaintext editor didn't create
nodes all over), we wouldn't need + // this. + { /* Scope for context pusher */ + nsCxPusher pusher; + pusher.PushNull(); + + mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags, + aValue); + } + if (canCache) { + mCachedValue = aValue; + } else { + mCachedValue.Truncate(); + } + } else { + if (!mTextCtrlElement->ValueChanged() || !mValue) { + mTextCtrlElement->GetDefaultValueFromContent(aValue); + } else { + aValue = NS_ConvertUTF8toUTF16(*mValue); + } + + if (IsSingleLineTextControl()) { + // If the value is not owned by the frame, then we should handle any + // existing newline characters inside it, instead of relying on the + // editor to do it for us. + nsString value(aValue); + nsTextEditRules::HandleNewLines(value, -1); + aValue.Assign(value); + } + } +} + +void +nsTextEditorState::SetValue(const nsAString& aValue, PRBool aUserInput) +{ + if (mEditor && mBoundFrame) { + // The InsertText call below might flush pending notifications, which + // could lead into a scheduled PrepareEditor to be called. That will + // lead to crashes (or worse) because we'd be initializing the editor + // before InsertText returns. This script blocker makes sure that + // PrepareEditor cannot be called prematurely. + nsAutoScriptBlocker scriptBlocker; + + PRBool fireChangeEvent = mBoundFrame->GetFireChangeEventState(); + if (aUserInput) { + mBoundFrame->SetFireChangeEventState(PR_TRUE); + } + + nsAutoString currentValue; + mBoundFrame->GetText(currentValue); + + nsWeakFrame weakFrame(mBoundFrame); + + // this is necessary to avoid infinite recursion + if (!currentValue.Equals(aValue)) + { + nsTextControlFrame::ValueSetter valueSetter(mBoundFrame, + mBoundFrame->mFocusedValue.Equals(currentValue)); + + // \r is an illegal character in the dom, but people use them, + // so convert windows and mac platform linebreaks to \n: + // Unfortunately aValue is declared const, so we have to copy + // in order to do this substitution. + nsString newValue(aValue); + if (aValue.FindChar(PRUnichar('\r')) != -1) { + nsContentUtils::PlatformToDOMLineBreaks(newValue); + } + + nsCOMPtr domDoc; + mEditor->GetDocument(getter_AddRefs(domDoc)); + if (!domDoc) { + NS_WARNING("Why don't we have a document?"); + return; + } + + // Time to mess with our security context... See comments in GetValue() + // for why this is needed. Note that we have to do this up here, because + // otherwise SelectAll() will fail. + { /* Scope for context pusher */ + nsCxPusher pusher; + pusher.PushNull(); + + nsCOMPtr domSel; + nsCOMPtr selPriv; + mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSel)); + if (domSel) + { + selPriv = do_QueryInterface(domSel); + if (selPriv) + selPriv->StartBatchChanges(); + } + + nsCOMPtr kungFuDeathGrip = mSelCon.get(); + PRUint32 currentLength = currentValue.Length(); + PRUint32 newlength = newValue.Length(); + if (!currentLength || + !StringBeginsWith(newValue, currentValue)) { + // Replace the whole text. + currentLength = 0; + mSelCon->SelectAll(); + } else { + // Collapse selection to the end so that we can append data. + mBoundFrame->SelectAllOrCollapseToEndOfText(PR_FALSE); + } + const nsAString& insertValue = + StringTail(newValue, newlength - currentLength); + nsCOMPtr plaintextEditor = do_QueryInterface(mEditor); + if (!plaintextEditor || !weakFrame.IsAlive()) { + NS_WARNING("Somehow not a plaintext editor?"); + return; + } + + valueSetter.Init(); + + // get the flags, remove readonly and disabled, set the value, + // restore flags + PRUint32 flags, savedFlags; + mEditor->GetFlags(&savedFlags); + flags = savedFlags; + flags &= ~(nsIPlaintextEditor::eEditorDisabledMask); + flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask); + flags |= nsIPlaintextEditor::eEditorUseAsyncUpdatesMask; + flags |= nsIPlaintextEditor::eEditorDontEchoPassword; + mEditor->SetFlags(flags); + + // Also don't enforce max-length here + PRInt32 savedMaxLength; + plaintextEditor->GetMaxTextLength(&savedMaxLength); + plaintextEditor->SetMaxTextLength(-1); + + if (insertValue.IsEmpty()) { + mEditor->DeleteSelection(nsIEditor::eNone); + } else { + plaintextEditor->InsertText(insertValue); + } + if (!weakFrame.IsAlive()) { + NS_ASSERTION(!mBoundFrame, "The frame should have been unbounded"); + SetValue(newValue, PR_FALSE); + valueSetter.Cancel(); + return; + } + + if (!IsSingleLineTextControl()) { + mCachedValue = newValue; + } + + plaintextEditor->SetMaxTextLength(savedMaxLength); + mEditor->SetFlags(savedFlags); + if (selPriv) + selPriv->EndBatchChanges(); + } + } + + // This second check _shouldn't_ be necessary, but let's be safe. + if (!weakFrame.IsAlive()) { + return; + } + nsIScrollableFrame* scrollableFrame = do_QueryFrame(mBoundFrame->GetFirstChild(nsnull)); + if (scrollableFrame) + { + // Scroll the upper left corner of the text control's + // content area back into view. + scrollableFrame->ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT); + } + + if (aUserInput) { + mBoundFrame->SetFireChangeEventState(fireChangeEvent); + } + } else { + if (!mValue) { + mValue = new nsCString; + } + nsString value(aValue); + nsContentUtils::PlatformToDOMLineBreaks(value); + *mValue = ToNewUTF8String(value); + + // Update the frame display if needed + if (mBoundFrame) { + mBoundFrame->UpdateValueDisplay(PR_TRUE); + } + } + + // If we've reached the point where the root node has been created, we + // can assume that it's safe to notify. + ValueWasChanged(!!mRootNode); +} + +void +nsTextEditorState::InitializeKeyboardEventListeners() +{ + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + + //register key listeners + nsCOMPtr systemGroup; + content->GetSystemEventGroup(getter_AddRefs(systemGroup)); + nsCOMPtr dom3Targ = do_QueryInterface(content); + if (dom3Targ) { + // cast because of ambiguous base + nsIDOMEventListener *listener = static_cast + (mTextListener); + + dom3Targ->AddGroupedEventListener(NS_LITERAL_STRING("keydown"), + listener, PR_FALSE, systemGroup); + dom3Targ->AddGroupedEventListener(NS_LITERAL_STRING("keypress"), + listener, PR_FALSE, systemGroup); + dom3Targ->AddGroupedEventListener(NS_LITERAL_STRING("keyup"), + listener, PR_FALSE, systemGroup); + } + + mSelCon->SetScrollableFrame(do_QueryFrame(mBoundFrame->GetFirstChild(nsnull))); +} + +/* static */ void +nsTextEditorState::ShutDown() +{ + NS_IF_RELEASE(sNativeTextAreaBindings); + NS_IF_RELEASE(sNativeInputBindings); +} + +void +nsTextEditorState::ValueWasChanged(PRBool aNotify) +{ + // placeholder management + PRBool showPlaceholder = PR_FALSE; + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + if (!nsContentUtils::IsFocusedContent(content)) { + // If the content is focused, we don't care about the changes because + // the placeholder is going to be hidden/shown on blur. + nsAutoString valueString; + GetValue(valueString, PR_TRUE); + showPlaceholder = valueString.IsEmpty(); + } + SetPlaceholderClass(showPlaceholder, aNotify); +} + +void +nsTextEditorState::UpdatePlaceholderText(PRBool aNotify) +{ + nsAutoString placeholderValue; + + nsCOMPtr content = do_QueryInterface(mTextCtrlElement); + content->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholderValue); + nsContentUtils::RemoveNewlines(placeholderValue); + NS_ASSERTION(mPlaceholderDiv->GetChildAt(0), "placeholder div has no child"); + mPlaceholderDiv->GetChildAt(0)->SetText(placeholderValue, aNotify); + ValueWasChanged(aNotify); +} + +void +nsTextEditorState::SetPlaceholderClass(PRBool aVisible, + PRBool aNotify) +{ + // No need to do anything if we don't have a frame yet + if (!mBoundFrame) + return; + + nsAutoString classValue; + + classValue.Assign(NS_LITERAL_STRING("anonymous-div placeholder")); + + if (!aVisible) + classValue.AppendLiteral(" hidden"); + + nsIContent* placeholderDiv = GetPlaceholderNode(); + NS_ENSURE_TRUE(placeholderDiv, ); + + placeholderDiv->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, + classValue, aNotify); +} + +NS_IMPL_ISUPPORTS1(nsAnonDivObserver, nsIMutationObserver) + +void +nsAnonDivObserver::CharacterDataChanged(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ + mTextEditorState->ClearValueCache(); +} + +void +nsAnonDivObserver::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + PRInt32 /* unused */) +{ + mTextEditorState->ClearValueCache(); +} + +void +nsAnonDivObserver::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + PRInt32 /* unused */) +{ + mTextEditorState->ClearValueCache(); +} + +void +nsAnonDivObserver::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + PRInt32 aIndexInContainer) +{ + mTextEditorState->ClearValueCache(); +} diff --git a/content/html/content/src/nsTextEditorState.h b/content/html/content/src/nsTextEditorState.h new file mode 100644 index 00000000000..bd8567296a7 --- /dev/null +++ b/content/html/content/src/nsTextEditorState.h @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 et tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla.org client code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ehsan Akhgari (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsTextEditorState_h__ +#define nsTextEditorState_h__ + +#include "nsAutoPtr.h" +#include "nsITextControlElement.h" +#include "nsCycleCollectionParticipant.h" + +class nsTextInputListener; +class nsTextControlFrame; +class nsTextInputSelectionImpl; +class nsAnonDivObserver; +class nsISelectionController; +class nsFrameSelection; +class nsIEditor; +class nsITextControlElement; + +/** + * nsTextEditorState is a class which is responsible for managing the state of + * plaintext controls. This currently includes the following HTML elements: + * + * + * + + + + +
+
+
+ + diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 5dab39e425c..5642168fad0 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -4315,8 +4315,7 @@ nsFrame::GetSelectionController(nsPresContext *aPresContext, nsISelectionControl while (frame && (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION)) { nsITextControlFrame *tcf = do_QueryFrame(frame); if (tcf) { - NS_IF_ADDREF(*aSelCon = tcf->GetOwnedSelectionController()); - return NS_OK; + return tcf->GetOwnedSelectionController(aSelCon); } frame = frame->GetParent(); } diff --git a/layout/generic/test/test_bug527306.html b/layout/generic/test/test_bug527306.html index d2658adbc6c..ba25f7fee62 100644 --- a/layout/generic/test/test_bug527306.html +++ b/layout/generic/test/test_bug527306.html @@ -10,18 +10,35 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=527306 - +

Mozilla Bug 527306 -

+

+

+

+

 
+
+
diff --git a/layout/reftests/editor/dynamic-type-2.html b/layout/reftests/editor/dynamic-type-2.html
new file mode 100644
index 00000000000..4d99ac06e24
--- /dev/null
+++ b/layout/reftests/editor/dynamic-type-2.html
@@ -0,0 +1,11 @@
+
+
+
+    
+    
+
+
diff --git a/layout/reftests/editor/dynamic-type-3.html b/layout/reftests/editor/dynamic-type-3.html
new file mode 100644
index 00000000000..7cf5be6abb5
--- /dev/null
+++ b/layout/reftests/editor/dynamic-type-3.html
@@ -0,0 +1,11 @@
+
+
+
+    
+    
+
+
diff --git a/layout/reftests/editor/dynamic-type-4.html b/layout/reftests/editor/dynamic-type-4.html
new file mode 100644
index 00000000000..7cf5be6abb5
--- /dev/null
+++ b/layout/reftests/editor/dynamic-type-4.html
@@ -0,0 +1,11 @@
+
+
+
+    
+    
+
+
diff --git a/layout/reftests/editor/reftest.list b/layout/reftests/editor/reftest.list
index d7968af7c5a..16b741b7f9f 100644
--- a/layout/reftests/editor/reftest.list
+++ b/layout/reftests/editor/reftest.list
@@ -5,6 +5,10 @@ include xul/reftest.list
 == newline-2.html newline-ref.html
 == newline-3.html newline-ref.html
 == dynamic-1.html dynamic-ref.html
+== dynamic-type-1.html dynamic-ref.html
+== dynamic-type-2.html dynamic-ref.html
+== dynamic-type-3.html dynamic-ref.html
+== dynamic-type-4.html dynamic-ref.html
 == passwd-1.html passwd-ref.html
 != passwd-2.html passwd-ref.html
 == passwd-3.html passwd-ref.html
diff --git a/layout/reftests/forms/placeholder/placeholder-18.html b/layout/reftests/forms/placeholder/placeholder-18.html
new file mode 100644
index 00000000000..159d5d7e27b
--- /dev/null
+++ b/layout/reftests/forms/placeholder/placeholder-18.html
@@ -0,0 +1,22 @@
+
+
+  
+  
+  
+    
+  
+
diff --git a/layout/reftests/forms/placeholder/reftest.list b/layout/reftests/forms/placeholder/reftest.list
index 5d45a28b540..f090c21657d 100644
--- a/layout/reftests/forms/placeholder/reftest.list
+++ b/layout/reftests/forms/placeholder/reftest.list
@@ -18,3 +18,4 @@
 == placeholder-15.html placeholder-focus-ref.html
 == placeholder-16.html placeholder-focus-ref.html
 == placeholder-17.html placeholder-focus-ref.html
+== placeholder-18.html placeholder-overridden-ref.html
diff --git a/layout/reftests/forms/reftest.list b/layout/reftests/forms/reftest.list
index 3c397733201..2d358705542 100644
--- a/layout/reftests/forms/reftest.list
+++ b/layout/reftests/forms/reftest.list
@@ -18,6 +18,7 @@ HTTP(..) == text-control-baseline-1.html text-control-baseline-1-ref.html
 != textarea-in-ltr-doc-scrollbar.html textarea-in-rtl-doc-scrollbar.html
 != textarea-ltr.html textarea-no-resize.html
 random-if(MOZ_WIDGET_TOOLKIT=="gtk2") != textarea-rtl.html textarea-no-resize.html # bug 558201
+== textarea-setvalue-framereconstruction-1.html textarea-setvalue-framereconstruction-ref.html
 
 == radio-label-dynamic.html radio-label-dynamic-ref.html
 == out-of-bounds-selectedindex.html out-of-bounds-selectedindex-ref.html # test for bug 471741
diff --git a/layout/reftests/forms/textarea-setvalue-framereconstruction-1.html b/layout/reftests/forms/textarea-setvalue-framereconstruction-1.html
new file mode 100644
index 00000000000..0be72b771dd
--- /dev/null
+++ b/layout/reftests/forms/textarea-setvalue-framereconstruction-1.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+  
+    
+      
+    
+  
+
+
+
+
+
+
+
+
+
+ +
+ + + + + + diff --git a/layout/reftests/forms/textarea-setvalue-framereconstruction-ref.html b/layout/reftests/forms/textarea-setvalue-framereconstruction-ref.html new file mode 100644 index 00000000000..95fcd5c2321 --- /dev/null +++ b/layout/reftests/forms/textarea-setvalue-framereconstruction-ref.html @@ -0,0 +1,17 @@ + + + + + + +
+ +
+ + + + + +