/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=2 et 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 "nsIEditorObserver.h" #include "nsIWidget.h" #include "nsIDocumentEncoder.h" #include "nsISelectionPrivate.h" #include "nsPIDOMWindow.h" #include "nsServiceManagerUtils.h" #include "nsIEditor.h" #include "nsTextEditRules.h" #include "mozilla/dom/Selection.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" using namespace mozilla; using namespace mozilla::dom; 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 mEditor; bool mOuterTransaction; }; class RestoreSelectionState : public nsRunnable { public: RestoreSelectionState(nsTextEditorState *aState, nsTextControlFrame *aFrame) : mFrame(aFrame), mTextEditorState(aState) { } NS_IMETHOD Run() { if (!mTextEditorState) { return NS_OK; } 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(); mFrame->SetSelectionRange(properties.mStart, properties.mEnd, properties.mDirection); if (!mTextEditorState->mSelectionRestoreEagerInit) { mTextEditorState->HideSelectionIfBlurred(); } mTextEditorState->mSelectionRestoreEagerInit = false; } 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::GetTextControlElementFromEditingHost(nsIContent* aHost) { if (!aHost) { return nullptr; } nsCOMPtr 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(int16_t type, nsISelection** _retval) override; NS_IMETHOD ScrollSelectionIntoView(int16_t aType, int16_t aRegion, int16_t aFlags) override; NS_IMETHOD RepaintSelection(int16_t type) override; NS_IMETHOD 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: nsRefPtr mFrameSelection; nsCOMPtr 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(int16_t 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(int16_t aType, int16_t aRegion, int16_t aFlags) { if (!mFrameSelection) return NS_ERROR_FAILURE; return mFrameSelection->ScrollSelectionIntoView(aType, aRegion, aFlags); } NS_IMETHODIMP nsTextInputSelectionImpl::RepaintSelection(int16_t 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(bool 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(bool 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(bool *_retval) { return GetCaretVisible(_retval); } NS_IMETHODIMP nsTextInputSelectionImpl::GetCaretVisible(bool *_retval) { if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; nsresult result; nsCOMPtr shell = do_QueryReferent(mPresShellWeak, &result); if (shell) { nsRefPtr 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 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::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) { if (mFrameSelection) return mFrameSelection->PhysicalMove(aDirection, aAmount, aExtend); return NS_ERROR_NULL_POINTER; } NS_IMETHODIMP nsTextInputSelectionImpl::CharacterMove(bool aForward, bool 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::CharacterExtendForBackspace() { if (mFrameSelection) return mFrameSelection->CharacterExtendForBackspace(); return NS_ERROR_NULL_POINTER; } NS_IMETHODIMP nsTextInputSelectionImpl::WordMove(bool aForward, bool aExtend) { if (mFrameSelection) return mFrameSelection->WordMove(aForward, aExtend); return NS_ERROR_NULL_POINTER; } NS_IMETHODIMP nsTextInputSelectionImpl::WordExtendForDelete(bool aForward) { if (mFrameSelection) return mFrameSelection->WordExtendForDelete(aForward); return NS_ERROR_NULL_POINTER; } NS_IMETHODIMP nsTextInputSelectionImpl::LineMove(bool aForward, bool 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(bool aForward, bool aExtend) { if (mFrameSelection) return mFrameSelection->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) { 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, nsISelectionController::SCROLL_SYNCHRONOUS); } NS_IMETHODIMP nsTextInputSelectionImpl::CompleteScroll(bool 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(bool aForward, bool 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 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 } } } mFrameSelection->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; 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; 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; mScrollFrame->ScrollBy(nsIntPoint(aRight ? 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, int16_t startOffset, int16_t EndOffset, bool *_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; } nsresult nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset, bool* aRetval) { if (!mPresShellWeak) { return NS_ERROR_NOT_INITIALIZED; } nsCOMPtr 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 doc = content->GetComposedDoc(); if (doc) { nsCOMPtr presShell = doc->GetShell(); if (presShell) { nsEventStatus status = nsEventStatus_eIgnore; WidgetEvent event(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 (!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(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; } const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand); nsCOMPtr 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->GetInternalNSEvent()->AsKeyboardEvent(); if (!keyEvent) { return NS_ERROR_UNEXPECTED; } if (keyEvent->message != NS_KEY_PRESS) { return NS_OK; } nsIWidget::NativeKeyBindingsType nativeKeyBindingsType = mTxtCtrlElement->IsTextArea() ? nsIWidget::NativeKeyBindingsForMultiLineEditor : nsIWidget::NativeKeyBindingsForSingleLineEditor; nsIWidget* widget = keyEvent->widget; // 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() { nsWeakFrame weakFrame = mFrame; nsITextControlFrame* frameBase = do_QueryFrame(mFrame); nsTextControlFrame* frame = static_cast (frameBase); NS_ASSERTION(frame, "Where is our frame?"); // // Update the undo / redo menus // nsCOMPtr 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(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 doc = content->GetComposedDoc(); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); nsPIDOMWindow *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), mRestoringSelection(nullptr), mBoundFrame(nullptr), mEverInited(false), mEditorInitialized(false), mInitializing(false), mValueTransferInProgress(false), mSelectionCached(true), mSelectionRestoreEagerInit(false), mPlaceholderVisibility(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 nsRunnable { public: PrepareEditorEvent(nsTextEditorState &aState, nsIContent *aOwnerContent, const nsAString &aCurrentValue) : mState(&aState) , mOwnerContent(aOwnerContent) , mCurrentValue(aCurrentValue) { aState.mValueTransferInProgress = true; } NS_IMETHOD Run() { 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 mState; nsCOMPtr 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; nsIContent *rootNode = GetRootNode(); nsresult rv = InitializeRootNode(); NS_ENSURE_SUCCESS(rv, rv); nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell(); NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); // Create selection nsRefPtr 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. 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) { nsCOMPtr 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 } if (!nsContentUtils::AddScriptRunner( new PrepareEditorEvent(*this, content, currentValue))) return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } struct PreDestroyer { void Init(nsIEditor* aEditor) { mNewEditor = aEditor; } ~PreDestroyer() { if (mNewEditor) { mNewEditor->PreDestroy(true); } } void Swap(nsCOMPtr& aEditor) { return mNewEditor.swap(aEditor); } private: nsCOMPtr 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; } // 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 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! } // 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
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 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 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) { uint32_t numControllers; bool found = false; rv = controllers->GetControllerCount(&numControllers); for (uint32_t 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 = 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 int32_t 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. // 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, false, false); 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 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; } bool nsTextEditorState::IsSelectionCached() const { if (mBoundFrame) { HTMLInputElement* number = GetParentNumberControl(mBoundFrame); if (number) { return number->IsSelectionCached(); } } return mSelectionCached; } nsTextEditorState::SelectionProperties& nsTextEditorState::GetSelectionProperties() { if (mBoundFrame) { HTMLInputElement* number = GetParentNumberControl(mBoundFrame); if (number) { return number->GetSelectionProperties(); } } return mSelectionProperties; } HTMLInputElement* nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const { MOZ_ASSERT(aFrame); nsIContent* content = aFrame->GetContent(); MOZ_ASSERT(content); nsIContent* parent = content->GetParent(); if (!parent) { return nullptr; } nsIContent* parentOfParent = parent->GetParent(); if (!parentOfParent) { return nullptr; } HTMLInputElement* input = HTMLInputElement::FromContent(parentOfParent); if (input) { // This function might be called during frame reconstruction as a result // of changing the input control's type from number to something else. In // that situation, the type of the control has changed, but its frame has // not been reconstructed yet. So we need to check the type of the input // control in addition to the type of the frame. return (input->GetType() == NS_FORM_INPUT_NUMBER) ? input : nullptr; } return nullptr; } void nsTextEditorState::DestroyEditor() { // 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. 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(&props.mStart, &props.mEnd, &props.mDirection); number->SetSelectionProperties(props); } else { mBoundFrame->GetSelectionRange(&mSelectionProperties.mStart, &mSelectionProperties.mEnd, &mSelectionProperties.mDirection); mSelectionCached = 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) { 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 controller; rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); if (NS_SUCCEEDED(rv) && controller) { nsCOMPtr editController = do_QueryInterface(controller); if (editController) { editController->SetCommandContext(nullptr); } } } } } 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(nullptr); mSelCon = nullptr; } if (mTextListener) { mTextListener->SetFrame(nullptr); nsCOMPtr 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; // 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, false, false); // TODO Find something better to do if this fails... NS_ENSURE_TRUE_VOID(success); } if (mRootNode && mMutationObserver) { mRootNode->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(&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. nsRefPtr 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 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 nsRefPtr 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 nsRefPtr 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 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 { 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 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 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 = NS_ConvertUTF8toUTF16(*mValue); } } } bool nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput, bool aSetValueChanged) { 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(aValue)) { ValueSetter valueSetter(mEditor); // \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; if (!newValue.Assign(aValue, fallible)) { return false; } if (aValue.FindChar(char16_t('\r')) != -1) { if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) { return false; } } nsCOMPtr 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 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(); uint32_t currentLength = currentValue.Length(); uint32_t 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(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 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); mTextListener->SetValueChanged(aSetValueChanged); // 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, false, aSetValueChanged); } 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 = new nsCString; } nsString value; if (!value.Assign(aValue, fallible)) { return false; } if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) { return false; } if (!CopyUTF16toUTF8(value, *mValue, 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(!!mRootNode); return true; } void nsTextEditorState::InitializeKeyboardEventListeners() { //register key listeners nsCOMPtr 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->GetFirstPrincipalChild())); } 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 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 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 content = do_QueryInterface(mTextCtrlElement); if (!nsContentUtils::IsFocusedContent(content)) { mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); } } 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(); }