diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/html/nsTextEditorState.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/html/nsTextEditorState.cpp')
-rw-r--r-- | dom/html/nsTextEditorState.cpp | 2354 |
1 files changed, 2354 insertions, 0 deletions
diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp new file mode 100644 index 000000000..d70199362 --- /dev/null +++ b/dom/html/nsTextEditorState.cpp @@ -0,0 +1,2354 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTextEditorState.h" + +#include "nsCOMPtr.h" +#include "nsIPresShell.h" +#include "nsView.h" +#include "nsCaret.h" +#include "nsEditorCID.h" +#include "nsLayoutCID.h" +#include "nsITextControlFrame.h" +#include "nsIPlaintextEditor.h" +#include "nsIDOMCharacterData.h" +#include "nsIDOMDocument.h" +#include "nsContentCreatorFunctions.h" +#include "nsTextControlFrame.h" +#include "nsIControllers.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsITransactionManager.h" +#include "nsIControllerContext.h" +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "nsGenericHTMLElement.h" +#include "nsIDOMEventListener.h" +#include "nsIEditorIMESupport.h" +#include "nsIEditorObserver.h" +#include "nsIWidget.h" +#include "nsIDocumentEncoder.h" +#include "nsISelectionPrivate.h" +#include "nsPIDOMWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsIEditor.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/TextEditRules.h" +#include "mozilla/EventListenerManager.h" +#include "nsContentUtils.h" +#include "mozilla/Preferences.h" +#include "nsTextNode.h" +#include "nsIController.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "nsNumberControlFrame.h" +#include "nsFrameSelection.h" +#include "mozilla/Telemetry.h" +#include "mozilla/layers/ScrollInputMethods.h" + +using namespace mozilla; +using namespace mozilla::dom; +using mozilla::layers::ScrollInputMethod; + +static NS_DEFINE_CID(kTextEditorCID, NS_TEXTEDITOR_CID); + +class MOZ_STACK_CLASS ValueSetter +{ +public: + explicit ValueSetter(nsIEditor* aEditor) + : mEditor(aEditor) + { + MOZ_ASSERT(aEditor); + + // To protect against a reentrant call to SetValue, we check whether + // another SetValue is already happening for this editor. If it is, + // we must wait until we unwind to re-enable oninput events. + mEditor->GetSuppressDispatchingInputEvent(&mOuterTransaction); + } + ~ValueSetter() + { + mEditor->SetSuppressDispatchingInputEvent(mOuterTransaction); + } + void Init() + { + mEditor->SetSuppressDispatchingInputEvent(true); + } + +private: + nsCOMPtr<nsIEditor> mEditor; + bool mOuterTransaction; +}; + +class RestoreSelectionState : public Runnable { +public: + RestoreSelectionState(nsTextEditorState *aState, nsTextControlFrame *aFrame) + : mFrame(aFrame), + mTextEditorState(aState) + { + } + + NS_IMETHOD Run() override { + if (!mTextEditorState) { + return NS_OK; + } + + AutoHideSelectionChanges hideSelectionChanges + (mFrame->GetConstFrameSelection()); + + if (mFrame) { + // SetSelectionRange leads to Selection::AddRange which flushes Layout - + // need to block script to avoid nested PrepareEditor calls (bug 642800). + nsAutoScriptBlocker scriptBlocker; + nsTextEditorState::SelectionProperties& properties = + mTextEditorState->GetSelectionProperties(); + if (properties.IsDirty()) { + mFrame->SetSelectionRange(properties.GetStart(), + properties.GetEnd(), + properties.GetDirection()); + } + if (!mTextEditorState->mSelectionRestoreEagerInit) { + mTextEditorState->HideSelectionIfBlurred(); + } + mTextEditorState->mSelectionRestoreEagerInit = false; + } + + if (mTextEditorState) { + mTextEditorState->FinishedRestoringSelection(); + } + return NS_OK; + } + + // Let the text editor tell us we're no longer relevant - avoids use of nsWeakFrame + void Revoke() { + mFrame = nullptr; + mTextEditorState = nullptr; + } + +private: + nsTextControlFrame* mFrame; + nsTextEditorState* mTextEditorState; +}; + +/*static*/ +bool +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->IsHTMLElement()) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::HARD, &nsGkAtoms::OFF, nullptr}; + + switch (aContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::wrap, + strings, eIgnoreCase)) { + case 0: aWrapProp = eHTMLTextWrap_Hard; break; + case 1: aWrapProp = eHTMLTextWrap_Off; break; + } + + return true; + } + + return false; +} + +/*static*/ +already_AddRefed<nsITextControlElement> +nsITextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) +{ + if (!aHost) { + return nullptr; + } + + nsCOMPtr<nsITextControlElement> parent = + do_QueryInterface(aHost->GetParent()); + + return parent.forget(); +} + +static bool +SuppressEventHandlers(nsPresContext* aPresContext) +{ + bool suppressHandlers = 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 final : public nsStubMutationObserver +{ +public: + explicit 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: + ~nsAnonDivObserver() {} + nsTextEditorState* mTextEditorState; +}; + +class nsTextInputSelectionImpl final : public nsSupportsWeakReference + , public nsISelectionController +{ + ~nsTextInputSelectionImpl(){} + +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController) + + nsTextInputSelectionImpl(nsFrameSelection *aSel, nsIPresShell *aShell, nsIContent *aLimiter); + + void SetScrollableFrame(nsIScrollableFrame *aScrollableFrame); + nsFrameSelection* GetConstFrameSelection() + { return mFrameSelection; } + + //NSISELECTIONCONTROLLER INTERFACES + NS_IMETHOD SetDisplaySelection(int16_t toggle) override; + NS_IMETHOD GetDisplaySelection(int16_t* _retval) override; + NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override; + NS_IMETHOD GetSelectionFlags(int16_t *aOutEnable) override; + NS_IMETHOD GetSelection(RawSelectionType aRawSelectionType, + nsISelection** aSelection) override; + NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType, + int16_t aRegion, int16_t aFlags) override; + NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override; + nsresult RepaintSelection(nsPresContext* aPresContext, + SelectionType aSelectionType); + NS_IMETHOD SetCaretEnabled(bool enabled) override; + NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override; + NS_IMETHOD GetCaretEnabled(bool* _retval) override; + NS_IMETHOD GetCaretVisible(bool* _retval) override; + NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override; + NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) override; + NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override; + NS_IMETHOD CharacterExtendForDelete() override; + NS_IMETHOD CharacterExtendForBackspace() override; + NS_IMETHOD WordMove(bool aForward, bool aExtend) override; + NS_IMETHOD WordExtendForDelete(bool aForward) override; + NS_IMETHOD LineMove(bool aForward, bool aExtend) override; + NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override; + NS_IMETHOD PageMove(bool aForward, bool aExtend) override; + NS_IMETHOD CompleteScroll(bool aForward) override; + NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override; + NS_IMETHOD ScrollPage(bool aForward) override; + NS_IMETHOD ScrollLine(bool aForward) override; + NS_IMETHOD ScrollCharacter(bool aRight) override; + NS_IMETHOD SelectAll(void) override; + NS_IMETHOD CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool* _retval) override; + virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset, bool* aRetval) override; + +private: + RefPtr<nsFrameSelection> mFrameSelection; + nsCOMPtr<nsIContent> mLimiter; + nsIScrollableFrame *mScrollFrame; + nsWeakPtr mPresShellWeak; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextInputSelectionImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextInputSelectionImpl) +NS_INTERFACE_TABLE_HEAD(nsTextInputSelectionImpl) + NS_INTERFACE_TABLE(nsTextInputSelectionImpl, + nsISelectionController, + nsISelectionDisplay, + nsISupportsWeakReference) + NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsTextInputSelectionImpl) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(nsTextInputSelectionImpl, mFrameSelection, mLimiter) + + +// BEGIN nsTextInputSelectionImpl + +nsTextInputSelectionImpl::nsTextInputSelectionImpl(nsFrameSelection *aSel, + nsIPresShell *aShell, + nsIContent *aLimiter) + : mScrollFrame(nullptr) +{ + 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 = nullptr; + } +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetDisplaySelection(int16_t aToggle) +{ + if (!mFrameSelection) + return NS_ERROR_NULL_POINTER; + + mFrameSelection->SetDisplaySelection(aToggle); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetDisplaySelection(int16_t *aToggle) +{ + if (!mFrameSelection) + return NS_ERROR_NULL_POINTER; + + *aToggle = mFrameSelection->GetDisplaySelection(); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetSelectionFlags(int16_t aToggle) +{ + return NS_OK;//stub this out. not used in input +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetSelectionFlags(int16_t *aOutEnable) +{ + *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT; + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetSelection(RawSelectionType aRawSelectionType, + nsISelection** aSelection) +{ + if (!mFrameSelection) + return NS_ERROR_NULL_POINTER; + + *aSelection = + mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType)); + + // GetSelection() fails only when aRawSelectionType is invalid value. + if (!(*aSelection)) { + return NS_ERROR_INVALID_ARG; + } + + NS_ADDREF(*aSelection); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollSelectionIntoView( + RawSelectionType aRawSelectionType, + int16_t aRegion, + int16_t aFlags) +{ + if (!mFrameSelection) + return NS_ERROR_FAILURE; + + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->ScrollSelectionIntoView( + ToSelectionType(aRawSelectionType), + aRegion, aFlags); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::RepaintSelection(RawSelectionType aRawSelectionType) +{ + if (!mFrameSelection) + return NS_ERROR_FAILURE; + + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType)); +} + +nsresult +nsTextInputSelectionImpl::RepaintSelection(nsPresContext* aPresContext, + SelectionType aSelectionType) +{ + if (!mFrameSelection) + return NS_ERROR_FAILURE; + + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->RepaintSelection(aSelectionType); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetCaretEnabled(bool enabled) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsIPresShell> 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<nsISelectionController> selCon = do_QueryInterface(shell); + if (!selCon) return NS_ERROR_NO_INTERFACE; + selCon->SetCaretEnabled(enabled); + + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + RefPtr<nsCaret> caret = shell->GetCaret(); + if (caret) { + nsISelection* domSel = + mFrameSelection->GetSelection(SelectionType::eNormal); + if (domSel) + caret->SetCaretReadOnly(aReadOnly); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetCaretEnabled(bool *_retval) +{ + return GetCaretVisible(_retval); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::GetCaretVisible(bool *_retval) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + RefPtr<nsCaret> caret = shell->GetCaret(); + if (caret) { + *_retval = caret->IsVisible(); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + RefPtr<nsCaret> caret = shell->GetCaret(); + if (caret) { + nsISelection* domSel = + mFrameSelection->GetSelection(SelectionType::eNormal); + if (domSel) + caret->SetVisibilityDuringSelection(aVisibility); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::PhysicalMove(int16_t aDirection, int16_t aAmount, + bool aExtend) +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->PhysicalMove(aDirection, aAmount, aExtend); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CharacterMove(bool aForward, bool aExtend) +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->CharacterMove(aForward, aExtend); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CharacterExtendForDelete() +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->CharacterExtendForDelete(); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CharacterExtendForBackspace() +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->CharacterExtendForBackspace(); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::WordMove(bool aForward, bool aExtend) +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->WordMove(aForward, aExtend); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::WordExtendForDelete(bool aForward) +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->WordExtendForDelete(aForward); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::LineMove(bool aForward, bool aExtend) +{ + if (mFrameSelection) + { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + nsresult result = frameSelection->LineMove(aForward, aExtend); + if (NS_FAILED(result)) + result = CompleteMove(aForward,aExtend); + return result; + } + return NS_ERROR_NULL_POINTER; +} + + +NS_IMETHODIMP +nsTextInputSelectionImpl::IntraLineMove(bool aForward, bool aExtend) +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->IntraLineMove(aForward, aExtend); + } + return NS_ERROR_NULL_POINTER; +} + + +NS_IMETHODIMP +nsTextInputSelectionImpl::PageMove(bool aForward, bool 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) + { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + frameSelection->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, + nsISelectionController::SCROLL_SYNCHRONOUS | + nsISelectionController::SCROLL_FOR_CARET_MOVE); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CompleteScroll(bool aForward) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadCompleteScroll); + + mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), + nsIScrollableFrame::WHOLE, + nsIScrollableFrame::INSTANT); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CompleteMove(bool aForward, bool aExtend) +{ + NS_ENSURE_STATE(mFrameSelection); + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + + // grab the parent / root DIV for this text widget + nsIContent* parentDIV = frameSelection->GetLimiter(); + if (!parentDIV) + return NS_ERROR_UNEXPECTED; + + // make the caret be either at the very beginning (0) or the very end + int32_t offset = 0; + CaretAssociationHint hint = CARET_ASSOCIATE_BEFORE; + 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->GetLastChild(); + + if (child->IsHTMLElement(nsGkAtoms::br)) + { + --offset; + hint = CARET_ASSOCIATE_AFTER; // for Bug 106855 + } + } + } + + frameSelection->HandleClick(parentDIV, offset, offset, aExtend, + false, hint); + + // if we got this far, attempt to scroll no matter what the above result is + return CompleteScroll(aForward); +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollPage(bool aForward) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadScrollPage); + + mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), + nsIScrollableFrame::PAGES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollLine(bool aForward) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadScrollLine); + + mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), + nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::ScrollCharacter(bool aRight) +{ + if (!mScrollFrame) + return NS_ERROR_NOT_INITIALIZED; + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadScrollCharacter); + + mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0), + nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::SelectAll() +{ + if (mFrameSelection) { + RefPtr<nsFrameSelection> frameSelection = mFrameSelection; + return frameSelection->SelectAll(); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsTextInputSelectionImpl::CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool *_retval) +{ + if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; + nsresult result; + nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak, &result); + if (shell) + { + return shell->CheckVisibility(node,startOffset,EndOffset, _retval); + } + return NS_ERROR_FAILURE; + +} + +nsresult +nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode, + int16_t aStartOffset, + int16_t aEndOffset, + bool* aRetval) +{ + if (!mPresShellWeak) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak); + NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); + + return shell->CheckVisibilityContent(aNode, aStartOffset, aEndOffset, aRetval); +} + +class nsTextInputListener : public nsISelectionListener, + public nsIDOMEventListener, + public nsIEditorObserver, + public nsSupportsWeakReference +{ +public: + /** the default constructor + */ + explicit nsTextInputListener(nsITextControlElement* aTxtCtrlElement); + + /** 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;} + + void SettingValue(bool aValue) { mSettingValue = aValue; } + void SetValueChanged(bool aSetValueChanged) { mSetValueChanged = aSetValueChanged; } + + NS_DECL_ISUPPORTS + + NS_DECL_NSISELECTIONLISTENER + + NS_DECL_NSIDOMEVENTLISTENER + + NS_DECL_NSIEDITOROBSERVER + +protected: + /** the default destructor. virtual due to the possibility of derivation. + */ + virtual ~nsTextInputListener(); + + nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate, + nsISelection* sel = nullptr, + int16_t reason = 0); + +protected: + + nsIFrame* mFrame; + + nsITextControlElement* const mTxtCtrlElement; + + bool 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) + */ + bool 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) + */ + bool mHadRedoItems; + /** + * Whether we're in the process of a SetValue call, and should therefore + * refrain from calling OnValueChanged. + */ + bool mSettingValue; + /** + * Whether we are in the process of a SetValue call that doesn't want + * |SetValueChanged| to be called. + */ + bool mSetValueChanged; +}; + + +/* + * nsTextInputListener implementation + */ + +nsTextInputListener::nsTextInputListener(nsITextControlElement* aTxtCtrlElement) +: mFrame(nullptr) +, mTxtCtrlElement(aTxtCtrlElement) +, mSelectionWasCollapsed(true) +, mHadUndoItems(false) +, mHadRedoItems(false) +, mSettingValue(false) +, mSetValueChanged(true) +{ +} + +nsTextInputListener::~nsTextInputListener() +{ +} + +NS_IMPL_ISUPPORTS(nsTextInputListener, + nsISelectionListener, + nsIEditorObserver, + nsISupportsWeakReference, + nsIDOMEventListener) + +// BEGIN nsIDOMSelectionListener + +NS_IMETHODIMP +nsTextInputListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel, int16_t aReason) +{ + bool collapsed; + nsWeakFrame weakFrame = mFrame; + + if (!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<nsIDocument> doc = content->GetComposedDoc(); + if (doc) + { + nsCOMPtr<nsIPresShell> presShell = doc->GetShell(); + if (presShell) + { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent event(true, eFormSelect); + + 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 (!weakFrame.IsAlive() || !nsContentUtils::IsFocusedContent(mFrame->GetContent())) + return NS_OK; + + return UpdateTextInputCommands(NS_LITERAL_STRING("select"), aSel, aReason); +} + +// END nsIDOMSelectionListener + +static void +DoCommandCallback(Command aCommand, void* aData) +{ + nsTextControlFrame *frame = static_cast<nsTextControlFrame*>(aData); + nsIContent *content = frame->GetContent(); + + nsCOMPtr<nsIControllers> controllers; + nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(content); + if (input) { + input->GetControllers(getter_AddRefs(controllers)); + } else { + nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea = + do_QueryInterface(content); + + if (textArea) { + textArea->GetControllers(getter_AddRefs(controllers)); + } + } + + if (!controllers) { + NS_WARNING("Could not get controllers"); + return; + } + + const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand); + + nsCOMPtr<nsIController> controller; + controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller)); + if (!controller) { + return; + } + + bool commandEnabled; + nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled); + NS_ENSURE_SUCCESS_VOID(rv); + if (commandEnabled) { + controller->DoCommand(commandStr); + } +} + +NS_IMETHODIMP +nsTextInputListener::HandleEvent(nsIDOMEvent* aEvent) +{ + bool defaultPrevented = false; + nsresult rv = aEvent->GetDefaultPrevented(&defaultPrevented); + NS_ENSURE_SUCCESS(rv, rv); + if (defaultPrevented) { + return NS_OK; + } + + bool isTrusted = false; + rv = aEvent->GetIsTrusted(&isTrusted); + NS_ENSURE_SUCCESS(rv, rv); + if (!isTrusted) { + return NS_OK; + } + + WidgetKeyboardEvent* keyEvent = + aEvent->WidgetEventPtr()->AsKeyboardEvent(); + if (!keyEvent) { + return NS_ERROR_UNEXPECTED; + } + + if (keyEvent->mMessage != eKeyPress) { + return NS_OK; + } + + nsIWidget::NativeKeyBindingsType nativeKeyBindingsType = + mTxtCtrlElement->IsTextArea() ? + nsIWidget::NativeKeyBindingsForMultiLineEditor : + nsIWidget::NativeKeyBindingsForSingleLineEditor; + nsIWidget* widget = keyEvent->mWidget; + // If the event is created by chrome script, the widget is nullptr. + if (!widget) { + widget = mFrame->GetNearestWidget(); + NS_ENSURE_TRUE(widget, NS_OK); + } + + if (widget->ExecuteNativeKeyBinding(nativeKeyBindingsType, + *keyEvent, DoCommandCallback, mFrame)) { + aEvent->PreventDefault(); + } + return NS_OK; +} + +// BEGIN nsIEditorObserver + +NS_IMETHODIMP +nsTextInputListener::EditAction() +{ + if (!mFrame) { + // We've been disconnected from the nsTextEditorState object, nothing to do + // here. + return NS_OK; + } + + nsWeakFrame weakFrame = mFrame; + + nsITextControlFrame* frameBase = do_QueryFrame(mFrame); + nsTextControlFrame* frame = static_cast<nsTextControlFrame*> (frameBase); + NS_ASSERTION(frame, "Where is our frame?"); + // + // Update the undo / redo menus + // + nsCOMPtr<nsIEditor> editor; + frame->GetEditor(getter_AddRefs(editor)); + + // Get the number of undo / redo items + int32_t numUndoItems = 0; + int32_t numRedoItems = 0; + editor->GetNumberOfUndoItems(&numUndoItems); + editor->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; + } + + if (!weakFrame.IsAlive()) { + return NS_OK; + } + + // 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) + if (mSetValueChanged) { + frame->SetValueChanged(true); + } + + if (!mSettingValue) { + mTxtCtrlElement->OnValueChanged(/* aNotify = */ true, + /* aWasInteractiveUserChange = */ true); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputListener::BeforeEditAction() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsTextInputListener::CancelEditAction() +{ + return NS_OK; +} + +// END nsIEditorObserver + + +nsresult +nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate, + nsISelection* sel, + int16_t reason) +{ + nsIContent* content = mFrame->GetContent(); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocument> doc = content->GetComposedDoc(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsPIDOMWindowOuter *domWindow = doc->GetWindow(); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + + return domWindow->UpdateCommands(commandsToUpdate, sel, reason); +} + +// END nsTextInputListener + +// nsTextEditorState + +nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement) + : mTextCtrlElement(aOwningElement) + , mBoundFrame(nullptr) + , mEverInited(false) + , mEditorInitialized(false) + , mInitializing(false) + , mValueTransferInProgress(false) + , mSelectionCached(true) + , mSelectionRestoreEagerInit(false) + , mPlaceholderVisibility(false) + , mIsCommittingComposition(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 = nullptr; + } else { + // If we have a bound frame around, UnbindFromFrame will call DestroyEditor + // for us. + DestroyEditor(); + } + mTextListener = nullptr; +} + +void nsTextEditorState::Unlink() +{ + nsTextEditorState* tmp = this; + tmp->Clear(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditor) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootNode) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaceholderDiv) +} + +void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback& cb) +{ + nsTextEditorState* tmp = this; + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditor) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderDiv) +} + +nsFrameSelection* +nsTextEditorState::GetConstFrameSelection() { + if (mSelCon) + return mSelCon->GetConstFrameSelection(); + return nullptr; +} + +nsIEditor* +nsTextEditorState::GetEditor() +{ + if (!mEditor) { + nsresult rv = PrepareEditor(); + NS_ENSURE_SUCCESS(rv, nullptr); + } + return mEditor; +} + +nsISelectionController* +nsTextEditorState::GetSelectionController() const +{ + return mSelCon; +} + +// Helper class, used below in BindToFrame(). +class PrepareEditorEvent : public Runnable { +public: + PrepareEditorEvent(nsTextEditorState &aState, + nsIContent *aOwnerContent, + const nsAString &aCurrentValue) + : mState(&aState) + , mOwnerContent(aOwnerContent) + , mCurrentValue(aCurrentValue) + { + aState.mValueTransferInProgress = true; + } + + NS_IMETHOD Run() override { + NS_ENSURE_TRUE(mState, NS_ERROR_NULL_POINTER); + + // Transfer the saved value to the editor if we have one + const nsAString *value = nullptr; + if (!mCurrentValue.IsEmpty()) { + value = &mCurrentValue; + } + + nsAutoScriptBlocker scriptBlocker; + + mState->PrepareEditor(value); + + mState->mValueTransferInProgress = false; + + return NS_OK; + } + +private: + WeakPtr<nsTextEditorState> mState; + nsCOMPtr<nsIContent> mOwnerContent; // strong reference + nsAutoString mCurrentValue; +}; + +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, true); + } + + mBoundFrame = aFrame; + + nsresult rv = CreateRootNode(); + NS_ENSURE_SUCCESS(rv, rv); + + nsIContent *rootNode = GetRootNode(); + rv = InitializeRootNode(); + NS_ENSURE_SUCCESS(rv, rv); + + nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell(); + NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); + + // Create selection + RefPtr<nsFrameSelection> frameSel = new nsFrameSelection(); + + // Create a SelectionController + mSelCon = new nsTextInputSelectionImpl(frameSel, shell, rootNode); + MOZ_ASSERT(!mTextListener, "Should not overwrite the object"); + mTextListener = new nsTextInputListener(mTextCtrlElement); + + mTextListener->SetFrame(mBoundFrame); + mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); + + // Get the caret and make it a selection listener. + RefPtr<nsISelection> domSelection; + if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection))) && + domSelection) { + nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection)); + RefPtr<nsCaret> caret = shell->GetCaret(); + nsCOMPtr<nsISelectionListener> listener; + if (caret) { + listener = do_QueryInterface(caret); + if (listener) { + selPriv->AddSelectionListener(listener); + } + } + + selPriv->AddSelectionListener(static_cast<nsISelectionListener*> + (mTextListener)); + } + + // If an editor exists from before, prepare it for usage + if (mEditor) { + nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + // Set the correct direction on the newly created root node + uint32_t flags; + nsresult rv = mEditor->GetFlags(&flags); + NS_ENSURE_SUCCESS(rv, rv); + if (flags & nsIPlaintextEditor::eEditorRightToLeft) { + rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), false); + } else if (flags & nsIPlaintextEditor::eEditorLeftToRight) { + rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), false); + } else { + // otherwise, inherit the content node's direction + } + + nsContentUtils::AddScriptRunner( + new PrepareEditorEvent(*this, content, currentValue)); + } + + return NS_OK; +} + +struct PreDestroyer +{ + void Init(nsIEditor* aEditor) + { + mNewEditor = aEditor; + } + ~PreDestroyer() + { + if (mNewEditor) { + mNewEditor->PreDestroy(true); + } + } + void Swap(nsCOMPtr<nsIEditor>& aEditor) + { + return mNewEditor.swap(aEditor); + } +private: + nsCOMPtr<nsIEditor> mNewEditor; +}; + +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; + } + + AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection()); + + // Don't attempt to initialize recursively! + InitializationGuard guard(*this); + if (guard.IsInitializingRecursively()) { + return NS_ERROR_NOT_INITIALIZED; + } + + // 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 + uint32_t editorFlags = 0; + if (IsPlainTextControl()) + editorFlags |= nsIPlaintextEditor::eEditorPlaintextMask; + if (IsSingleLineTextControl()) + editorFlags |= nsIPlaintextEditor::eEditorSingleLineMask; + if (IsPasswordTextControl()) + editorFlags |= nsIPlaintextEditor::eEditorPasswordMask; + + // All nsTextControlFrames are widgets + editorFlags |= nsIPlaintextEditor::eEditorWidgetMask; + + // Spell check is diabled at creation time. It is enabled once + // the editor comes into focus. + editorFlags |= nsIPlaintextEditor::eEditorSkipSpellCheck; + + bool shouldInitializeEditor = false; + nsCOMPtr<nsIEditor> newEditor; // the editor that we might create + nsresult rv = NS_OK; + PreDestroyer preDestroyer; + if (!mEditor) { + shouldInitializeEditor = true; + + // Create an editor + newEditor = do_CreateInstance(kTextEditorCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + preDestroyer.Init(newEditor); + + // Make sure we clear out the non-breaking space before we initialize the editor + rv = mBoundFrame->UpdateValueDisplay(true, true); + NS_ENSURE_SUCCESS(rv, rv); + } else { + if (aValue || !mEditorInitialized) { + // Set the correct value in the root node + rv = mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue); + NS_ENSURE_SUCCESS(rv, rv); + } + + newEditor = mEditor; // just pretend that we have a new editor! + + // Don't lose application flags in the process. + uint32_t originalFlags = 0; + newEditor->GetFlags(&originalFlags); + if (originalFlags & nsIPlaintextEditor::eEditorMailMask) { + editorFlags |= nsIPlaintextEditor::eEditorMailMask; + } + } + + // 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, true); + } + + if (!mEditorInitialized) { + // Now initialize the editor. + // + // NOTE: Conversion of '\n' to <BR> happens inside the + // editor's Init() call. + + // Get the DOM document + nsCOMPtr<nsIDOMDocument> 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 explictly 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. + AutoNoJSAPI nojsapi; + + rv = newEditor->Init(domdoc, GetRootNode(), mSelCon, editorFlags, + defaultValue); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Initialize the controller for the editor + + if (!SuppressEventHandlers(presContext)) { + nsCOMPtr<nsIControllers> controllers; + nsCOMPtr<nsIDOMHTMLInputElement> inputElement = + do_QueryInterface(mTextCtrlElement); + if (inputElement) { + rv = inputElement->GetControllers(getter_AddRefs(controllers)); + } else { + nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement = + do_QueryInterface(mTextCtrlElement); + + if (!textAreaElement) + return NS_ERROR_FAILURE; + + rv = textAreaElement->GetControllers(getter_AddRefs(controllers)); + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (controllers) { + uint32_t numControllers; + bool found = false; + rv = controllers->GetControllerCount(&numControllers); + for (uint32_t i = 0; i < numControllers; i ++) { + nsCOMPtr<nsIController> controller; + rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); + if (NS_SUCCEEDED(rv) && controller) { + nsCOMPtr<nsIControllerContext> editController = + do_QueryInterface(controller); + if (editController) { + editController->SetCommandContext(newEditor); + found = true; + } + } + } + if (!found) + rv = NS_ERROR_FAILURE; + } + } + + if (shouldInitializeEditor) { + // Initialize the plaintext editor + nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryInterface(newEditor)); + if (textEditor) { + // Set up wrapping + textEditor->SetWrapColumn(GetWrapCols()); + + // Set max text field length + int32_t maxLength; + if (GetMaxLength(&maxLength)) { + textEditor->SetMaxTextLength(maxLength); + } + } + } + + nsCOMPtr<nsIContent> 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. + // TODO: call IsDisabled() here! + 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); + } + + if (shouldInitializeEditor) { + // Hold on to the newly created editor + preDestroyer.Swap(mEditor); + } + + // 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()) { + rv = newEditor->SetFlags(editorFlags); + 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(false); + NS_ENSURE_SUCCESS(rv, rv); + + bool success = SetValue(defaultValue, eSetValue_Internal); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + + rv = newEditor->EnableUndo(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<nsITransactionManager> 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(false); + } + + if (!mEditorInitialized) { + newEditor->PostCreate(); + mEverInited = true; + mEditorInitialized = true; + } + + if (mTextListener) + newEditor->AddEditorObserver(mTextListener); + + // Restore our selection after being bound to a new frame + HTMLInputElement* number = GetParentNumberControl(mBoundFrame); + if (number ? number->IsSelectionCached() : mSelectionCached) { + if (mRestoringSelection) // paranoia + mRestoringSelection->Revoke(); + mRestoringSelection = new RestoreSelectionState(this, mBoundFrame); + if (mRestoringSelection) { + nsContentUtils::AddScriptRunner(mRestoringSelection); + } + } + + // The selection cache is no longer going to be valid + if (number) { + number->ClearSelectionCached(); + } else { + mSelectionCached = false; + } + + return rv; +} + +void +nsTextEditorState::FinishedRestoringSelection() +{ + mRestoringSelection = nullptr; +} + +bool +nsTextEditorState::IsSelectionCached() const +{ + if (mBoundFrame) { + HTMLInputElement* number = GetParentNumberControl(mBoundFrame); + if (number) { + return number->IsSelectionCached(); + } + } + return mSelectionCached; +} + +nsTextEditorState::SelectionProperties& +nsTextEditorState::GetSelectionProperties() +{ + if (mBoundFrame) { + HTMLInputElement* number = GetParentNumberControl(mBoundFrame); + if (number) { + return number->GetSelectionProperties(); + } + } + return mSelectionProperties; +} + +void +nsTextEditorState::SetSelectionProperties(nsTextEditorState::SelectionProperties& aProps) +{ + if (mBoundFrame) { + mBoundFrame->SetSelectionRange(aProps.GetStart(), + aProps.GetEnd(), + aProps.GetDirection()); + } else { + mSelectionProperties = aProps; + } +} + + +HTMLInputElement* +nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const +{ + MOZ_ASSERT(aFrame); + nsIContent* content = aFrame->GetContent(); + MOZ_ASSERT(content); + nsIContent* parent = content->GetParent(); + if (!parent) { + return nullptr; + } + nsIContent* parentOfParent = parent->GetParent(); + if (!parentOfParent) { + return nullptr; + } + HTMLInputElement* input = HTMLInputElement::FromContent(parentOfParent); + if (input) { + // This function might be called during frame reconstruction as a result + // of changing the input control's type from number to something else. In + // that situation, the type of the control has changed, but its frame has + // not been reconstructed yet. So we need to check the type of the input + // control in addition to the type of the frame. + return (input->GetType() == NS_FORM_INPUT_NUMBER) ? input : nullptr; + } + + return nullptr; +} + +void +nsTextEditorState::DestroyEditor() +{ + // notify the editor that we are going away + if (mEditorInitialized) { + if (mTextListener) + mEditor->RemoveEditorObserver(mTextListener); + + mEditor->PreDestroy(true); + mEditorInitialized = false; + } + ClearValueCache(); +} + +void +nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame) +{ + NS_ENSURE_TRUE_VOID(mBoundFrame); + + // If it was, however, it should be unbounded from the same frame. + NS_ASSERTION(!aFrame || aFrame == mBoundFrame, "Unbinding from the wrong frame"); + NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame); + + // If the editor is modified but nsIEditorObserver::EditAction() hasn't been + // called yet, we need to notify it here because editor may be destroyed + // before EditAction() is called if selection listener causes flushing layout. + bool isInEditAction = false; + if (mTextListener && mEditor && mEditorInitialized && + NS_SUCCEEDED(mEditor->GetIsInEditAction(&isInEditAction)) && + isInEditAction) { + mTextListener->EditAction(); + } + + // 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, true); + + if (mRestoringSelection) { + mRestoringSelection->Revoke(); + mRestoringSelection = nullptr; + } + + // Save our selection state if needed. + // Note that nsTextControlFrame::GetSelectionRange attempts to initialize the + // editor before grabbing the range, and because this is not an acceptable + // side effect for unbinding from a text control frame, we need to call + // GetSelectionRange before calling DestroyEditor, and only if + // mEditorInitialized indicates that we actually have an editor available. + int32_t start = 0, end = 0; + nsITextControlFrame::SelectionDirection direction = + nsITextControlFrame::eForward; + if (mEditorInitialized) { + HTMLInputElement* number = GetParentNumberControl(aFrame); + if (number) { + // If we are inside a number control, cache the selection on the + // parent control, because this text editor state will be destroyed + // together with the native anonymous text control. + SelectionProperties props; + mBoundFrame->GetSelectionRange(&start, &end, &direction); + props.SetStart(start); + props.SetEnd(end); + props.SetDirection(direction); + number->SetSelectionProperties(props); + } else { + mBoundFrame->GetSelectionRange(&start, &end, &direction); + mSelectionProperties.SetStart(start); + mSelectionProperties.SetEnd(end); + mSelectionProperties.SetDirection(direction); + mSelectionCached = true; + } + } + + // Destroy our editor + DestroyEditor(); + + // Clean up the controller + if (!SuppressEventHandlers(mBoundFrame->PresContext())) + { + nsCOMPtr<nsIControllers> controllers; + nsCOMPtr<nsIDOMHTMLInputElement> inputElement = + do_QueryInterface(mTextCtrlElement); + if (inputElement) + inputElement->GetControllers(getter_AddRefs(controllers)); + else + { + nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement = + do_QueryInterface(mTextCtrlElement); + if (textAreaElement) { + textAreaElement->GetControllers(getter_AddRefs(controllers)); + } + } + + if (controllers) + { + uint32_t numControllers; + nsresult rv = controllers->GetControllerCount(&numControllers); + NS_ASSERTION((NS_SUCCEEDED(rv)), "bad result in gfx text control destructor"); + for (uint32_t i = 0; i < numControllers; i ++) + { + nsCOMPtr<nsIController> controller; + rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); + if (NS_SUCCEEDED(rv) && controller) + { + nsCOMPtr<nsIControllerContext> editController = do_QueryInterface(controller); + if (editController) + { + editController->SetCommandContext(nullptr); + } + } + } + } + } + + if (mSelCon) { + if (mTextListener) { + RefPtr<nsISelection> domSelection; + if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSelection))) && + domSelection) { + nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection)); + + selPriv->RemoveSelectionListener(static_cast<nsISelectionListener*> + (mTextListener)); + } + } + + mSelCon->SetScrollableFrame(nullptr); + mSelCon = nullptr; + } + + if (mTextListener) + { + mTextListener->SetFrame(nullptr); + + nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement); + EventListenerManager* manager = target->GetExistingListenerManager(); + if (manager) { + manager->RemoveEventListenerByType(mTextListener, + NS_LITERAL_STRING("keydown"), + TrustedEventsAtSystemGroupBubble()); + manager->RemoveEventListenerByType(mTextListener, + NS_LITERAL_STRING("keypress"), + TrustedEventsAtSystemGroupBubble()); + manager->RemoveEventListenerByType(mTextListener, + NS_LITERAL_STRING("keyup"), + TrustedEventsAtSystemGroupBubble()); + } + + mTextListener = nullptr; + } + + mBoundFrame = nullptr; + // Clear mRootNode so that we don't unexpectedly notify below. + nsCOMPtr<Element> rootNode = mRootNode.forget(); + + // Now that we don't have a frame any more, store the value in the text buffer. + // The only case where we don't do this is if a value transfer is in progress. + if (!mValueTransferInProgress) { + bool success = SetValue(value, eSetValue_Internal); + // TODO Find something better to do if this fails... + NS_ENSURE_TRUE_VOID(success); + } + + if (rootNode && mMutationObserver) { + rootNode->RemoveMutationObserver(mMutationObserver); + mMutationObserver = nullptr; + } + + // 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(&rootNode); + nsContentUtils::DestroyAnonymousContent(&mPlaceholderDiv); +} + +nsresult +nsTextEditorState::CreateRootNode() +{ + MOZ_ASSERT(!mRootNode); + MOZ_ASSERT(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. + RefPtr<mozilla::dom::NodeInfo> nodeInfo; + nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr, + kNameSpaceID_XHTML, + nsIDOMNode::ELEMENT_NODE); + + nsresult rv = NS_NewHTMLElement(getter_AddRefs(mRootNode), nodeInfo.forget(), + NOT_FROM_PARSER); + NS_ENSURE_SUCCESS(rv, rv); + + if (!IsSingleLineTextControl()) { + mMutationObserver = new nsAnonDivObserver(this); + mRootNode->AddMutationObserver(mMutationObserver); + } + + return rv; +} + +nsresult +nsTextEditorState::InitializeRootNode() +{ + // Make our root node editable + mRootNode->SetFlags(NODE_IS_EDITABLE); + + // 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"); + int32_t 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->StyleDisplay(); + if (disp->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE && + disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) { + classValue.AppendLiteral(" inherit-overflow"); + } + } + nsresult rv = mRootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, + classValue, false); + NS_ENSURE_SUCCESS(rv, rv); + + return mBoundFrame->UpdateValueDisplay(false); +} + +nsresult +nsTextEditorState::CreatePlaceholderNode() +{ +#ifdef DEBUG + { + nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); + if (content) { + nsAutoString placeholderTxt; + content->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, + placeholderTxt); + nsContentUtils::RemoveNewlines(placeholderTxt); + NS_ASSERTION(!placeholderTxt.IsEmpty(), "CreatePlaceholderNode() shouldn't \ +be called if @placeholder is the empty string when trimmed from line breaks"); + } + } +#endif // DEBUG + + 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; + + // Create a DIV for the placeholder + // and add it to the anonymous content child list + RefPtr<mozilla::dom::NodeInfo> nodeInfo; + nodeInfo = pNodeInfoManager->GetNodeInfo(nsGkAtoms::div, nullptr, + kNameSpaceID_XHTML, + nsIDOMNode::ELEMENT_NODE); + + rv = NS_NewHTMLElement(getter_AddRefs(mPlaceholderDiv), nodeInfo.forget(), + NOT_FROM_PARSER); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the text node for the placeholder text before doing anything else + RefPtr<nsTextNode> placeholderText = new nsTextNode(pNodeInfoManager); + + rv = mPlaceholderDiv->AppendChildTo(placeholderText, false); + NS_ENSURE_SUCCESS(rv, rv); + + // initialize the text + UpdatePlaceholderText(false); + + return NS_OK; +} + +bool +nsTextEditorState::GetMaxLength(int32_t* aMaxLength) +{ + nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); + nsGenericHTMLElement* element = + nsGenericHTMLElement::FromContentOrNull(content); + NS_ENSURE_TRUE(element, false); + + const nsAttrValue* attr = element->GetParsedAttr(nsGkAtoms::maxlength); + if (attr && attr->Type() == nsAttrValue::eInteger) { + *aMaxLength = attr->GetIntegerValue(); + + return true; + } + + return false; +} + +void +nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const +{ + // While SetValue() is being called and requesting to commit composition to + // IME, GetValue() may be called for appending text or something. Then, we + // need to return the latest aValue of SetValue() since the value hasn't + // been set to the editor yet. + if (mIsCommittingComposition) { + aValue = mValueBeingSet; + return; + } + + if (mEditor && mBoundFrame && (mEditorInitialized || !IsSingleLineTextControl())) { + bool canCache = aIgnoreWrap && !IsSingleLineTextControl(); + if (canCache && !mCachedValue.IsEmpty()) { + aValue = mCachedValue; + return; + } + + aValue.Truncate(); // initialize out param + + uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak | + nsIDocumentEncoder::OutputPreformatted | + nsIDocumentEncoder::OutputPersistNBSP); + + if (IsPlainTextControl()) + { + flags |= nsIDocumentEncoder::OutputBodyOnly; + } + + if (!aIgnoreWrap) { + nsITextControlElement::nsHTMLTextWrap wrapProp; + nsCOMPtr<nsIContent> 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 <br> nodes all over), we wouldn't need + // this. + { /* Scope for AutoNoJSAPI. */ + AutoNoJSAPI nojsapi; + + 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 = *mValue; + } + } +} + +bool +nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags) +{ + nsAutoString newValue(aValue); + + // While mIsCommittingComposition is true (that means that some event + // handlers which are fired during committing composition are the caller of + // this method), GetValue() uses mValueBeingSet for its result because the + // first calls of this methods hasn't set the value yet. So, when it's true, + // we need to modify mValueBeingSet. In this case, we will back to the first + // call of this method, then, mValueBeingSet will be truncated when + // mIsCommittingComposition is set false. See below. + if (mIsCommittingComposition) { + mValueBeingSet = aValue; + } + + // Note that if this may be called during reframe of the editor. In such + // case, we shouldn't commit composition. Therefore, when this is called + // for internal processing, we shouldn't commit the composition. + if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) { + if (EditorHasComposition()) { + // When this is called recursively, there shouldn't be composition. + if (NS_WARN_IF(mIsCommittingComposition)) { + // Don't request to commit composition again. But if it occurs, + // we should skip to set the new value to the editor here. It should + // be set later with the updated mValueBeingSet. + return true; + } + if (NS_WARN_IF(!mBoundFrame)) { + // We're not sure if this case is possible. + } else { + // If setting value won't change current value, we shouldn't commit + // composition for compatibility with the other browsers. + nsAutoString currentValue; + mBoundFrame->GetText(currentValue); + if (newValue == currentValue) { + // Note that in this case, we shouldn't fire any events with setting + // value because event handlers may try to set value recursively but + // we cannot commit composition at that time due to unsafe to run + // script (see below). + return true; + } + } + // If there is composition, need to commit composition first because + // other browsers do that. + // NOTE: We don't need to block nested calls of this because input nor + // other events won't be fired by setting values and script blocker + // is used during setting the value to the editor. IE also allows + // to set the editor value on the input event which is caused by + // forcibly committing composition. + if (nsContentUtils::IsSafeToRunScript()) { + WeakPtr<nsTextEditorState> self(this); + // WARNING: During this call, compositionupdate, compositionend, input + // events will be fired. Therefore, everything can occur. E.g., the + // document may be unloaded. + mValueBeingSet = aValue; + mIsCommittingComposition = true; + nsCOMPtr<nsIEditorIMESupport> editorIMESupport = + do_QueryInterface(mEditor); + MOZ_RELEASE_ASSERT(editorIMESupport); + nsresult rv = editorIMESupport->ForceCompositionEnd(); + if (!self.get()) { + return true; + } + mIsCommittingComposition = false; + // If this is called recursively during committing composition and + // some of them may be skipped above. Therefore, we need to set + // value to the editor with the aValue of the latest call. + newValue = mValueBeingSet; + // When mIsCommittingComposition is false, mValueBeingSet won't be + // used. Therefore, let's clear it. + mValueBeingSet.Truncate(); + if (NS_FAILED(rv)) { + NS_WARNING("nsTextEditorState failed to commit composition"); + return true; + } + } else { + NS_WARNING("SetValue() is called when there is composition but " + "it's not safe to request to commit the composition"); + } + } + } + + 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; + +#ifdef DEBUG + if (IsSingleLineTextControl()) { + NS_ASSERTION(mEditorInitialized || mInitializing, + "We should never try to use the editor if we're not initialized unless we're being initialized"); + } +#endif + + nsAutoString currentValue; + mBoundFrame->GetText(currentValue); + + nsWeakFrame weakFrame(mBoundFrame); + + // this is necessary to avoid infinite recursion + if (!currentValue.Equals(newValue)) + { + ValueSetter valueSetter(mEditor); + + // \r is an illegal character in the dom, but people use them, + // so convert windows and mac platform linebreaks to \n: + if (newValue.FindChar(char16_t('\r')) != -1) { + if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) { + return false; + } + } + + nsCOMPtr<nsIDOMDocument> domDoc; + mEditor->GetDocument(getter_AddRefs(domDoc)); + if (!domDoc) { + NS_WARNING("Why don't we have a document?"); + return true; + } + + // 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. + { + AutoNoJSAPI nojsapi; + + nsCOMPtr<nsISelection> domSel; + nsCOMPtr<nsISelectionPrivate> selPriv; + mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(domSel)); + if (domSel) + { + selPriv = do_QueryInterface(domSel); + if (selPriv) + selPriv->StartBatchChanges(); + } + + nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get(); + uint32_t currentLength = currentValue.Length(); + uint32_t newlength = newValue.Length(); + if (!currentLength || + !StringBeginsWith(newValue, currentValue)) { + // Replace the whole text. + currentLength = 0; + kungFuDeathGrip->SelectAll(); + } else { + // Collapse selection to the end so that we can append data. + mBoundFrame->SelectAllOrCollapseToEndOfText(false); + } + const nsAString& insertValue = + StringTail(newValue, newlength - currentLength); + nsCOMPtr<nsIPlaintextEditor> plaintextEditor = do_QueryInterface(mEditor); + if (!plaintextEditor || !weakFrame.IsAlive()) { + NS_WARNING("Somehow not a plaintext editor?"); + return true; + } + + valueSetter.Init(); + + // get the flags, remove readonly and disabled, set the value, + // restore flags + uint32_t flags, savedFlags; + mEditor->GetFlags(&savedFlags); + flags = savedFlags; + flags &= ~(nsIPlaintextEditor::eEditorDisabledMask); + flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask); + flags |= nsIPlaintextEditor::eEditorDontEchoPassword; + mEditor->SetFlags(flags); + + mTextListener->SettingValue(true); + bool notifyValueChanged = !!(aFlags & eSetValue_Notify); + mTextListener->SetValueChanged(notifyValueChanged); + + // Also don't enforce max-length here + int32_t savedMaxLength; + plaintextEditor->GetMaxTextLength(&savedMaxLength); + plaintextEditor->SetMaxTextLength(-1); + + if (insertValue.IsEmpty()) { + mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); + } else { + plaintextEditor->InsertText(insertValue); + } + + mTextListener->SetValueChanged(true); + mTextListener->SettingValue(false); + + if (!weakFrame.IsAlive()) { + // If the frame was destroyed because of a flush somewhere inside + // InsertText, mBoundFrame here will be false. But it's also possible + // for the frame to go away because of another reason (such as deleting + // the existing selection -- see bug 574558), in which case we don't + // need to reset the value here. + if (!mBoundFrame) { + return SetValue(newValue, aFlags & eSetValue_Notify); + } + return true; + } + + if (!IsSingleLineTextControl()) { + if (!mCachedValue.Assign(newValue, fallible)) { + return false; + } + } + + plaintextEditor->SetMaxTextLength(savedMaxLength); + mEditor->SetFlags(savedFlags); + if (selPriv) + selPriv->EndBatchChanges(); + } + } + } else { + if (!mValue) { + mValue.emplace(); + } + nsString value; + if (!value.Assign(newValue, fallible)) { + return false; + } + if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) { + return false; + } + if (!mValue->Assign(value, fallible)) { + return false; + } + + // Update the frame display if needed + if (mBoundFrame) { + mBoundFrame->UpdateValueDisplay(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); + + mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mRootNode, + /* aWasInteractiveUserChange = */ false); + + return true; +} + +void +nsTextEditorState::InitializeKeyboardEventListeners() +{ + //register key listeners + nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement); + EventListenerManager* manager = target->GetOrCreateListenerManager(); + if (manager) { + manager->AddEventListenerByType(mTextListener, + NS_LITERAL_STRING("keydown"), + TrustedEventsAtSystemGroupBubble()); + manager->AddEventListenerByType(mTextListener, + NS_LITERAL_STRING("keypress"), + TrustedEventsAtSystemGroupBubble()); + manager->AddEventListenerByType(mTextListener, + NS_LITERAL_STRING("keyup"), + TrustedEventsAtSystemGroupBubble()); + } + + mSelCon->SetScrollableFrame(do_QueryFrame(mBoundFrame->PrincipalChildList().FirstChild())); +} + +void +nsTextEditorState::ValueWasChanged(bool aNotify) +{ + UpdatePlaceholderVisibility(aNotify); +} + +void +nsTextEditorState::UpdatePlaceholderText(bool aNotify) +{ + NS_ASSERTION(mPlaceholderDiv, "This function should not be called if " + "mPlaceholderDiv isn't set"); + + // If we don't have a placeholder div, there's nothing to do. + if (!mPlaceholderDiv) + return; + + nsAutoString placeholderValue; + + nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); + content->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholderValue); + nsContentUtils::RemoveNewlines(placeholderValue); + NS_ASSERTION(mPlaceholderDiv->GetFirstChild(), "placeholder div has no child"); + mPlaceholderDiv->GetFirstChild()->SetText(placeholderValue, aNotify); +} + +void +nsTextEditorState::UpdatePlaceholderVisibility(bool aNotify) +{ + nsAutoString value; + GetValue(value, true); + + mPlaceholderVisibility = value.IsEmpty(); + + if (mPlaceholderVisibility && + !Preferences::GetBool("dom.placeholder.show_on_focus", true)) { + nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); + mPlaceholderVisibility = !nsContentUtils::IsFocusedContent(content); + } + + if (mBoundFrame && aNotify) { + mBoundFrame->InvalidateFrame(); + } +} + +void +nsTextEditorState::HideSelectionIfBlurred() +{ + MOZ_ASSERT(mSelCon, "Should have a selection controller if we have a frame!"); + nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement); + if (!nsContentUtils::IsFocusedContent(content)) { + mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); + } +} + +bool +nsTextEditorState::EditorHasComposition() +{ + bool isComposing = false; + nsCOMPtr<nsIEditorIMESupport> editorIMESupport = do_QueryInterface(mEditor); + return editorIMESupport && + NS_SUCCEEDED(editorIMESupport->GetComposing(&isComposing)) && + isComposing; +} + +NS_IMPL_ISUPPORTS(nsAnonDivObserver, nsIMutationObserver) + +void +nsAnonDivObserver::CharacterDataChanged(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ + mTextEditorState->ClearValueCache(); +} + +void +nsAnonDivObserver::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t /* unused */) +{ + mTextEditorState->ClearValueCache(); +} + +void +nsAnonDivObserver::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t /* unused */) +{ + mTextEditorState->ClearValueCache(); +} + +void +nsAnonDivObserver::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + mTextEditorState->ClearValueCache(); +} |