/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "mozilla/TextEditor.h" #include "InternetCiter.h" #include "TextEditUtils.h" #include "gfxFontUtils.h" #include "mozilla/Assertions.h" #include "mozilla/EditAction.h" #include "mozilla/EditorDOMPoint.h" #include "mozilla/EditorUtils.h" // AutoPlaceholderBatch, AutoRules #include "mozilla/HTMLEditor.h" #include "mozilla/mozalloc.h" #include "mozilla/Preferences.h" #include "mozilla/TextEditRules.h" #include "mozilla/TextComposition.h" #include "mozilla/TextEvents.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/Element.h" #include "nsAString.h" #include "nsCRT.h" #include "nsCaret.h" #include "nsCharTraits.h" #include "nsComponentManagerUtils.h" #include "nsContentCID.h" #include "nsContentList.h" #include "nsCopySupport.h" #include "nsDebug.h" #include "nsDependentSubstring.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsIClipboard.h" #include "nsIContent.h" #include "nsIContentIterator.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" #include "nsIDOMEventTarget.h" #include "nsIDOMNode.h" #include "nsIDocumentEncoder.h" #include "nsINode.h" #include "nsIPresShell.h" #include "nsISelectionController.h" #include "nsISupportsPrimitives.h" #include "nsITransferable.h" #include "nsIWeakReferenceUtils.h" #include "nsNameSpaceManager.h" #include "nsLiteralString.h" #include "nsReadableUtils.h" #include "nsServiceManagerUtils.h" #include "nsString.h" #include "nsStringFwd.h" #include "nsSubstringTuple.h" #include "nsTextNode.h" #include "nsUnicharUtils.h" #include "nsXPCOM.h" class nsIOutputStream; class nsISupports; namespace mozilla { using namespace dom; TextEditor::TextEditor() : mWrapColumn(0) , mMaxTextLength(-1) , mInitTriggerCounter(0) , mNewlineHandling(nsIPlaintextEditor::eNewlinesPasteToFirst) #ifdef XP_WIN , mCaretStyle(1) #else , mCaretStyle(0) #endif { // check the "single line editor newline handling" // and "caret behaviour in selection" prefs GetDefaultEditorPrefs(mNewlineHandling, mCaretStyle); } TextEditor::~TextEditor() { // Remove event listeners. Note that if we had an HTML editor, // it installed its own instead of these RemoveEventListeners(); if (mRules) mRules->DetachEditor(); } NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase) if (tmp->mRules) tmp->mRules->DetachEditor(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mRules) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRules) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase) NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor) NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor) NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport) NS_INTERFACE_MAP_END_INHERITING(EditorBase) NS_IMETHODIMP TextEditor::Init(nsIDOMDocument* aDoc, nsIContent* aRoot, nsISelectionController* aSelCon, uint32_t aFlags, const nsAString& aInitialValue) { NS_PRECONDITION(aDoc, "bad arg"); NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); if (mRules) { mRules->DetachEditor(); } nsresult rulesRv = NS_OK; { // block to scope AutoEditInitRulesTrigger AutoEditInitRulesTrigger rulesTrigger(this, rulesRv); // Init the base editor nsresult rv = EditorBase::Init(aDoc, aRoot, aSelCon, aFlags, aInitialValue); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } NS_ENSURE_SUCCESS(rulesRv, rulesRv); // mRules may not have been initialized yet, when this is called via // HTMLEditor::Init. if (mRules) { mRules->SetInitialValue(aInitialValue); } return NS_OK; } static int32_t sNewlineHandlingPref = -1, sCaretStylePref = -1; static void EditorPrefsChangedCallback(const char* aPrefName, void *) { if (!nsCRT::strcmp(aPrefName, "editor.singleLine.pasteNewlines")) { sNewlineHandlingPref = Preferences::GetInt("editor.singleLine.pasteNewlines", nsIPlaintextEditor::eNewlinesPasteToFirst); } else if (!nsCRT::strcmp(aPrefName, "layout.selection.caret_style")) { sCaretStylePref = Preferences::GetInt("layout.selection.caret_style", #ifdef XP_WIN 1); if (!sCaretStylePref) { sCaretStylePref = 1; } #else 0); #endif } } // static void TextEditor::GetDefaultEditorPrefs(int32_t& aNewlineHandling, int32_t& aCaretStyle) { if (sNewlineHandlingPref == -1) { Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback, "editor.singleLine.pasteNewlines"); Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback, "layout.selection.caret_style"); } aNewlineHandling = sNewlineHandlingPref; aCaretStyle = sCaretStylePref; } void TextEditor::BeginEditorInit() { mInitTriggerCounter++; } nsresult TextEditor::EndEditorInit() { NS_PRECONDITION(mInitTriggerCounter > 0, "ended editor init before we began?"); mInitTriggerCounter--; if (mInitTriggerCounter) { return NS_OK; } nsresult rv = InitRules(); if (NS_FAILED(rv)) { return rv; } // Throw away the old transaction manager if this is not the first time that // we're initializing the editor. EnableUndo(false); EnableUndo(true); return NS_OK; } NS_IMETHODIMP TextEditor::SetDocumentCharacterSet(const nsACString& characterSet) { nsresult rv = EditorBase::SetDocumentCharacterSet(characterSet); NS_ENSURE_SUCCESS(rv, rv); // Update META charset element. nsCOMPtr doc = GetDocument(); if (NS_WARN_IF(!doc)) { return NS_ERROR_NOT_INITIALIZED; } if (UpdateMetaCharset(*doc, characterSet)) { return NS_OK; } RefPtr headList = doc->GetElementsByTagName(NS_LITERAL_STRING("head")); if (NS_WARN_IF(!headList)) { return NS_OK; } nsCOMPtr headNode = headList->Item(0); if (NS_WARN_IF(!headNode)) { return NS_OK; } // Create a new meta charset tag EditorRawDOMPoint atStartOfHeadNode(headNode, 0); RefPtr metaElement = CreateNode(nsGkAtoms::meta, atStartOfHeadNode); if (NS_WARN_IF(!metaElement)) { return NS_OK; } // Set attributes to the created element if (characterSet.IsEmpty()) { return NS_OK; } // not undoable, undo should undo CreateNode metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, NS_LITERAL_STRING("Content-Type"), true); metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::content, NS_LITERAL_STRING("text/html;charset=") + NS_ConvertASCIItoUTF16(characterSet), true); return NS_OK; } bool TextEditor::UpdateMetaCharset(nsIDocument& aDocument, const nsACString& aCharacterSet) { // get a list of META tags RefPtr metaList = aDocument.GetElementsByTagName(NS_LITERAL_STRING("meta")); if (NS_WARN_IF(!metaList)) { return false; } for (uint32_t i = 0; i < metaList->Length(true); ++i) { nsCOMPtr metaNode = metaList->Item(i); MOZ_ASSERT(metaNode); if (!metaNode->IsElement()) { continue; } nsAutoString currentValue; metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, currentValue); if (!FindInReadable(NS_LITERAL_STRING("content-type"), currentValue, nsCaseInsensitiveStringComparator())) { continue; } metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::content, currentValue); NS_NAMED_LITERAL_STRING(charsetEquals, "charset="); nsAString::const_iterator originalStart, start, end; originalStart = currentValue.BeginReading(start); currentValue.EndReading(end); if (!FindInReadable(charsetEquals, start, end, nsCaseInsensitiveStringComparator())) { continue; } // set attribute to charset=text/html RefPtr metaElement = metaNode->AsElement(); MOZ_ASSERT(metaElement); nsresult rv = EditorBase::SetAttribute(metaElement, nsGkAtoms::content, Substring(originalStart, start) + charsetEquals + NS_ConvertASCIItoUTF16(aCharacterSet)); return NS_SUCCEEDED(rv); } return false; } NS_IMETHODIMP TextEditor::InitRules() { if (!mRules) { // instantiate the rules for this text editor mRules = new TextEditRules(); } return mRules->Init(this); } nsresult TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) { // NOTE: When you change this method, you should also change: // * editor/libeditor/tests/test_texteditor_keyevent_handling.html // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html // // And also when you add new key handling, you need to change the subclass's // HandleKeyPressEvent()'s switch statement. if (IsReadonly() || IsDisabled()) { // When we're not editable, the events handled on EditorBase. return EditorBase::HandleKeyPressEvent(aKeyboardEvent); } if (NS_WARN_IF(!aKeyboardEvent)) { return NS_ERROR_UNEXPECTED; } MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress, "HandleKeyPressEvent gets non-keypress event"); switch (aKeyboardEvent->mKeyCode) { case NS_VK_META: case NS_VK_WIN: case NS_VK_SHIFT: case NS_VK_CONTROL: case NS_VK_ALT: case NS_VK_BACK: case NS_VK_DELETE: // These keys are handled on EditorBase return EditorBase::HandleKeyPressEvent(aKeyboardEvent); case NS_VK_TAB: { if (IsTabbable()) { return NS_OK; // let it be used for focus switching } if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) { return NS_OK; } // else we insert the tab straight through aKeyboardEvent->PreventDefault(); return TypedText(NS_LITERAL_STRING("\t"), eTypedText); } case NS_VK_RETURN: if (IsSingleLineEditor() || aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) { return NS_OK; } aKeyboardEvent->PreventDefault(); return TypedText(EmptyString(), eTypedBreak); } // NOTE: On some keyboard layout, some characters are inputted with Control // key or Alt key, but at that time, widget sets FALSE to these keys. if (!aKeyboardEvent->mCharCode || aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) { // we don't PreventDefault() here or keybindings like control-x won't work return NS_OK; } aKeyboardEvent->PreventDefault(); nsAutoString str(aKeyboardEvent->mCharCode); return TypedText(str, eTypedText); } /* This routine is needed to provide a bottleneck for typing for logging purposes. Can't use HandleKeyPress() (above) for that since it takes a nsIDOMKeyEvent* parameter. So instead we pass enough info through to TypedText() to determine what action to take, but without passing an event. */ NS_IMETHODIMP TextEditor::TypedText(const nsAString& aString, ETypingAction aAction) { AutoPlaceholderBatch batch(this, nsGkAtoms::TypingTxnName); switch (aAction) { case eTypedText: return InsertText(aString); case eTypedBreak: return InsertLineBreak(); default: // eTypedBR is only for HTML return NS_ERROR_FAILURE; } } already_AddRefed TextEditor::CreateBR(const EditorRawDOMPoint& aPointToInsert, EDirection aSelect /* = eNone */) { RefPtr selection = GetSelection(); if (NS_WARN_IF(!selection)) { return nullptr; } // We assume everything is fine if newBRElement is not null. return CreateBRImpl(*selection, aPointToInsert, aSelect); } already_AddRefed TextEditor::CreateBRImpl(Selection& aSelection, const EditorRawDOMPoint& aPointToInsert, EDirection aSelect) { if (NS_WARN_IF(!aPointToInsert.IsSet())) { return nullptr; } // We need to insert a
node. RefPtr newBRElement; if (aPointToInsert.IsInTextNode()) { EditorDOMPoint pointInContainer; if (aPointToInsert.IsStartOfContainer()) { // Insert before the text node. pointInContainer.Set(aPointToInsert.GetContainer()); if (NS_WARN_IF(!pointInContainer.IsSet())) { return nullptr; } } else if (aPointToInsert.IsEndOfContainer()) { // Insert after the text node. pointInContainer.Set(aPointToInsert.GetContainer()); if (NS_WARN_IF(!pointInContainer.IsSet())) { return nullptr; } DebugOnly advanced = pointInContainer.AdvanceOffset(); NS_WARNING_ASSERTION(advanced, "Failed to advance offset to after the text node"); } else { MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid()); // Unfortunately, we need to split the text node at the offset. ErrorResult error; nsCOMPtr newLeftNode = SplitNode(aPointToInsert, error); if (NS_WARN_IF(error.Failed())) { error.SuppressException(); return nullptr; } Unused << newLeftNode; // Insert new
before the right node. pointInContainer.Set(aPointToInsert.GetContainer()); } // Create a
node. newBRElement = CreateNode(nsGkAtoms::br, pointInContainer.AsRaw()); if (NS_WARN_IF(!newBRElement)) { return nullptr; } } else { newBRElement = CreateNode(nsGkAtoms::br, aPointToInsert); if (NS_WARN_IF(!newBRElement)) { return nullptr; } } switch (aSelect) { case eNone: break; case eNext: { aSelection.SetInterlinePosition(true); // Collapse selection after the
node. EditorRawDOMPoint afterBRElement(newBRElement); if (afterBRElement.IsSet()) { DebugOnly advanced = afterBRElement.AdvanceOffset(); NS_WARNING_ASSERTION(advanced, "Failed to advance offset after the
element"); ErrorResult error; aSelection.Collapse(afterBRElement, error); NS_WARNING_ASSERTION(!error.Failed(), "Failed to collapse selection after the
element"); } else { NS_WARNING("The
node is not in the DOM tree?"); } break; } case ePrevious: { aSelection.SetInterlinePosition(true); // Collapse selection at the
node. EditorRawDOMPoint atBRElement(newBRElement); if (atBRElement.IsSet()) { ErrorResult error; aSelection.Collapse(atBRElement, error); NS_WARNING_ASSERTION(!error.Failed(), "Failed to collapse selection at the
element"); } else { NS_WARNING("The
node is not in the DOM tree?"); } break; } default: NS_WARNING("aSelect has invalid value, the caller need to set selection " "by itself"); break; } return newBRElement.forget(); } nsresult TextEditor::ExtendSelectionForDelete(Selection* aSelection, nsIEditor::EDirection* aAction) { bool bCollapsed = aSelection->Collapsed(); if (*aAction == eNextWord || *aAction == ePreviousWord || (*aAction == eNext && bCollapsed) || (*aAction == ePrevious && bCollapsed) || *aAction == eToBeginningOfLine || *aAction == eToEndOfLine) { nsCOMPtr selCont; GetSelectionController(getter_AddRefs(selCont)); NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE); switch (*aAction) { case eNextWord: { nsresult rv = selCont->WordExtendForDelete(true); // DeleteSelectionImpl doesn't handle these actions // because it's inside batching, so don't confuse it: *aAction = eNone; if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } case ePreviousWord: { nsresult rv = selCont->WordExtendForDelete(false); *aAction = eNone; if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } case eNext: { nsresult rv = selCont->CharacterExtendForDelete(); // Don't set aAction to eNone (see Bug 502259) if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } case ePrevious: { // Only extend the selection where the selection is after a UTF-16 // surrogate pair or a variation selector. // For other cases we don't want to do that, in order // to make sure that pressing backspace will only delete the last // typed character. EditorRawDOMPoint atStartOfSelection = EditorBase::GetStartPoint(aSelection); if (NS_WARN_IF(!atStartOfSelection.IsSet())) { return NS_ERROR_FAILURE; } // node might be anonymous DIV, so we find better text node EditorRawDOMPoint insertionPoint = FindBetterInsertionPoint(atStartOfSelection); if (insertionPoint.IsInTextNode()) { const nsTextFragment* data = insertionPoint.GetContainerAsText()->GetText(); uint32_t offset = insertionPoint.Offset(); if ((offset > 1 && NS_IS_LOW_SURROGATE(data->CharAt(offset - 1)) && NS_IS_HIGH_SURROGATE(data->CharAt(offset - 2))) || (offset > 0 && gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) { nsresult rv = selCont->CharacterExtendForBackspace(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } return NS_OK; } case eToBeginningOfLine: { // Try to move to end selCont->IntraLineMove(true, false); // Select to beginning nsresult rv = selCont->IntraLineMove(false, true); *aAction = eNone; if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } case eToEndOfLine: { nsresult rv = selCont->IntraLineMove(true, true); *aAction = eNext; if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } // For avoiding several compiler warnings default: return NS_OK; } } return NS_OK; } nsresult TextEditor::DeleteSelection(EDirection aAction, EStripWrappers aStripWrappers) { MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } // Protect the edit rules object from dying RefPtr rules(mRules); // delete placeholder txns merge. AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName); AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction); // pre-process RefPtr selection = GetSelection(); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); // If there is an existing selection when an extended delete is requested, // platforms that use "caret-style" caret positioning collapse the // selection to the start and then create a new selection. // Platforms that use "selection-style" caret positioning just delete the // existing selection without extending it. if (!selection->Collapsed() && (aAction == eNextWord || aAction == ePreviousWord || aAction == eToBeginningOfLine || aAction == eToEndOfLine)) { if (mCaretStyle == 1) { nsresult rv = selection->CollapseToStart(); NS_ENSURE_SUCCESS(rv, rv); } else { aAction = eNone; } } RulesInfo ruleInfo(EditAction::deleteSelection); ruleInfo.collapsedAction = aAction; ruleInfo.stripWrappers = aStripWrappers; bool cancel, handled; nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled); NS_ENSURE_SUCCESS(rv, rv); if (!cancel && !handled) { rv = DeleteSelectionImpl(aAction, aStripWrappers); } if (!cancel) { // post-process rv = rules->DidDoAction(selection, &ruleInfo, rv); } return rv; } NS_IMETHODIMP TextEditor::InsertText(const nsAString& aStringToInsert) { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } // Protect the edit rules object from dying RefPtr rules(mRules); EditAction opID = EditAction::insertText; if (ShouldHandleIMEComposition()) { opID = EditAction::insertIMEText; } AutoPlaceholderBatch batch(this, nullptr); AutoRules beginRulesSniffing(this, opID, nsIEditor::eNext); // pre-process RefPtr selection = GetSelection(); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); nsAutoString resultString; // XXX can we trust instring to outlive ruleInfo, // XXX and ruleInfo not to refer to instring in its dtor? //nsAutoString instring(aStringToInsert); RulesInfo ruleInfo(opID); ruleInfo.inString = &aStringToInsert; ruleInfo.outString = &resultString; ruleInfo.maxLength = mMaxTextLength; bool cancel, handled; nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled); NS_ENSURE_SUCCESS(rv, rv); if (!cancel && !handled) { // we rely on rules code for now - no default implementation } if (cancel) { return NS_OK; } // post-process return rules->DidDoAction(selection, &ruleInfo, rv); } NS_IMETHODIMP TextEditor::InsertLineBreak() { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } // Protect the edit rules object from dying RefPtr rules(mRules); AutoPlaceholderBatch beginBatching(this); AutoRules beginRulesSniffing(this, EditAction::insertBreak, nsIEditor::eNext); // pre-process RefPtr selection = GetSelection(); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); RulesInfo ruleInfo(EditAction::insertBreak); ruleInfo.maxLength = mMaxTextLength; bool cancel, handled; // XXX DidDoAction() won't be called when this returns error. Perhaps, // we should move the code between WillDoAction() and DidDoAction() // to a new method and guarantee that DidDoAction() is always called // after WillDoAction(). nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled); NS_ENSURE_SUCCESS(rv, rv); if (!cancel && !handled) { // get the (collapsed) selection location nsRange* firstRange = selection->GetRangeAt(0); if (NS_WARN_IF(!firstRange)) { return NS_ERROR_FAILURE; } EditorRawDOMPoint pointToInsert(firstRange->StartRef()); if (NS_WARN_IF(!pointToInsert.IsSet())) { return NS_ERROR_FAILURE; } MOZ_ASSERT(pointToInsert.IsSetAndValid()); // don't put text in places that can't have it if (!pointToInsert.IsInTextNode() && !CanContainTag(*pointToInsert.GetContainer(), *nsGkAtoms::textTagName)) { return NS_ERROR_FAILURE; } // we need to get the doc nsCOMPtr doc = GetDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); // don't change my selection in subtransactions AutoTransactionsConserveSelection dontChangeMySelection(this); // insert a linefeed character EditorRawDOMPoint pointAfterInsertedLineBreak; rv = InsertTextImpl(*doc, NS_LITERAL_STRING("\n"), pointToInsert, &pointAfterInsertedLineBreak); if (NS_WARN_IF(!pointAfterInsertedLineBreak.IsSet())) { rv = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called } if (NS_SUCCEEDED(rv)) { // set the selection to the correct location MOZ_ASSERT(!pointAfterInsertedLineBreak.GetChild(), "After inserting text into a text node, pointAfterInsertedLineBreak." "GetChild() should be nullptr"); rv = selection->Collapse(pointAfterInsertedLineBreak); if (NS_SUCCEEDED(rv)) { // see if we're at the end of the editor range EditorRawDOMPoint endPoint = GetEndPoint(selection); if (endPoint == pointAfterInsertedLineBreak) { // SetInterlinePosition(true) means we want the caret to stick to the // content on the "right". We want the caret to stick to whatever is // past the break. This is because the break is on the same line we // were on, but the next content will be on the following line. selection->SetInterlinePosition(true); } } } } if (!cancel) { // post-process, always called if WillInsertBreak didn't return cancel==true rv = rules->DidDoAction(selection, &ruleInfo, rv); } return rv; } NS_IMETHODIMP TextEditor::SetText(const nsAString& aString) { if (NS_WARN_IF(!mRules)) { return NS_ERROR_NOT_INITIALIZED; } // Protect the edit rules object from dying RefPtr rules(mRules); // delete placeholder txns merge. AutoPlaceholderBatch batch(this, nullptr); AutoRules beginRulesSniffing(this, EditAction::setText, nsIEditor::eNext); // pre-process RefPtr selection = GetSelection(); if (NS_WARN_IF(!selection)) { return NS_ERROR_NULL_POINTER; } RulesInfo ruleInfo(EditAction::setText); ruleInfo.inString = &aString; ruleInfo.maxLength = mMaxTextLength; bool cancel; bool handled; nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (cancel) { return NS_OK; } if (!handled) { // We want to select trailing BR node to remove all nodes to replace all, // but TextEditor::SelectEntireDocument doesn't select that BR node. if (rules->DocumentIsEmpty()) { // if it's empty, don't select entire doc - that would select // the bogus node Element* rootElement = GetRoot(); if (NS_WARN_IF(!rootElement)) { return NS_ERROR_FAILURE; } rv = selection->Collapse(rootElement, 0); } else { rv = EditorBase::SelectEntireDocument(selection); } if (NS_SUCCEEDED(rv)) { if (aString.IsEmpty()) { rv = DeleteSelection(eNone, eStrip); } else { rv = InsertText(aString); } } } // post-process return rules->DidDoAction(selection, &ruleInfo, rv); } nsresult TextEditor::BeginIMEComposition(WidgetCompositionEvent* aEvent) { NS_ENSURE_TRUE(!mComposition, NS_OK); if (IsPasswordEditor()) { NS_ENSURE_TRUE(mRules, NS_ERROR_NULL_POINTER); // Protect the edit rules object from dying RefPtr rules(mRules); rules->ResetIMETextPWBuf(); } return EditorBase::BeginIMEComposition(aEvent); } nsresult TextEditor::UpdateIMEComposition(WidgetCompositionEvent* aCompsitionChangeEvent) { MOZ_ASSERT(aCompsitionChangeEvent, "aCompsitionChangeEvent must not be nullptr"); if (NS_WARN_IF(!aCompsitionChangeEvent)) { return NS_ERROR_INVALID_ARG; } MOZ_ASSERT(aCompsitionChangeEvent->mMessage == eCompositionChange, "The event should be eCompositionChange"); if (!EnsureComposition(aCompsitionChangeEvent)) { return NS_OK; } nsCOMPtr ps = GetPresShell(); NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); RefPtr selection = GetSelection(); NS_ENSURE_STATE(selection); // NOTE: TextComposition should receive selection change notification before // CompositionChangeEventHandlingMarker notifies TextComposition of the // end of handling compositionchange event because TextComposition may // need to ignore selection changes caused by composition. Therefore, // CompositionChangeEventHandlingMarker must be destroyed after a call // of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or // NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies // TextComposition of a selection change. MOZ_ASSERT(!mPlaceholderBatch, "UpdateIMEComposition() must be called without place holder batch"); TextComposition::CompositionChangeEventHandlingMarker compositionChangeEventHandlingMarker(mComposition, aCompsitionChangeEvent); RefPtr caretP = ps->GetCaret(); nsresult rv; { AutoPlaceholderBatch batch(this, nsGkAtoms::IMETxnName); MOZ_ASSERT(mIsInEditAction, "AutoPlaceholderBatch should've notified the observes of before-edit"); rv = InsertText(aCompsitionChangeEvent->mData); if (caretP) { caretP->SetSelection(selection); } } // If still composing, we should fire input event via observer. // Note that if the composition will be committed by the following // compositionend event, we don't need to notify editor observes of this // change. // NOTE: We must notify after the auto batch will be gone. if (!aCompsitionChangeEvent->IsFollowedByCompositionEnd()) { NotifyEditorObservers(eNotifyEditorObserversOfEnd); } return rv; } already_AddRefed TextEditor::GetInputEventTargetContent() { nsCOMPtr target = do_QueryInterface(mEventTarget); return target.forget(); } nsresult TextEditor::DocumentIsEmpty(bool* aIsEmpty) { NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED); if (mRules->HasBogusNode()) { *aIsEmpty = true; return NS_OK; } // Even if there is no bogus node, we should be detected as empty document // if all the children are text nodes and these have no content. Element* rootElement = GetRoot(); if (!rootElement) { *aIsEmpty = true; return NS_OK; } for (nsIContent* child = rootElement->GetFirstChild(); child; child = child->GetNextSibling()) { if (!EditorBase::IsTextNode(child) || static_cast(child)->TextDataLength()) { *aIsEmpty = false; return NS_OK; } } *aIsEmpty = true; return NS_OK; } NS_IMETHODIMP TextEditor::GetDocumentIsEmpty(bool* aDocumentIsEmpty) { return DocumentIsEmpty(aDocumentIsEmpty); } NS_IMETHODIMP TextEditor::GetTextLength(int32_t* aCount) { NS_ASSERTION(aCount, "null pointer"); // initialize out params *aCount = 0; // special-case for empty document, to account for the bogus node bool docEmpty; nsresult rv = GetDocumentIsEmpty(&docEmpty); NS_ENSURE_SUCCESS(rv, rv); if (docEmpty) { return NS_OK; } dom::Element *rootElement = GetRoot(); NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); nsCOMPtr iter = do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv); NS_ENSURE_SUCCESS(rv, rv); uint32_t totalLength = 0; iter->Init(rootElement); for (; !iter->IsDone(); iter->Next()) { nsCOMPtr currentNode = iter->GetCurrentNode(); if (IsTextNode(currentNode) && IsEditable(currentNode)) { totalLength += currentNode->Length(); } } *aCount = totalLength; return NS_OK; } NS_IMETHODIMP TextEditor::SetMaxTextLength(int32_t aMaxTextLength) { mMaxTextLength = aMaxTextLength; return NS_OK; } NS_IMETHODIMP TextEditor::GetMaxTextLength(int32_t* aMaxTextLength) { // NOTE: If you need to override this method, you need to make // MaxTextLength() virtual. if (NS_WARN_IF(!aMaxTextLength)) { return NS_ERROR_INVALID_POINTER; } *aMaxTextLength = MaxTextLength(); return NS_OK; } NS_IMETHODIMP TextEditor::GetWrapWidth(int32_t* aWrapColumn) { NS_ENSURE_TRUE( aWrapColumn, NS_ERROR_NULL_POINTER); *aWrapColumn = mWrapColumn; return NS_OK; } // // See if the style value includes this attribute, and if it does, // cut out everything from the attribute to the next semicolon. // static void CutStyle(const char* stylename, nsString& styleValue) { // Find the current wrapping type: int32_t styleStart = styleValue.Find(stylename, true); if (styleStart >= 0) { int32_t styleEnd = styleValue.Find(";", false, styleStart); if (styleEnd > styleStart) { styleValue.Cut(styleStart, styleEnd - styleStart + 1); } else { styleValue.Cut(styleStart, styleValue.Length() - styleStart); } } } NS_IMETHODIMP TextEditor::SetWrapWidth(int32_t aWrapColumn) { SetWrapColumn(aWrapColumn); // Make sure we're a plaintext editor, otherwise we shouldn't // do the rest of this. if (!IsPlaintextEditor()) { return NS_OK; } // Ought to set a style sheet here ... // Probably should keep around an mPlaintextStyleSheet for this purpose. dom::Element *rootElement = GetRoot(); NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); // Get the current style for this root element: nsAutoString styleValue; rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue); // We'll replace styles for these values: CutStyle("white-space", styleValue); CutStyle("width", styleValue); CutStyle("font-family", styleValue); // If we have other style left, trim off any existing semicolons // or whitespace, then add a known semicolon-space: if (!styleValue.IsEmpty()) { styleValue.Trim("; \t", false, true); styleValue.AppendLiteral("; "); } // Make sure we have fixed-width font. This should be done for us, // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;". // Only do this if we're wrapping. if (IsWrapHackEnabled() && aWrapColumn >= 0) { styleValue.AppendLiteral("font-family: -moz-fixed; "); } // and now we're ready to set the new whitespace/wrapping style. if (aWrapColumn > 0) { // Wrap to a fixed column. styleValue.AppendLiteral("white-space: pre-wrap; width: "); styleValue.AppendInt(aWrapColumn); styleValue.AppendLiteral("ch;"); } else if (!aWrapColumn) { styleValue.AppendLiteral("white-space: pre-wrap;"); } else { styleValue.AppendLiteral("white-space: pre;"); } return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue, true); } NS_IMETHODIMP TextEditor::SetWrapColumn(int32_t aWrapColumn) { mWrapColumn = aWrapColumn; return NS_OK; } NS_IMETHODIMP TextEditor::GetNewlineHandling(int32_t* aNewlineHandling) { NS_ENSURE_ARG_POINTER(aNewlineHandling); *aNewlineHandling = mNewlineHandling; return NS_OK; } NS_IMETHODIMP TextEditor::SetNewlineHandling(int32_t aNewlineHandling) { mNewlineHandling = aNewlineHandling; return NS_OK; } NS_IMETHODIMP TextEditor::Undo(uint32_t aCount) { // Protect the edit rules object from dying RefPtr rules(mRules); AutoUpdateViewBatch beginViewBatching(this); CommitComposition(); NotifyEditorObservers(eNotifyEditorObserversOfBefore); AutoRules beginRulesSniffing(this, EditAction::undo, nsIEditor::eNone); RulesInfo ruleInfo(EditAction::undo); RefPtr selection = GetSelection(); bool cancel, handled; nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (!cancel && NS_SUCCEEDED(rv)) { rv = EditorBase::Undo(aCount); rv = rules->DidDoAction(selection, &ruleInfo, rv); } NotifyEditorObservers(eNotifyEditorObserversOfEnd); return rv; } NS_IMETHODIMP TextEditor::Redo(uint32_t aCount) { // Protect the edit rules object from dying RefPtr rules(mRules); AutoUpdateViewBatch beginViewBatching(this); CommitComposition(); NotifyEditorObservers(eNotifyEditorObserversOfBefore); AutoRules beginRulesSniffing(this, EditAction::redo, nsIEditor::eNone); RulesInfo ruleInfo(EditAction::redo); RefPtr selection = GetSelection(); bool cancel, handled; nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (!cancel && NS_SUCCEEDED(rv)) { rv = EditorBase::Redo(aCount); rv = rules->DidDoAction(selection, &ruleInfo, rv); } NotifyEditorObservers(eNotifyEditorObserversOfEnd); return rv; } bool TextEditor::CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed) { RefPtr selection = GetSelection(); if (!selection) { return false; } if (aPasswordFieldAllowed == ePasswordFieldNotAllowed && IsPasswordEditor()) { return false; } return !selection->Collapsed(); } bool TextEditor::FireClipboardEvent(EventMessage aEventMessage, int32_t aSelectionType, bool* aActionTaken) { if (aEventMessage == ePaste) { CommitComposition(); } nsCOMPtr presShell = GetPresShell(); NS_ENSURE_TRUE(presShell, false); RefPtr selection = GetSelection(); if (!selection) { return false; } if (!nsCopySupport::FireClipboardEvent(aEventMessage, aSelectionType, presShell, selection, aActionTaken)) { return false; } // If the event handler caused the editor to be destroyed, return false. // Otherwise return true to indicate that the event was not cancelled. return !mDidPreDestroy; } NS_IMETHODIMP TextEditor::Cut() { bool actionTaken = false; if (FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) { DeleteSelection(eNone, eStrip); } return actionTaken ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP TextEditor::CanCut(bool* aCanCut) { NS_ENSURE_ARG_POINTER(aCanCut); // Cut is always enabled in HTML documents nsCOMPtr doc = GetDocument(); *aCanCut = (doc && doc->IsHTMLOrXHTML()) || (IsModifiable() && CanCutOrCopy(ePasswordFieldNotAllowed)); return NS_OK; } NS_IMETHODIMP TextEditor::Copy() { bool actionTaken = false; FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, &actionTaken); return actionTaken ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP TextEditor::CanCopy(bool* aCanCopy) { NS_ENSURE_ARG_POINTER(aCanCopy); // Copy is always enabled in HTML documents nsCOMPtr doc = GetDocument(); *aCanCopy = (doc && doc->IsHTMLOrXHTML()) || CanCutOrCopy(ePasswordFieldNotAllowed); return NS_OK; } NS_IMETHODIMP TextEditor::CanDelete(bool* aCanDelete) { NS_ENSURE_ARG_POINTER(aCanDelete); *aCanDelete = IsModifiable() && CanCutOrCopy(ePasswordFieldAllowed); return NS_OK; } // Shared between OutputToString and OutputToStream already_AddRefed TextEditor::GetAndInitDocEncoder(const nsAString& aFormatType, uint32_t aFlags, const nsACString& aCharset) { nsCOMPtr docEncoder; if (!mCachedDocumentEncoder || !mCachedDocumentEncoderType.Equals(aFormatType)) { nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE); LossyAppendUTF16toASCII(aFormatType, formatType); docEncoder = do_CreateInstance(formatType.get()); if (NS_WARN_IF(!docEncoder)) { return nullptr; } mCachedDocumentEncoder = docEncoder; mCachedDocumentEncoderType = aFormatType; } else { docEncoder = mCachedDocumentEncoder; } nsCOMPtr doc = GetDocument(); NS_ASSERTION(doc, "Need a document"); nsresult rv = docEncoder->NativeInit( doc, aFormatType, aFlags | nsIDocumentEncoder::RequiresReinitAfterOutput); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) { docEncoder->SetCharset(aCharset); } int32_t wc; (void) GetWrapWidth(&wc); if (wc >= 0) { (void) docEncoder->SetWrapColumn(wc); } // Set the selection, if appropriate. // We do this either if the OutputSelectionOnly flag is set, // in which case we use our existing selection ... if (aFlags & nsIDocumentEncoder::OutputSelectionOnly) { RefPtr selection = GetSelection(); if (NS_WARN_IF(!selection)) { return nullptr; } rv = docEncoder->SetSelection(selection); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } } // ... or if the root element is not a body, // in which case we set the selection to encompass the root. else { dom::Element* rootElement = GetRoot(); if (NS_WARN_IF(!rootElement)) { return nullptr; } if (!rootElement->IsHTMLElement(nsGkAtoms::body)) { rv = docEncoder->SetNativeContainerNode(rootElement); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } } } return docEncoder.forget(); } NS_IMETHODIMP TextEditor::OutputToString(const nsAString& aFormatType, uint32_t aFlags, nsAString& aOutputString) { // Protect the edit rules object from dying RefPtr rules(mRules); nsString resultString; RulesInfo ruleInfo(EditAction::outputText); ruleInfo.outString = &resultString; ruleInfo.flags = aFlags; // XXX Struct should store a nsAReadable* nsAutoString str(aFormatType); ruleInfo.outputFormat = &str; bool cancel, handled; nsresult rv = rules->WillDoAction(nullptr, &ruleInfo, &cancel, &handled); if (cancel || NS_FAILED(rv)) { return rv; } if (handled) { // This case will get triggered by password fields. aOutputString.Assign(*(ruleInfo.outString)); return rv; } nsAutoCString charsetStr; rv = GetDocumentCharacterSet(charsetStr); if (NS_FAILED(rv) || charsetStr.IsEmpty()) { charsetStr.AssignLiteral("windows-1252"); } nsCOMPtr encoder = GetAndInitDocEncoder(aFormatType, aFlags, charsetStr); if (NS_WARN_IF(!encoder)) { return NS_ERROR_FAILURE; } return encoder->EncodeToString(aOutputString); } NS_IMETHODIMP TextEditor::OutputToStream(nsIOutputStream* aOutputStream, const nsAString& aFormatType, const nsACString& aCharset, uint32_t aFlags) { nsresult rv; // special-case for empty document when requesting plain text, // to account for the bogus text node. // XXX Should there be a similar test in OutputToString? if (aFormatType.EqualsLiteral("text/plain")) { bool docEmpty; rv = GetDocumentIsEmpty(&docEmpty); NS_ENSURE_SUCCESS(rv, rv); if (docEmpty) { return NS_OK; // Output nothing. } } nsCOMPtr encoder = GetAndInitDocEncoder(aFormatType, aFlags, aCharset); if (NS_WARN_IF(!encoder)) { return NS_ERROR_FAILURE; } return encoder->EncodeToStream(aOutputStream); } NS_IMETHODIMP TextEditor::InsertTextWithQuotations(const nsAString& aStringToInsert) { return InsertText(aStringToInsert); } NS_IMETHODIMP TextEditor::PasteAsQuotation(int32_t aSelectionType) { // Get Clipboard Service nsresult rv; nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); // Get the nsITransferable interface for getting the data from the clipboard nsCOMPtr trans; rv = PrepareTransferable(getter_AddRefs(trans)); if (NS_SUCCEEDED(rv) && trans) { // Get the Data from the clipboard clipboard->GetData(trans, aSelectionType); // Now we ask the transferable for the data // it still owns the data, we just have a pointer to it. // If it can't support a "text" output of the data the call will fail nsCOMPtr genericDataObj; uint32_t len; nsAutoCString flav; rv = trans->GetAnyTransferData(flav, getter_AddRefs(genericDataObj), &len); if (NS_FAILED(rv)) { return rv; } if (flav.EqualsLiteral(kUnicodeMime) || flav.EqualsLiteral(kMozTextInternal)) { nsCOMPtr textDataObj ( do_QueryInterface(genericDataObj) ); if (textDataObj && len > 0) { nsAutoString stuffToPaste; textDataObj->GetData ( stuffToPaste ); AutoPlaceholderBatch beginBatching(this); rv = InsertAsQuotation(stuffToPaste, 0); } } } return rv; } NS_IMETHODIMP TextEditor::InsertAsQuotation(const nsAString& aQuotedText, nsIDOMNode** aNodeInserted) { // Protect the edit rules object from dying RefPtr rules(mRules); // Let the citer quote it for us: nsString quotedStuff; nsresult rv = InternetCiter::GetCiteString(aQuotedText, quotedStuff); NS_ENSURE_SUCCESS(rv, rv); // It's best to put a blank line after the quoted text so that mails // written without thinking won't be so ugly. if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) { quotedStuff.Append(char16_t('\n')); } // get selection RefPtr selection = GetSelection(); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); AutoPlaceholderBatch beginBatching(this); AutoRules beginRulesSniffing(this, EditAction::insertText, nsIEditor::eNext); // give rules a chance to handle or cancel RulesInfo ruleInfo(EditAction::insertElement); bool cancel, handled; rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled); NS_ENSURE_SUCCESS(rv, rv); if (cancel) { return NS_OK; // Rules canceled the operation. } if (!handled) { rv = InsertText(quotedStuff); // XXX Should set *aNodeInserted to the first node inserted if (aNodeInserted && NS_SUCCEEDED(rv)) { *aNodeInserted = nullptr; } } return rv; } NS_IMETHODIMP TextEditor::PasteAsCitedQuotation(const nsAString& aCitation, int32_t aSelectionType) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP TextEditor::InsertAsCitedQuotation(const nsAString& aQuotedText, const nsAString& aCitation, bool aInsertHTML, nsIDOMNode** aNodeInserted) { return InsertAsQuotation(aQuotedText, aNodeInserted); } nsresult TextEditor::SharedOutputString(uint32_t aFlags, bool* aIsCollapsed, nsAString& aResult) { RefPtr selection = GetSelection(); NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); *aIsCollapsed = selection->Collapsed(); if (!*aIsCollapsed) { aFlags |= nsIDocumentEncoder::OutputSelectionOnly; } // If the selection isn't collapsed, we'll use the whole document. return OutputToString(NS_LITERAL_STRING("text/plain"), aFlags, aResult); } NS_IMETHODIMP TextEditor::Rewrap(bool aRespectNewlines) { int32_t wrapCol; nsresult rv = GetWrapWidth(&wrapCol); NS_ENSURE_SUCCESS(rv, NS_OK); // Rewrap makes no sense if there's no wrap column; default to 72. if (wrapCol <= 0) { wrapCol = 72; } nsAutoString current; bool isCollapsed; rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted | nsIDocumentEncoder::OutputLFLineBreak, &isCollapsed, current); NS_ENSURE_SUCCESS(rv, rv); nsString wrapped; uint32_t firstLineOffset = 0; // XXX need to reset this if there is a selection rv = InternetCiter::Rewrap(current, wrapCol, firstLineOffset, aRespectNewlines, wrapped); NS_ENSURE_SUCCESS(rv, rv); if (isCollapsed) { SelectAll(); } return InsertTextWithQuotations(wrapped); } NS_IMETHODIMP TextEditor::StripCites() { nsAutoString current; bool isCollapsed; nsresult rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted, &isCollapsed, current); NS_ENSURE_SUCCESS(rv, rv); nsString stripped; rv = InternetCiter::StripCites(current, stripped); NS_ENSURE_SUCCESS(rv, rv); if (isCollapsed) { rv = SelectAll(); NS_ENSURE_SUCCESS(rv, rv); } return InsertText(stripped); } NS_IMETHODIMP TextEditor::GetEmbeddedObjects(nsIArray** aNodeList) { if (NS_WARN_IF(!aNodeList)) { return NS_ERROR_INVALID_ARG; } *aNodeList = nullptr; return NS_OK; } /** * All editor operations which alter the doc should be prefaced * with a call to StartOperation, naming the action and direction. */ NS_IMETHODIMP TextEditor::StartOperation(EditAction opID, nsIEditor::EDirection aDirection) { // Protect the edit rules object from dying RefPtr rules(mRules); EditorBase::StartOperation(opID, aDirection); // will set mAction, mDirection if (rules) { return rules->BeforeEdit(mAction, mDirection); } return NS_OK; } /** * All editor operations which alter the doc should be followed * with a call to EndOperation. */ NS_IMETHODIMP TextEditor::EndOperation() { // Protect the edit rules object from dying RefPtr rules(mRules); // post processing nsresult rv = rules ? rules->AfterEdit(mAction, mDirection) : NS_OK; EditorBase::EndOperation(); // will clear mAction, mDirection return rv; } nsresult TextEditor::SelectEntireDocument(Selection* aSelection) { if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } // Protect the edit rules object from dying RefPtr rules(mRules); // is doc empty? if (rules->DocumentIsEmpty()) { // get root node nsCOMPtr rootElement = do_QueryInterface(GetRoot()); NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE); // if it's empty don't select entire doc - that would select the bogus node return aSelection->Collapse(rootElement, 0); } SelectionBatcher selectionBatcher(aSelection); nsresult rv = EditorBase::SelectEntireDocument(aSelection); NS_ENSURE_SUCCESS(rv, rv); // Don't select the trailing BR node if we have one nsCOMPtr childNode; rv = GetEndChildNode(aSelection, getter_AddRefs(childNode)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (childNode) { childNode = childNode->GetPreviousSibling(); } if (childNode && TextEditUtils::IsMozBR(childNode)) { int32_t parentOffset; nsINode* parentNode = GetNodeLocation(childNode, &parentOffset); return aSelection->Extend(parentNode, parentOffset); } return NS_OK; } EventTarget* TextEditor::GetDOMEventTarget() { return mEventTarget; } nsresult TextEditor::SetAttributeOrEquivalent(Element* aElement, nsAtom* aAttribute, const nsAString& aValue, bool aSuppressTransaction) { return EditorBase::SetAttribute(aElement, aAttribute, aValue); } nsresult TextEditor::RemoveAttributeOrEquivalent(Element* aElement, nsAtom* aAttribute, bool aSuppressTransaction) { return EditorBase::RemoveAttribute(aElement, aAttribute); } } // namespace mozilla