diff -r 000000000000 -r 6474c204b198 editor/libeditor/text/nsPlaintextEditor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor/libeditor/text/nsPlaintextEditor.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1614 @@ +/* -*- 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/Assertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Element.h" +#include "mozilla/mozalloc.h" +#include "nsAString.h" +#include "nsAutoPtr.h" +#include "nsCRT.h" +#include "nsCaret.h" +#include "nsCharTraits.h" +#include "nsComponentManagerUtils.h" +#include "nsContentCID.h" +#include "nsCopySupport.h" +#include "nsDebug.h" +#include "nsDependentSubstring.h" +#include "nsEditRules.h" +#include "nsEditorUtils.h" // nsAutoEditBatch, nsAutoRules +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsIClipboard.h" +#include "nsIContent.h" +#include "nsIContentIterator.h" +#include "nsIDOMCharacterData.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMKeyEvent.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsIDocumentEncoder.h" +#include "nsIEditorIMESupport.h" +#include "nsNameSpaceManager.h" +#include "nsINode.h" +#include "nsIPresShell.h" +#include "nsISelection.h" +#include "nsISelectionController.h" +#include "nsISelectionPrivate.h" +#include "nsISupportsPrimitives.h" +#include "nsITransferable.h" +#include "nsIWeakReferenceUtils.h" +#include "nsInternetCiter.h" +#include "nsLiteralString.h" +#include "nsPlaintextEditor.h" +#include "nsReadableUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsSubstringTuple.h" +#include "nsTextEditRules.h" +#include "nsTextEditUtils.h" +#include "nsUnicharUtils.h" +#include "nsXPCOM.h" + +class nsIOutputStream; +class nsISupports; +class nsISupportsArray; + +using namespace mozilla; +using namespace mozilla::dom; + +nsPlaintextEditor::nsPlaintextEditor() +: nsEditor() +, mRules(nullptr) +, mWrapToWindow(false) +, 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); +} + +nsPlaintextEditor::~nsPlaintextEditor() +{ + // 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(nsPlaintextEditor) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPlaintextEditor, nsEditor) + if (tmp->mRules) + tmp->mRules->DetachEditor(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRules) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPlaintextEditor, nsEditor) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRules) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ADDREF_INHERITED(nsPlaintextEditor, nsEditor) +NS_IMPL_RELEASE_INHERITED(nsPlaintextEditor, nsEditor) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsPlaintextEditor) + NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor) + NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport) +NS_INTERFACE_MAP_END_INHERITING(nsEditor) + + +NS_IMETHODIMP nsPlaintextEditor::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); + + nsresult res = NS_OK, rulesRes = NS_OK; + if (mRules) { + mRules->DetachEditor(); + } + + { + // block to scope nsAutoEditInitRulesTrigger + nsAutoEditInitRulesTrigger rulesTrigger(this, rulesRes); + + // Init the base editor + res = nsEditor::Init(aDoc, aRoot, aSelCon, aFlags, aInitialValue); + } + + NS_ENSURE_SUCCESS(rulesRes, rulesRes); + + // mRules may not have been initialized yet, when this is called via + // nsHTMLEditor::Init. + if (mRules) { + mRules->SetInitialValue(aInitialValue); + } + + return res; +} + +static int32_t sNewlineHandlingPref = -1, + sCaretStylePref = -1; + +static void +EditorPrefsChangedCallback(const char *aPrefName, void *) +{ + if (nsCRT::strcmp(aPrefName, "editor.singleLine.pasteNewlines") == 0) { + sNewlineHandlingPref = + Preferences::GetInt("editor.singleLine.pasteNewlines", + nsIPlaintextEditor::eNewlinesPasteToFirst); + } else if (nsCRT::strcmp(aPrefName, "layout.selection.caret_style") == 0) { + sCaretStylePref = Preferences::GetInt("layout.selection.caret_style", +#ifdef XP_WIN + 1); + if (sCaretStylePref == 0) + sCaretStylePref = 1; +#else + 0); +#endif + } +} + +// static +void +nsPlaintextEditor::GetDefaultEditorPrefs(int32_t &aNewlineHandling, + int32_t &aCaretStyle) +{ + if (sNewlineHandlingPref == -1) { + Preferences::RegisterCallback(EditorPrefsChangedCallback, + "editor.singleLine.pasteNewlines"); + EditorPrefsChangedCallback("editor.singleLine.pasteNewlines", nullptr); + Preferences::RegisterCallback(EditorPrefsChangedCallback, + "layout.selection.caret_style"); + EditorPrefsChangedCallback("layout.selection.caret_style", nullptr); + } + + aNewlineHandling = sNewlineHandlingPref; + aCaretStyle = sCaretStylePref; +} + +void +nsPlaintextEditor::BeginEditorInit() +{ + mInitTriggerCounter++; +} + +nsresult +nsPlaintextEditor::EndEditorInit() +{ + nsresult res = NS_OK; + NS_PRECONDITION(mInitTriggerCounter > 0, "ended editor init before we began?"); + mInitTriggerCounter--; + if (mInitTriggerCounter == 0) + { + res = InitRules(); + if (NS_SUCCEEDED(res)) { + // Throw away the old transaction manager if this is not the first time that + // we're initializing the editor. + EnableUndo(false); + EnableUndo(true); + } + } + return res; +} + +NS_IMETHODIMP +nsPlaintextEditor::SetDocumentCharacterSet(const nsACString& characterSet) +{ + nsresult rv = nsEditor::SetDocumentCharacterSet(characterSet); + NS_ENSURE_SUCCESS(rv, rv); + + // Update META charset element. + nsCOMPtr domdoc = GetDOMDocument(); + NS_ENSURE_TRUE(domdoc, NS_ERROR_NOT_INITIALIZED); + + if (UpdateMetaCharset(domdoc, characterSet)) { + return NS_OK; + } + + nsCOMPtr headList; + rv = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(headList)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(headList, NS_OK); + + nsCOMPtr headNode; + headList->Item(0, getter_AddRefs(headNode)); + NS_ENSURE_TRUE(headNode, NS_OK); + + // Create a new meta charset tag + nsCOMPtr resultNode; + rv = CreateNode(NS_LITERAL_STRING("meta"), headNode, 0, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(resultNode, NS_OK); + + // Set attributes to the created element + if (characterSet.IsEmpty()) { + return NS_OK; + } + + nsCOMPtr metaElement = do_QueryInterface(resultNode); + if (!metaElement) { + 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 +nsPlaintextEditor::UpdateMetaCharset(nsIDOMDocument* aDocument, + const nsACString& aCharacterSet) +{ + MOZ_ASSERT(aDocument); + // get a list of META tags + nsCOMPtr list; + nsresult rv = aDocument->GetElementsByTagName(NS_LITERAL_STRING("meta"), + getter_AddRefs(list)); + NS_ENSURE_SUCCESS(rv, false); + NS_ENSURE_TRUE(list, false); + + nsCOMPtr metaList = do_QueryInterface(list); + + uint32_t listLength = 0; + metaList->GetLength(&listLength); + + for (uint32_t i = 0; i < listLength; ++i) { + nsCOMPtr metaNode = metaList->Item(i); + MOZ_ASSERT(metaNode); + + if (!metaNode->IsElement()) { + continue; + } + + nsAutoString currentValue; + metaNode->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, currentValue); + + if (!FindInReadable(NS_LITERAL_STRING("content-type"), + currentValue, + nsCaseInsensitiveStringComparator())) { + continue; + } + + metaNode->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 + nsCOMPtr metaElement = do_QueryInterface(metaNode); + MOZ_ASSERT(metaElement); + rv = nsEditor::SetAttribute(metaElement, NS_LITERAL_STRING("content"), + Substring(originalStart, start) + + charsetEquals + + NS_ConvertASCIItoUTF16(aCharacterSet)); + return NS_SUCCEEDED(rv); + } + return false; +} + +NS_IMETHODIMP nsPlaintextEditor::InitRules() +{ + if (!mRules) { + // instantiate the rules for this text editor + mRules = new nsTextEditRules(); + } + return mRules->Init(this); +} + + +NS_IMETHODIMP +nsPlaintextEditor::GetIsDocumentEditable(bool *aIsDocumentEditable) +{ + NS_ENSURE_ARG_POINTER(aIsDocumentEditable); + + nsCOMPtr doc = GetDOMDocument(); + *aIsDocumentEditable = doc && IsModifiable(); + + return NS_OK; +} + +bool nsPlaintextEditor::IsModifiable() +{ + return !IsReadonly(); +} + +nsresult +nsPlaintextEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent) +{ + // NOTE: When you change this method, you should also change: + // * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html + // * editor/libeditor/html/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 nsEditor. + return nsEditor::HandleKeyPressEvent(aKeyEvent); + } + + WidgetKeyboardEvent* nativeKeyEvent = + aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); + NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED); + NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS, + "HandleKeyPressEvent gets non-keypress event"); + + switch (nativeKeyEvent->keyCode) { + case nsIDOMKeyEvent::DOM_VK_META: + case nsIDOMKeyEvent::DOM_VK_WIN: + case nsIDOMKeyEvent::DOM_VK_SHIFT: + case nsIDOMKeyEvent::DOM_VK_CONTROL: + case nsIDOMKeyEvent::DOM_VK_ALT: + case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: + case nsIDOMKeyEvent::DOM_VK_DELETE: + // These keys are handled on nsEditor + return nsEditor::HandleKeyPressEvent(aKeyEvent); + case nsIDOMKeyEvent::DOM_VK_TAB: { + if (IsTabbable()) { + return NS_OK; // let it be used for focus switching + } + + if (nativeKeyEvent->IsShift() || nativeKeyEvent->IsControl() || + nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || + nativeKeyEvent->IsOS()) { + return NS_OK; + } + + // else we insert the tab straight through + aKeyEvent->PreventDefault(); + return TypedText(NS_LITERAL_STRING("\t"), eTypedText); + } + case nsIDOMKeyEvent::DOM_VK_RETURN: + if (IsSingleLineEditor() || nativeKeyEvent->IsControl() || + nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || + nativeKeyEvent->IsOS()) { + return NS_OK; + } + aKeyEvent->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 (nativeKeyEvent->charCode == 0 || nativeKeyEvent->IsControl() || + nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || + nativeKeyEvent->IsOS()) { + // we don't PreventDefault() here or keybindings like control-x won't work + return NS_OK; + } + aKeyEvent->PreventDefault(); + nsAutoString str(nativeKeyEvent->charCode); + 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 +nsPlaintextEditor::TypedText(const nsAString& aString, ETypingAction aAction) +{ + nsAutoPlaceHolderBatch 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; + } +} + +nsresult +nsPlaintextEditor::CreateBRImpl(nsCOMPtr* aInOutParent, + int32_t* aInOutOffset, + nsCOMPtr* outBRNode, + EDirection aSelect) +{ + NS_ENSURE_TRUE(aInOutParent && *aInOutParent && aInOutOffset && outBRNode, NS_ERROR_NULL_POINTER); + *outBRNode = nullptr; + nsresult res; + + // we need to insert a br. unfortunately, we may have to split a text node to do it. + nsCOMPtr node = *aInOutParent; + int32_t theOffset = *aInOutOffset; + nsCOMPtr nodeAsText = do_QueryInterface(node); + NS_NAMED_LITERAL_STRING(brType, "br"); + nsCOMPtr brNode; + if (nodeAsText) + { + int32_t offset; + uint32_t len; + nodeAsText->GetLength(&len); + nsCOMPtr tmp = GetNodeLocation(node, &offset); + NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); + if (!theOffset) + { + // we are already set to go + } + else if (theOffset == (int32_t)len) + { + // update offset to point AFTER the text node + offset++; + } + else + { + // split the text node + res = SplitNode(node, theOffset, getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(res, res); + tmp = GetNodeLocation(node, &offset); + } + // create br + res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode)); + NS_ENSURE_SUCCESS(res, res); + *aInOutParent = tmp; + *aInOutOffset = offset+1; + } + else + { + res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode)); + NS_ENSURE_SUCCESS(res, res); + (*aInOutOffset)++; + } + + *outBRNode = brNode; + if (*outBRNode && (aSelect != eNone)) + { + int32_t offset; + nsCOMPtr parent = GetNodeLocation(*outBRNode, &offset); + + nsCOMPtr selection; + res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + nsCOMPtr selPriv(do_QueryInterface(selection)); + if (aSelect == eNext) + { + // position selection after br + selPriv->SetInterlinePosition(true); + res = selection->Collapse(parent, offset+1); + } + else if (aSelect == ePrevious) + { + // position selection before br + selPriv->SetInterlinePosition(true); + res = selection->Collapse(parent, offset); + } + } + return NS_OK; +} + + +NS_IMETHODIMP nsPlaintextEditor::CreateBR(nsIDOMNode *aNode, int32_t aOffset, nsCOMPtr *outBRNode, EDirection aSelect) +{ + nsCOMPtr parent = aNode; + int32_t offset = aOffset; + return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect); +} + +nsresult +nsPlaintextEditor::InsertBR(nsCOMPtr* outBRNode) +{ + NS_ENSURE_TRUE(outBRNode, NS_ERROR_NULL_POINTER); + *outBRNode = nullptr; + + // calling it text insertion to trigger moz br treatment by rules + nsAutoRules beginRulesSniffing(this, EditAction::insertText, nsIEditor::eNext); + + nsCOMPtr selection; + nsresult res = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(res, res); + + if (!selection->Collapsed()) { + res = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); + NS_ENSURE_SUCCESS(res, res); + } + + nsCOMPtr selNode; + int32_t selOffset; + res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); + NS_ENSURE_SUCCESS(res, res); + + res = CreateBR(selNode, selOffset, outBRNode); + NS_ENSURE_SUCCESS(res, res); + + // position selection after br + selNode = GetNodeLocation(*outBRNode, &selOffset); + nsCOMPtr selPriv(do_QueryInterface(selection)); + selPriv->SetInterlinePosition(true); + return selection->Collapse(selNode, selOffset+1); +} + +nsresult +nsPlaintextEditor::ExtendSelectionForDelete(nsISelection *aSelection, + nsIEditor::EDirection *aAction) +{ + nsresult result = NS_OK; + + 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: + result = selCont->WordExtendForDelete(true); + // DeleteSelectionImpl doesn't handle these actions + // because it's inside batching, so don't confuse it: + *aAction = eNone; + break; + case ePreviousWord: + result = selCont->WordExtendForDelete(false); + *aAction = eNone; + break; + case eNext: + result = selCont->CharacterExtendForDelete(); + // Don't set aAction to eNone (see Bug 502259) + break; + case ePrevious: { + // Only extend the selection where the selection is after a UTF-16 + // surrogate pair. 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. + nsCOMPtr node; + int32_t offset; + result = GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); + NS_ENSURE_SUCCESS(result, result); + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + + if (IsTextNode(node)) { + nsCOMPtr charData = do_QueryInterface(node); + if (charData) { + nsAutoString data; + result = charData->GetData(data); + NS_ENSURE_SUCCESS(result, result); + + if (offset > 1 && + NS_IS_LOW_SURROGATE(data[offset - 1]) && + NS_IS_HIGH_SURROGATE(data[offset - 2])) { + result = selCont->CharacterExtendForBackspace(); + } + } + } + break; + } + case eToBeginningOfLine: + selCont->IntraLineMove(true, false); // try to move to end + result = selCont->IntraLineMove(false, true); // select to beginning + *aAction = eNone; + break; + case eToEndOfLine: + result = selCont->IntraLineMove(true, true); + *aAction = eNext; + break; + default: // avoid several compiler warnings + result = NS_OK; + break; + } + } + return result; +} + +nsresult +nsPlaintextEditor::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 + nsCOMPtr kungFuDeathGrip(mRules); + + nsresult result; + + // delete placeholder txns merge. + nsAutoPlaceHolderBatch batch(this, nsGkAtoms::DeleteTxnName); + nsAutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction); + + // pre-process + nsRefPtr 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) + { + result = selection->CollapseToStart(); + NS_ENSURE_SUCCESS(result, result); + } + else + { + aAction = eNone; + } + } + + nsTextRulesInfo ruleInfo(EditAction::deleteSelection); + ruleInfo.collapsedAction = aAction; + ruleInfo.stripWrappers = aStripWrappers; + bool cancel, handled; + result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); + NS_ENSURE_SUCCESS(result, result); + if (!cancel && !handled) + { + result = DeleteSelectionImpl(aAction, aStripWrappers); + } + if (!cancel) + { + // post-process + result = mRules->DidDoAction(selection, &ruleInfo, result); + } + + return result; +} + +NS_IMETHODIMP nsPlaintextEditor::InsertText(const nsAString &aStringToInsert) +{ + if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } + + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + EditAction opID = EditAction::insertText; + if (mComposition) { + opID = EditAction::insertIMEText; + } + nsAutoPlaceHolderBatch batch(this, nullptr); + nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext); + + // pre-process + nsRefPtr 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); + nsTextRulesInfo ruleInfo(opID); + ruleInfo.inString = &aStringToInsert; + ruleInfo.outString = &resultString; + ruleInfo.maxLength = mMaxTextLength; + + bool cancel, handled; + nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); + NS_ENSURE_SUCCESS(res, res); + if (!cancel && !handled) + { + // we rely on rules code for now - no default implementation + } + if (!cancel) + { + // post-process + res = mRules->DidDoAction(selection, &ruleInfo, res); + } + return res; +} + +NS_IMETHODIMP nsPlaintextEditor::InsertLineBreak() +{ + if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } + + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + nsAutoEditBatch beginBatching(this); + nsAutoRules beginRulesSniffing(this, EditAction::insertBreak, nsIEditor::eNext); + + // pre-process + nsRefPtr selection = GetSelection(); + NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); + + // Batching the selection and moving nodes out from under the caret causes + // caret turds. Ask the shell to invalidate the caret now to avoid the turds. + nsCOMPtr shell = GetPresShell(); + NS_ENSURE_TRUE(shell, NS_ERROR_NOT_INITIALIZED); + shell->MaybeInvalidateCaretPosition(); + + nsTextRulesInfo ruleInfo(EditAction::insertBreak); + ruleInfo.maxLength = mMaxTextLength; + bool cancel, handled; + nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); + NS_ENSURE_SUCCESS(res, res); + if (!cancel && !handled) + { + // get the (collapsed) selection location + nsCOMPtr selNode; + int32_t selOffset; + res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); + NS_ENSURE_SUCCESS(res, res); + + // don't put text in places that can't have it + if (!IsTextNode(selNode) && !CanContainTag(selNode, nsGkAtoms::textTagName)) { + return NS_ERROR_FAILURE; + } + + // we need to get the doc + nsCOMPtr doc = GetDOMDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); + + // don't spaz my selection in subtransactions + nsAutoTxnsConserveSelection dontSpazMySelection(this); + + // insert a linefeed character + res = InsertTextImpl(NS_LITERAL_STRING("\n"), address_of(selNode), + &selOffset, doc); + if (!selNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called + if (NS_SUCCEEDED(res)) + { + // set the selection to the correct location + res = selection->Collapse(selNode, selOffset); + + if (NS_SUCCEEDED(res)) + { + // see if we're at the end of the editor range + nsCOMPtr endNode; + int32_t endOffset; + res = GetEndNodeAndOffset(selection, getter_AddRefs(endNode), &endOffset); + + if (NS_SUCCEEDED(res) && endNode == selNode && endOffset == selOffset) + { + // 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 + res = mRules->DidDoAction(selection, &ruleInfo, res); + } + + return res; +} + +nsresult +nsPlaintextEditor::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 + nsCOMPtr kungFuDeathGrip(mRules); + + nsTextEditRules *textEditRules = + static_cast(mRules.get()); + textEditRules->ResetIMETextPWBuf(); + } + + return nsEditor::BeginIMEComposition(aEvent); +} + +nsresult +nsPlaintextEditor::UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent) +{ + NS_ABORT_IF_FALSE(aDOMTextEvent, "aDOMTextEvent must not be nullptr"); + + WidgetTextEvent* widgetTextEvent = + aDOMTextEvent->GetInternalNSEvent()->AsTextEvent(); + NS_ENSURE_TRUE(widgetTextEvent, NS_ERROR_INVALID_ARG); + + EnsureComposition(widgetTextEvent); + + nsCOMPtr ps = GetPresShell(); + NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr selection; + nsresult rv = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr caretP = ps->GetCaret(); + + { + TextComposition::TextEventHandlingMarker + textEventHandlingMarker(mComposition, widgetTextEvent); + + nsAutoPlaceHolderBatch batch(this, nsGkAtoms::IMETxnName); + + rv = InsertText(widgetTextEvent->theText); + + if (caretP) { + caretP->SetCaretDOMSelection(selection); + } + } + + // If still composing, we should fire input event via observer. + // Note that if committed, we don't need to notify it since it will be + // notified at followed compositionend event. + // NOTE: We must notify after the auto batch will be gone. + if (IsIMEComposing()) { + NotifyEditorObservers(); + } + + return rv; +} + +already_AddRefed +nsPlaintextEditor::GetInputEventTargetContent() +{ + nsCOMPtr target = do_QueryInterface(mEventTarget); + return target.forget(); +} + +NS_IMETHODIMP +nsPlaintextEditor::GetDocumentIsEmpty(bool *aDocumentIsEmpty) +{ + NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER); + + NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED); + + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + return mRules->DocumentIsEmpty(aDocumentIsEmpty); +} + +NS_IMETHODIMP +nsPlaintextEditor::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 = do_QueryInterface(iter->GetCurrentNode()); + nsCOMPtr textNode = do_QueryInterface(currentNode); + if (textNode && IsEditable(currentNode)) { + uint32_t length; + textNode->GetLength(&length); + totalLength += length; + } + } + + *aCount = totalLength; + return NS_OK; +} + +NS_IMETHODIMP +nsPlaintextEditor::SetMaxTextLength(int32_t aMaxTextLength) +{ + mMaxTextLength = aMaxTextLength; + return NS_OK; +} + +NS_IMETHODIMP +nsPlaintextEditor::GetMaxTextLength(int32_t* aMaxTextLength) +{ + NS_ENSURE_TRUE(aMaxTextLength, NS_ERROR_INVALID_POINTER); + *aMaxTextLength = mMaxTextLength; + return NS_OK; +} + +// +// Get the wrap width +// +NS_IMETHODIMP +nsPlaintextEditor::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); + } +} + +// +// Change the wrap width on the root of this document. +// +NS_IMETHODIMP +nsPlaintextEditor::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; "); + + // If "mail.compose.wrap_to_window_width" is set, and we're a mail editor, + // then remember our wrap width (for output purposes) but set the visual + // wrapping to window width. + // We may reset mWrapToWindow here, based on the pref's current value. + if (IsMailEditor()) + { + mWrapToWindow = + Preferences::GetBool("mail.compose.wrap_to_window_width", mWrapToWindow); + } + + // and now we're ready to set the new whitespace/wrapping style. + if (aWrapColumn > 0 && !mWrapToWindow) // Wrap to a fixed column + { + styleValue.AppendLiteral("white-space: pre-wrap; width: "); + styleValue.AppendInt(aWrapColumn); + styleValue.AppendLiteral("ch;"); + } + else if (mWrapToWindow || aWrapColumn == 0) + styleValue.AppendLiteral("white-space: pre-wrap;"); + else + styleValue.AppendLiteral("white-space: pre;"); + + return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue, true); +} + +NS_IMETHODIMP +nsPlaintextEditor::SetWrapColumn(int32_t aWrapColumn) +{ + mWrapColumn = aWrapColumn; + return NS_OK; +} + +// +// Get the newline handling for this editor +// +NS_IMETHODIMP +nsPlaintextEditor::GetNewlineHandling(int32_t *aNewlineHandling) +{ + NS_ENSURE_ARG_POINTER(aNewlineHandling); + + *aNewlineHandling = mNewlineHandling; + return NS_OK; +} + +// +// Change the newline handling for this editor +// +NS_IMETHODIMP +nsPlaintextEditor::SetNewlineHandling(int32_t aNewlineHandling) +{ + mNewlineHandling = aNewlineHandling; + + return NS_OK; +} + +NS_IMETHODIMP +nsPlaintextEditor::Undo(uint32_t aCount) +{ + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + nsAutoUpdateViewBatch beginViewBatching(this); + + ForceCompositionEnd(); + + nsAutoRules beginRulesSniffing(this, EditAction::undo, nsIEditor::eNone); + + nsTextRulesInfo ruleInfo(EditAction::undo); + nsRefPtr selection = GetSelection(); + bool cancel, handled; + nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); + + if (!cancel && NS_SUCCEEDED(result)) + { + result = nsEditor::Undo(aCount); + result = mRules->DidDoAction(selection, &ruleInfo, result); + } + + NotifyEditorObservers(); + return result; +} + +NS_IMETHODIMP +nsPlaintextEditor::Redo(uint32_t aCount) +{ + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + nsAutoUpdateViewBatch beginViewBatching(this); + + ForceCompositionEnd(); + + nsAutoRules beginRulesSniffing(this, EditAction::redo, nsIEditor::eNone); + + nsTextRulesInfo ruleInfo(EditAction::redo); + nsRefPtr selection = GetSelection(); + bool cancel, handled; + nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); + + if (!cancel && NS_SUCCEEDED(result)) + { + result = nsEditor::Redo(aCount); + result = mRules->DidDoAction(selection, &ruleInfo, result); + } + + NotifyEditorObservers(); + return result; +} + +bool +nsPlaintextEditor::CanCutOrCopy() +{ + nsCOMPtr selection; + if (NS_FAILED(GetSelection(getter_AddRefs(selection)))) + return false; + + return !selection->Collapsed(); +} + +bool +nsPlaintextEditor::FireClipboardEvent(int32_t aType, int32_t aSelectionType) +{ + if (aType == NS_PASTE) + ForceCompositionEnd(); + + nsCOMPtr presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, false); + + nsCOMPtr selection; + if (NS_FAILED(GetSelection(getter_AddRefs(selection)))) + return false; + + if (!nsCopySupport::FireClipboardEvent(aType, aSelectionType, presShell, selection)) + 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 nsPlaintextEditor::Cut() +{ + if (FireClipboardEvent(NS_CUT, nsIClipboard::kGlobalClipboard)) + return DeleteSelection(eNone, eStrip); + return NS_OK; +} + +NS_IMETHODIMP nsPlaintextEditor::CanCut(bool *aCanCut) +{ + NS_ENSURE_ARG_POINTER(aCanCut); + *aCanCut = IsModifiable() && CanCutOrCopy(); + return NS_OK; +} + +NS_IMETHODIMP nsPlaintextEditor::Copy() +{ + FireClipboardEvent(NS_COPY, nsIClipboard::kGlobalClipboard); + return NS_OK; +} + +NS_IMETHODIMP nsPlaintextEditor::CanCopy(bool *aCanCopy) +{ + NS_ENSURE_ARG_POINTER(aCanCopy); + *aCanCopy = CanCutOrCopy(); + return NS_OK; +} + +// Shared between OutputToString and OutputToStream +NS_IMETHODIMP +nsPlaintextEditor::GetAndInitDocEncoder(const nsAString& aFormatType, + uint32_t aFlags, + const nsACString& aCharset, + nsIDocumentEncoder** encoder) +{ + nsresult rv = NS_OK; + + nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE); + LossyAppendUTF16toASCII(aFormatType, formatType); + nsCOMPtr docEncoder (do_CreateInstance(formatType.get(), &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr domDoc = do_QueryReferent(mDocWeak); + NS_ASSERTION(domDoc, "Need a document"); + + rv = docEncoder->Init(domDoc, aFormatType, aFlags); + NS_ENSURE_SUCCESS(rv, rv); + + 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) + { + nsCOMPtr selection; + rv = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, rv); + if (selection) { + rv = docEncoder->SetSelection(selection); + NS_ENSURE_SUCCESS(rv, rv); + } + } + // ... 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(); + NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE); + if (!rootElement->IsHTML(nsGkAtoms::body)) { + rv = docEncoder->SetNativeContainerNode(rootElement); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + docEncoder.forget(encoder); + return NS_OK; +} + + +NS_IMETHODIMP +nsPlaintextEditor::OutputToString(const nsAString& aFormatType, + uint32_t aFlags, + nsAString& aOutputString) +{ + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + nsString resultString; + nsTextRulesInfo ruleInfo(EditAction::outputText); + ruleInfo.outString = &resultString; + // XXX Struct should store a nsAReadable* + nsAutoString str(aFormatType); + ruleInfo.outputFormat = &str; + bool cancel, handled; + nsresult rv = mRules->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("ISO-8859-1"); + + nsCOMPtr encoder; + rv = GetAndInitDocEncoder(aFormatType, aFlags, charsetStr, getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, rv); + return encoder->EncodeToString(aOutputString); +} + +NS_IMETHODIMP +nsPlaintextEditor::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; + rv = GetAndInitDocEncoder(aFormatType, aFlags, aCharset, + getter_AddRefs(encoder)); + + NS_ENSURE_SUCCESS(rv, rv); + + return encoder->EncodeToStream(aOutputStream); +} + +NS_IMETHODIMP +nsPlaintextEditor::InsertTextWithQuotations(const nsAString &aStringToInsert) +{ + return InsertText(aStringToInsert); +} + +NS_IMETHODIMP +nsPlaintextEditor::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; + char* flav = nullptr; + rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj), + &len); + if (NS_FAILED(rv) || !flav) + { +#ifdef DEBUG_akkana + printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv); +#endif + return rv; + } +#ifdef DEBUG_clipboard + printf("Got flavor [%s]\n", flav); +#endif + if (0 == nsCRT::strcmp(flav, kUnicodeMime) || + 0 == nsCRT::strcmp(flav, kMozTextInternal)) + { + nsCOMPtr textDataObj ( do_QueryInterface(genericDataObj) ); + if (textDataObj && len > 0) + { + nsAutoString stuffToPaste; + textDataObj->GetData ( stuffToPaste ); + nsAutoEditBatch beginBatching(this); + rv = InsertAsQuotation(stuffToPaste, 0); + } + } + NS_Free(flav); + } + + return rv; +} + +NS_IMETHODIMP +nsPlaintextEditor::InsertAsQuotation(const nsAString& aQuotedText, + nsIDOMNode **aNodeInserted) +{ + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + // Let the citer quote it for us: + nsString quotedStuff; + nsresult rv = nsInternetCiter::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 + nsRefPtr selection = GetSelection(); + NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); + + nsAutoEditBatch beginBatching(this); + nsAutoRules beginRulesSniffing(this, EditAction::insertText, nsIEditor::eNext); + + // give rules a chance to handle or cancel + nsTextRulesInfo ruleInfo(EditAction::insertElement); + bool cancel, handled; + rv = mRules->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 = 0; + //NS_IF_ADDREF(*aNodeInserted); + } + } + return rv; +} + +NS_IMETHODIMP +nsPlaintextEditor::PasteAsCitedQuotation(const nsAString& aCitation, + int32_t aSelectionType) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPlaintextEditor::InsertAsCitedQuotation(const nsAString& aQuotedText, + const nsAString& aCitation, + bool aInsertHTML, + nsIDOMNode **aNodeInserted) +{ + return InsertAsQuotation(aQuotedText, aNodeInserted); +} + +nsresult +nsPlaintextEditor::SharedOutputString(uint32_t aFlags, + bool* aIsCollapsed, + nsAString& aResult) +{ + nsCOMPtr selection; + nsresult rv = GetSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, rv); + 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 +nsPlaintextEditor::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; + +#ifdef DEBUG_akkana + printf("nsPlaintextEditor::Rewrap to %ld columns\n", (long)wrapCol); +#endif + + 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 = nsInternetCiter::Rewrap(current, wrapCol, firstLineOffset, aRespectNewlines, + wrapped); + NS_ENSURE_SUCCESS(rv, rv); + + if (isCollapsed) // rewrap the whole document + SelectAll(); + + return InsertTextWithQuotations(wrapped); +} + +NS_IMETHODIMP +nsPlaintextEditor::StripCites() +{ +#ifdef DEBUG_akkana + printf("nsPlaintextEditor::StripCites()\n"); +#endif + + nsAutoString current; + bool isCollapsed; + nsresult rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted, + &isCollapsed, current); + NS_ENSURE_SUCCESS(rv, rv); + + nsString stripped; + rv = nsInternetCiter::StripCites(current, stripped); + NS_ENSURE_SUCCESS(rv, rv); + + if (isCollapsed) // rewrap the whole document + { + rv = SelectAll(); + NS_ENSURE_SUCCESS(rv, rv); + } + + return InsertText(stripped); +} + +NS_IMETHODIMP +nsPlaintextEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList) +{ + *aNodeList = 0; + 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 +nsPlaintextEditor::StartOperation(EditAction opID, + nsIEditor::EDirection aDirection) +{ + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection + if (mRules) return mRules->BeforeEdit(mAction, mDirection); + return NS_OK; +} + + +/** All editor operations which alter the doc should be followed + * with a call to EndOperation */ +NS_IMETHODIMP +nsPlaintextEditor::EndOperation() +{ + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + // post processing + nsresult res = NS_OK; + if (mRules) res = mRules->AfterEdit(mAction, mDirection); + nsEditor::EndOperation(); // will clear mAction, mDirection + return res; +} + + +NS_IMETHODIMP +nsPlaintextEditor::SelectEntireDocument(nsISelection *aSelection) +{ + if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } + + // Protect the edit rules object from dying + nsCOMPtr kungFuDeathGrip(mRules); + + // is doc empty? + bool bDocIsEmpty; + if (NS_SUCCEEDED(mRules->DocumentIsEmpty(&bDocIsEmpty)) && bDocIsEmpty) + { + // 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); + } + + nsresult rv = nsEditor::SelectEntireDocument(aSelection); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't select the trailing BR node if we have one + int32_t selOffset; + nsCOMPtr selNode; + rv = GetEndNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr childNode = GetChildAt(selNode, selOffset - 1); + + if (childNode && nsTextEditUtils::IsMozBR(childNode)) { + int32_t parentOffset; + nsCOMPtr parentNode = GetNodeLocation(childNode, &parentOffset); + + return aSelection->Extend(parentNode, parentOffset); + } + + return NS_OK; +} + +already_AddRefed +nsPlaintextEditor::GetDOMEventTarget() +{ + nsCOMPtr copy = mEventTarget; + return copy.forget(); +} + + +nsresult +nsPlaintextEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement, + const nsAString & aAttribute, + const nsAString & aValue, + bool aSuppressTransaction) +{ + return nsEditor::SetAttribute(aElement, aAttribute, aValue); +} + +nsresult +nsPlaintextEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement, + const nsAString & aAttribute, + bool aSuppressTransaction) +{ + return nsEditor::RemoveAttribute(aElement, aAttribute); +}