michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/TextComposition.h" michael@0: #include "mozilla/TextEvents.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsAString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCRT.h" michael@0: #include "nsCaret.h" michael@0: #include "nsCharTraits.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsContentCID.h" michael@0: #include "nsCopySupport.h" michael@0: #include "nsDebug.h" michael@0: #include "nsDependentSubstring.h" michael@0: #include "nsEditRules.h" michael@0: #include "nsEditorUtils.h" // nsAutoEditBatch, nsAutoRules michael@0: #include "nsError.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIClipboard.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsIDOMCharacterData.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMEventTarget.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIDocumentEncoder.h" michael@0: #include "nsIEditorIMESupport.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsINode.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISelectionController.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsITransferable.h" michael@0: #include "nsIWeakReferenceUtils.h" michael@0: #include "nsInternetCiter.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsPlaintextEditor.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsStringFwd.h" michael@0: #include "nsSubstringTuple.h" michael@0: #include "nsTextEditRules.h" michael@0: #include "nsTextEditUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsXPCOM.h" michael@0: michael@0: class nsIOutputStream; michael@0: class nsISupports; michael@0: class nsISupportsArray; michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsPlaintextEditor::nsPlaintextEditor() michael@0: : nsEditor() michael@0: , mRules(nullptr) michael@0: , mWrapToWindow(false) michael@0: , mWrapColumn(0) michael@0: , mMaxTextLength(-1) michael@0: , mInitTriggerCounter(0) michael@0: , mNewlineHandling(nsIPlaintextEditor::eNewlinesPasteToFirst) michael@0: #ifdef XP_WIN michael@0: , mCaretStyle(1) michael@0: #else michael@0: , mCaretStyle(0) michael@0: #endif michael@0: { michael@0: // check the "single line editor newline handling" michael@0: // and "caret behaviour in selection" prefs michael@0: GetDefaultEditorPrefs(mNewlineHandling, mCaretStyle); michael@0: } michael@0: michael@0: nsPlaintextEditor::~nsPlaintextEditor() michael@0: { michael@0: // Remove event listeners. Note that if we had an HTML editor, michael@0: // it installed its own instead of these michael@0: RemoveEventListeners(); michael@0: michael@0: if (mRules) michael@0: mRules->DetachEditor(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsPlaintextEditor) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPlaintextEditor, nsEditor) michael@0: if (tmp->mRules) michael@0: tmp->mRules->DetachEditor(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mRules) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPlaintextEditor, nsEditor) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRules) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsPlaintextEditor, nsEditor) michael@0: NS_IMPL_RELEASE_INHERITED(nsPlaintextEditor, nsEditor) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsPlaintextEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport) michael@0: NS_INTERFACE_MAP_END_INHERITING(nsEditor) michael@0: michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::Init(nsIDOMDocument *aDoc, michael@0: nsIContent *aRoot, michael@0: nsISelectionController *aSelCon, michael@0: uint32_t aFlags, michael@0: const nsAString& aInitialValue) michael@0: { michael@0: NS_PRECONDITION(aDoc, "bad arg"); michael@0: NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = NS_OK, rulesRes = NS_OK; michael@0: if (mRules) { michael@0: mRules->DetachEditor(); michael@0: } michael@0: michael@0: { michael@0: // block to scope nsAutoEditInitRulesTrigger michael@0: nsAutoEditInitRulesTrigger rulesTrigger(this, rulesRes); michael@0: michael@0: // Init the base editor michael@0: res = nsEditor::Init(aDoc, aRoot, aSelCon, aFlags, aInitialValue); michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rulesRes, rulesRes); michael@0: michael@0: // mRules may not have been initialized yet, when this is called via michael@0: // nsHTMLEditor::Init. michael@0: if (mRules) { michael@0: mRules->SetInitialValue(aInitialValue); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: static int32_t sNewlineHandlingPref = -1, michael@0: sCaretStylePref = -1; michael@0: michael@0: static void michael@0: EditorPrefsChangedCallback(const char *aPrefName, void *) michael@0: { michael@0: if (nsCRT::strcmp(aPrefName, "editor.singleLine.pasteNewlines") == 0) { michael@0: sNewlineHandlingPref = michael@0: Preferences::GetInt("editor.singleLine.pasteNewlines", michael@0: nsIPlaintextEditor::eNewlinesPasteToFirst); michael@0: } else if (nsCRT::strcmp(aPrefName, "layout.selection.caret_style") == 0) { michael@0: sCaretStylePref = Preferences::GetInt("layout.selection.caret_style", michael@0: #ifdef XP_WIN michael@0: 1); michael@0: if (sCaretStylePref == 0) michael@0: sCaretStylePref = 1; michael@0: #else michael@0: 0); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsPlaintextEditor::GetDefaultEditorPrefs(int32_t &aNewlineHandling, michael@0: int32_t &aCaretStyle) michael@0: { michael@0: if (sNewlineHandlingPref == -1) { michael@0: Preferences::RegisterCallback(EditorPrefsChangedCallback, michael@0: "editor.singleLine.pasteNewlines"); michael@0: EditorPrefsChangedCallback("editor.singleLine.pasteNewlines", nullptr); michael@0: Preferences::RegisterCallback(EditorPrefsChangedCallback, michael@0: "layout.selection.caret_style"); michael@0: EditorPrefsChangedCallback("layout.selection.caret_style", nullptr); michael@0: } michael@0: michael@0: aNewlineHandling = sNewlineHandlingPref; michael@0: aCaretStyle = sCaretStylePref; michael@0: } michael@0: michael@0: void michael@0: nsPlaintextEditor::BeginEditorInit() michael@0: { michael@0: mInitTriggerCounter++; michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::EndEditorInit() michael@0: { michael@0: nsresult res = NS_OK; michael@0: NS_PRECONDITION(mInitTriggerCounter > 0, "ended editor init before we began?"); michael@0: mInitTriggerCounter--; michael@0: if (mInitTriggerCounter == 0) michael@0: { michael@0: res = InitRules(); michael@0: if (NS_SUCCEEDED(res)) { michael@0: // Throw away the old transaction manager if this is not the first time that michael@0: // we're initializing the editor. michael@0: EnableUndo(false); michael@0: EnableUndo(true); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::SetDocumentCharacterSet(const nsACString& characterSet) michael@0: { michael@0: nsresult rv = nsEditor::SetDocumentCharacterSet(characterSet); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Update META charset element. michael@0: nsCOMPtr domdoc = GetDOMDocument(); michael@0: NS_ENSURE_TRUE(domdoc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (UpdateMetaCharset(domdoc, characterSet)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr headList; michael@0: rv = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(headList)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(headList, NS_OK); michael@0: michael@0: nsCOMPtr headNode; michael@0: headList->Item(0, getter_AddRefs(headNode)); michael@0: NS_ENSURE_TRUE(headNode, NS_OK); michael@0: michael@0: // Create a new meta charset tag michael@0: nsCOMPtr resultNode; michael@0: rv = CreateNode(NS_LITERAL_STRING("meta"), headNode, 0, getter_AddRefs(resultNode)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(resultNode, NS_OK); michael@0: michael@0: // Set attributes to the created element michael@0: if (characterSet.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr metaElement = do_QueryInterface(resultNode); michael@0: if (!metaElement) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // not undoable, undo should undo CreateNode michael@0: metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, michael@0: NS_LITERAL_STRING("Content-Type"), true); michael@0: metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::content, michael@0: NS_LITERAL_STRING("text/html;charset=") + michael@0: NS_ConvertASCIItoUTF16(characterSet), michael@0: true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsPlaintextEditor::UpdateMetaCharset(nsIDOMDocument* aDocument, michael@0: const nsACString& aCharacterSet) michael@0: { michael@0: MOZ_ASSERT(aDocument); michael@0: // get a list of META tags michael@0: nsCOMPtr list; michael@0: nsresult rv = aDocument->GetElementsByTagName(NS_LITERAL_STRING("meta"), michael@0: getter_AddRefs(list)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: NS_ENSURE_TRUE(list, false); michael@0: michael@0: nsCOMPtr metaList = do_QueryInterface(list); michael@0: michael@0: uint32_t listLength = 0; michael@0: metaList->GetLength(&listLength); michael@0: michael@0: for (uint32_t i = 0; i < listLength; ++i) { michael@0: nsCOMPtr metaNode = metaList->Item(i); michael@0: MOZ_ASSERT(metaNode); michael@0: michael@0: if (!metaNode->IsElement()) { michael@0: continue; michael@0: } michael@0: michael@0: nsAutoString currentValue; michael@0: metaNode->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, currentValue); michael@0: michael@0: if (!FindInReadable(NS_LITERAL_STRING("content-type"), michael@0: currentValue, michael@0: nsCaseInsensitiveStringComparator())) { michael@0: continue; michael@0: } michael@0: michael@0: metaNode->GetAttr(kNameSpaceID_None, nsGkAtoms::content, currentValue); michael@0: michael@0: NS_NAMED_LITERAL_STRING(charsetEquals, "charset="); michael@0: nsAString::const_iterator originalStart, start, end; michael@0: originalStart = currentValue.BeginReading(start); michael@0: currentValue.EndReading(end); michael@0: if (!FindInReadable(charsetEquals, start, end, michael@0: nsCaseInsensitiveStringComparator())) { michael@0: continue; michael@0: } michael@0: michael@0: // set attribute to charset=text/html michael@0: nsCOMPtr metaElement = do_QueryInterface(metaNode); michael@0: MOZ_ASSERT(metaElement); michael@0: rv = nsEditor::SetAttribute(metaElement, NS_LITERAL_STRING("content"), michael@0: Substring(originalStart, start) + michael@0: charsetEquals + michael@0: NS_ConvertASCIItoUTF16(aCharacterSet)); michael@0: return NS_SUCCEEDED(rv); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::InitRules() michael@0: { michael@0: if (!mRules) { michael@0: // instantiate the rules for this text editor michael@0: mRules = new nsTextEditRules(); michael@0: } michael@0: return mRules->Init(this); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetIsDocumentEditable(bool *aIsDocumentEditable) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIsDocumentEditable); michael@0: michael@0: nsCOMPtr doc = GetDOMDocument(); michael@0: *aIsDocumentEditable = doc && IsModifiable(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsPlaintextEditor::IsModifiable() michael@0: { michael@0: return !IsReadonly(); michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: // NOTE: When you change this method, you should also change: michael@0: // * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html michael@0: // * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html michael@0: // michael@0: // And also when you add new key handling, you need to change the subclass's michael@0: // HandleKeyPressEvent()'s switch statement. michael@0: michael@0: if (IsReadonly() || IsDisabled()) { michael@0: // When we're not editable, the events handled on nsEditor. michael@0: return nsEditor::HandleKeyPressEvent(aKeyEvent); michael@0: } michael@0: michael@0: WidgetKeyboardEvent* nativeKeyEvent = michael@0: aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); michael@0: NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED); michael@0: NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS, michael@0: "HandleKeyPressEvent gets non-keypress event"); michael@0: michael@0: switch (nativeKeyEvent->keyCode) { michael@0: case nsIDOMKeyEvent::DOM_VK_META: michael@0: case nsIDOMKeyEvent::DOM_VK_WIN: michael@0: case nsIDOMKeyEvent::DOM_VK_SHIFT: michael@0: case nsIDOMKeyEvent::DOM_VK_CONTROL: michael@0: case nsIDOMKeyEvent::DOM_VK_ALT: michael@0: case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: michael@0: case nsIDOMKeyEvent::DOM_VK_DELETE: michael@0: // These keys are handled on nsEditor michael@0: return nsEditor::HandleKeyPressEvent(aKeyEvent); michael@0: case nsIDOMKeyEvent::DOM_VK_TAB: { michael@0: if (IsTabbable()) { michael@0: return NS_OK; // let it be used for focus switching michael@0: } michael@0: michael@0: if (nativeKeyEvent->IsShift() || nativeKeyEvent->IsControl() || michael@0: nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || michael@0: nativeKeyEvent->IsOS()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // else we insert the tab straight through michael@0: aKeyEvent->PreventDefault(); michael@0: return TypedText(NS_LITERAL_STRING("\t"), eTypedText); michael@0: } michael@0: case nsIDOMKeyEvent::DOM_VK_RETURN: michael@0: if (IsSingleLineEditor() || nativeKeyEvent->IsControl() || michael@0: nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || michael@0: nativeKeyEvent->IsOS()) { michael@0: return NS_OK; michael@0: } michael@0: aKeyEvent->PreventDefault(); michael@0: return TypedText(EmptyString(), eTypedBreak); michael@0: } michael@0: michael@0: // NOTE: On some keyboard layout, some characters are inputted with Control michael@0: // key or Alt key, but at that time, widget sets FALSE to these keys. michael@0: if (nativeKeyEvent->charCode == 0 || nativeKeyEvent->IsControl() || michael@0: nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || michael@0: nativeKeyEvent->IsOS()) { michael@0: // we don't PreventDefault() here or keybindings like control-x won't work michael@0: return NS_OK; michael@0: } michael@0: aKeyEvent->PreventDefault(); michael@0: nsAutoString str(nativeKeyEvent->charCode); michael@0: return TypedText(str, eTypedText); michael@0: } michael@0: michael@0: /* This routine is needed to provide a bottleneck for typing for logging michael@0: purposes. Can't use HandleKeyPress() (above) for that since it takes michael@0: a nsIDOMKeyEvent* parameter. So instead we pass enough info through michael@0: to TypedText() to determine what action to take, but without passing michael@0: an event. michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::TypedText(const nsAString& aString, ETypingAction aAction) michael@0: { michael@0: nsAutoPlaceHolderBatch batch(this, nsGkAtoms::TypingTxnName); michael@0: michael@0: switch (aAction) { michael@0: case eTypedText: michael@0: return InsertText(aString); michael@0: case eTypedBreak: michael@0: return InsertLineBreak(); michael@0: default: michael@0: // eTypedBR is only for HTML michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::CreateBRImpl(nsCOMPtr* aInOutParent, michael@0: int32_t* aInOutOffset, michael@0: nsCOMPtr* outBRNode, michael@0: EDirection aSelect) michael@0: { michael@0: NS_ENSURE_TRUE(aInOutParent && *aInOutParent && aInOutOffset && outBRNode, NS_ERROR_NULL_POINTER); michael@0: *outBRNode = nullptr; michael@0: nsresult res; michael@0: michael@0: // we need to insert a br. unfortunately, we may have to split a text node to do it. michael@0: nsCOMPtr node = *aInOutParent; michael@0: int32_t theOffset = *aInOutOffset; michael@0: nsCOMPtr nodeAsText = do_QueryInterface(node); michael@0: NS_NAMED_LITERAL_STRING(brType, "br"); michael@0: nsCOMPtr brNode; michael@0: if (nodeAsText) michael@0: { michael@0: int32_t offset; michael@0: uint32_t len; michael@0: nodeAsText->GetLength(&len); michael@0: nsCOMPtr tmp = GetNodeLocation(node, &offset); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: if (!theOffset) michael@0: { michael@0: // we are already set to go michael@0: } michael@0: else if (theOffset == (int32_t)len) michael@0: { michael@0: // update offset to point AFTER the text node michael@0: offset++; michael@0: } michael@0: else michael@0: { michael@0: // split the text node michael@0: res = SplitNode(node, theOffset, getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: tmp = GetNodeLocation(node, &offset); michael@0: } michael@0: // create br michael@0: res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *aInOutParent = tmp; michael@0: *aInOutOffset = offset+1; michael@0: } michael@0: else michael@0: { michael@0: res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: (*aInOutOffset)++; michael@0: } michael@0: michael@0: *outBRNode = brNode; michael@0: if (*outBRNode && (aSelect != eNone)) michael@0: { michael@0: int32_t offset; michael@0: nsCOMPtr parent = GetNodeLocation(*outBRNode, &offset); michael@0: michael@0: nsCOMPtr selection; michael@0: res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: if (aSelect == eNext) michael@0: { michael@0: // position selection after br michael@0: selPriv->SetInterlinePosition(true); michael@0: res = selection->Collapse(parent, offset+1); michael@0: } michael@0: else if (aSelect == ePrevious) michael@0: { michael@0: // position selection before br michael@0: selPriv->SetInterlinePosition(true); michael@0: res = selection->Collapse(parent, offset); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::CreateBR(nsIDOMNode *aNode, int32_t aOffset, nsCOMPtr *outBRNode, EDirection aSelect) michael@0: { michael@0: nsCOMPtr parent = aNode; michael@0: int32_t offset = aOffset; michael@0: return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect); michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::InsertBR(nsCOMPtr* outBRNode) michael@0: { michael@0: NS_ENSURE_TRUE(outBRNode, NS_ERROR_NULL_POINTER); michael@0: *outBRNode = nullptr; michael@0: michael@0: // calling it text insertion to trigger moz br treatment by rules michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertText, nsIEditor::eNext); michael@0: michael@0: nsCOMPtr selection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!selection->Collapsed()) { michael@0: res = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: nsCOMPtr selNode; michael@0: int32_t selOffset; michael@0: res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: res = CreateBR(selNode, selOffset, outBRNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // position selection after br michael@0: selNode = GetNodeLocation(*outBRNode, &selOffset); michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: selPriv->SetInterlinePosition(true); michael@0: return selection->Collapse(selNode, selOffset+1); michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::ExtendSelectionForDelete(nsISelection *aSelection, michael@0: nsIEditor::EDirection *aAction) michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: bool bCollapsed = aSelection->Collapsed(); michael@0: michael@0: if (*aAction == eNextWord || *aAction == ePreviousWord michael@0: || (*aAction == eNext && bCollapsed) michael@0: || (*aAction == ePrevious && bCollapsed) michael@0: || *aAction == eToBeginningOfLine || *aAction == eToEndOfLine) michael@0: { michael@0: nsCOMPtr selCont; michael@0: GetSelectionController(getter_AddRefs(selCont)); michael@0: NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE); michael@0: michael@0: switch (*aAction) michael@0: { michael@0: case eNextWord: michael@0: result = selCont->WordExtendForDelete(true); michael@0: // DeleteSelectionImpl doesn't handle these actions michael@0: // because it's inside batching, so don't confuse it: michael@0: *aAction = eNone; michael@0: break; michael@0: case ePreviousWord: michael@0: result = selCont->WordExtendForDelete(false); michael@0: *aAction = eNone; michael@0: break; michael@0: case eNext: michael@0: result = selCont->CharacterExtendForDelete(); michael@0: // Don't set aAction to eNone (see Bug 502259) michael@0: break; michael@0: case ePrevious: { michael@0: // Only extend the selection where the selection is after a UTF-16 michael@0: // surrogate pair. For other cases we don't want to do that, in order michael@0: // to make sure that pressing backspace will only delete the last michael@0: // typed character. michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: result = GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: if (IsTextNode(node)) { michael@0: nsCOMPtr charData = do_QueryInterface(node); michael@0: if (charData) { michael@0: nsAutoString data; michael@0: result = charData->GetData(data); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: if (offset > 1 && michael@0: NS_IS_LOW_SURROGATE(data[offset - 1]) && michael@0: NS_IS_HIGH_SURROGATE(data[offset - 2])) { michael@0: result = selCont->CharacterExtendForBackspace(); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: case eToBeginningOfLine: michael@0: selCont->IntraLineMove(true, false); // try to move to end michael@0: result = selCont->IntraLineMove(false, true); // select to beginning michael@0: *aAction = eNone; michael@0: break; michael@0: case eToEndOfLine: michael@0: result = selCont->IntraLineMove(true, true); michael@0: *aAction = eNext; michael@0: break; michael@0: default: // avoid several compiler warnings michael@0: result = NS_OK; michael@0: break; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::DeleteSelection(EDirection aAction, michael@0: EStripWrappers aStripWrappers) michael@0: { michael@0: MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); michael@0: michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsresult result; michael@0: michael@0: // delete placeholder txns merge. michael@0: nsAutoPlaceHolderBatch batch(this, nsGkAtoms::DeleteTxnName); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // If there is an existing selection when an extended delete is requested, michael@0: // platforms that use "caret-style" caret positioning collapse the michael@0: // selection to the start and then create a new selection. michael@0: // Platforms that use "selection-style" caret positioning just delete the michael@0: // existing selection without extending it. michael@0: if (!selection->Collapsed() && michael@0: (aAction == eNextWord || aAction == ePreviousWord || michael@0: aAction == eToBeginningOfLine || aAction == eToEndOfLine)) michael@0: { michael@0: if (mCaretStyle == 1) michael@0: { michael@0: result = selection->CollapseToStart(); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: else michael@0: { michael@0: aAction = eNone; michael@0: } michael@0: } michael@0: michael@0: nsTextRulesInfo ruleInfo(EditAction::deleteSelection); michael@0: ruleInfo.collapsedAction = aAction; michael@0: ruleInfo.stripWrappers = aStripWrappers; michael@0: bool cancel, handled; michael@0: result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: if (!cancel && !handled) michael@0: { michael@0: result = DeleteSelectionImpl(aAction, aStripWrappers); michael@0: } michael@0: if (!cancel) michael@0: { michael@0: // post-process michael@0: result = mRules->DidDoAction(selection, &ruleInfo, result); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::InsertText(const nsAString &aStringToInsert) michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: EditAction opID = EditAction::insertText; michael@0: if (mComposition) { michael@0: opID = EditAction::insertIMEText; michael@0: } michael@0: nsAutoPlaceHolderBatch batch(this, nullptr); michael@0: nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: nsAutoString resultString; michael@0: // XXX can we trust instring to outlive ruleInfo, michael@0: // XXX and ruleInfo not to refer to instring in its dtor? michael@0: //nsAutoString instring(aStringToInsert); michael@0: nsTextRulesInfo ruleInfo(opID); michael@0: ruleInfo.inString = &aStringToInsert; michael@0: ruleInfo.outString = &resultString; michael@0: ruleInfo.maxLength = mMaxTextLength; michael@0: michael@0: bool cancel, handled; michael@0: nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!cancel && !handled) michael@0: { michael@0: // we rely on rules code for now - no default implementation michael@0: } michael@0: if (!cancel) michael@0: { michael@0: // post-process michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::InsertLineBreak() michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertBreak, nsIEditor::eNext); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Batching the selection and moving nodes out from under the caret causes michael@0: // caret turds. Ask the shell to invalidate the caret now to avoid the turds. michael@0: nsCOMPtr shell = GetPresShell(); michael@0: NS_ENSURE_TRUE(shell, NS_ERROR_NOT_INITIALIZED); michael@0: shell->MaybeInvalidateCaretPosition(); michael@0: michael@0: nsTextRulesInfo ruleInfo(EditAction::insertBreak); michael@0: ruleInfo.maxLength = mMaxTextLength; michael@0: bool cancel, handled; michael@0: nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!cancel && !handled) michael@0: { michael@0: // get the (collapsed) selection location michael@0: nsCOMPtr selNode; michael@0: int32_t selOffset; michael@0: res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // don't put text in places that can't have it michael@0: if (!IsTextNode(selNode) && !CanContainTag(selNode, nsGkAtoms::textTagName)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // we need to get the doc michael@0: nsCOMPtr doc = GetDOMDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // don't spaz my selection in subtransactions michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: michael@0: // insert a linefeed character michael@0: res = InsertTextImpl(NS_LITERAL_STRING("\n"), address_of(selNode), michael@0: &selOffset, doc); michael@0: if (!selNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: // set the selection to the correct location michael@0: res = selection->Collapse(selNode, selOffset); michael@0: michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: // see if we're at the end of the editor range michael@0: nsCOMPtr endNode; michael@0: int32_t endOffset; michael@0: res = GetEndNodeAndOffset(selection, getter_AddRefs(endNode), &endOffset); michael@0: michael@0: if (NS_SUCCEEDED(res) && endNode == selNode && endOffset == selOffset) michael@0: { michael@0: // SetInterlinePosition(true) means we want the caret to stick to the content on the "right". michael@0: // We want the caret to stick to whatever is past the break. This is michael@0: // because the break is on the same line we were on, but the next content michael@0: // will be on the following line. michael@0: selection->SetInterlinePosition(true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (!cancel) michael@0: { michael@0: // post-process, always called if WillInsertBreak didn't return cancel==true michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::BeginIMEComposition(WidgetCompositionEvent* aEvent) michael@0: { michael@0: NS_ENSURE_TRUE(!mComposition, NS_OK); michael@0: michael@0: if (IsPasswordEditor()) { michael@0: NS_ENSURE_TRUE(mRules, NS_ERROR_NULL_POINTER); michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsTextEditRules *textEditRules = michael@0: static_cast(mRules.get()); michael@0: textEditRules->ResetIMETextPWBuf(); michael@0: } michael@0: michael@0: return nsEditor::BeginIMEComposition(aEvent); michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent) michael@0: { michael@0: NS_ABORT_IF_FALSE(aDOMTextEvent, "aDOMTextEvent must not be nullptr"); michael@0: michael@0: WidgetTextEvent* widgetTextEvent = michael@0: aDOMTextEvent->GetInternalNSEvent()->AsTextEvent(); michael@0: NS_ENSURE_TRUE(widgetTextEvent, NS_ERROR_INVALID_ARG); michael@0: michael@0: EnsureComposition(widgetTextEvent); michael@0: michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr selection; michael@0: nsresult rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr caretP = ps->GetCaret(); michael@0: michael@0: { michael@0: TextComposition::TextEventHandlingMarker michael@0: textEventHandlingMarker(mComposition, widgetTextEvent); michael@0: michael@0: nsAutoPlaceHolderBatch batch(this, nsGkAtoms::IMETxnName); michael@0: michael@0: rv = InsertText(widgetTextEvent->theText); michael@0: michael@0: if (caretP) { michael@0: caretP->SetCaretDOMSelection(selection); michael@0: } michael@0: } michael@0: michael@0: // If still composing, we should fire input event via observer. michael@0: // Note that if committed, we don't need to notify it since it will be michael@0: // notified at followed compositionend event. michael@0: // NOTE: We must notify after the auto batch will be gone. michael@0: if (IsIMEComposing()) { michael@0: NotifyEditorObservers(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsPlaintextEditor::GetInputEventTargetContent() michael@0: { michael@0: nsCOMPtr target = do_QueryInterface(mEventTarget); michael@0: return target.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetDocumentIsEmpty(bool *aDocumentIsEmpty) michael@0: { michael@0: NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER); michael@0: michael@0: NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: return mRules->DocumentIsEmpty(aDocumentIsEmpty); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetTextLength(int32_t *aCount) michael@0: { michael@0: NS_ASSERTION(aCount, "null pointer"); michael@0: michael@0: // initialize out params michael@0: *aCount = 0; michael@0: michael@0: // special-case for empty document, to account for the bogus node michael@0: bool docEmpty; michael@0: nsresult rv = GetDocumentIsEmpty(&docEmpty); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (docEmpty) michael@0: return NS_OK; michael@0: michael@0: dom::Element *rootElement = GetRoot(); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t totalLength = 0; michael@0: iter->Init(rootElement); michael@0: for (; !iter->IsDone(); iter->Next()) { michael@0: nsCOMPtr currentNode = do_QueryInterface(iter->GetCurrentNode()); michael@0: nsCOMPtr textNode = do_QueryInterface(currentNode); michael@0: if (textNode && IsEditable(currentNode)) { michael@0: uint32_t length; michael@0: textNode->GetLength(&length); michael@0: totalLength += length; michael@0: } michael@0: } michael@0: michael@0: *aCount = totalLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::SetMaxTextLength(int32_t aMaxTextLength) michael@0: { michael@0: mMaxTextLength = aMaxTextLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetMaxTextLength(int32_t* aMaxTextLength) michael@0: { michael@0: NS_ENSURE_TRUE(aMaxTextLength, NS_ERROR_INVALID_POINTER); michael@0: *aMaxTextLength = mMaxTextLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Get the wrap width michael@0: // michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetWrapWidth(int32_t *aWrapColumn) michael@0: { michael@0: NS_ENSURE_TRUE( aWrapColumn, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aWrapColumn = mWrapColumn; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // See if the style value includes this attribute, and if it does, michael@0: // cut out everything from the attribute to the next semicolon. michael@0: // michael@0: static void CutStyle(const char* stylename, nsString& styleValue) michael@0: { michael@0: // Find the current wrapping type: michael@0: int32_t styleStart = styleValue.Find(stylename, true); michael@0: if (styleStart >= 0) michael@0: { michael@0: int32_t styleEnd = styleValue.Find(";", false, styleStart); michael@0: if (styleEnd > styleStart) michael@0: styleValue.Cut(styleStart, styleEnd - styleStart + 1); michael@0: else michael@0: styleValue.Cut(styleStart, styleValue.Length() - styleStart); michael@0: } michael@0: } michael@0: michael@0: // michael@0: // Change the wrap width on the root of this document. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::SetWrapWidth(int32_t aWrapColumn) michael@0: { michael@0: SetWrapColumn(aWrapColumn); michael@0: michael@0: // Make sure we're a plaintext editor, otherwise we shouldn't michael@0: // do the rest of this. michael@0: if (!IsPlaintextEditor()) michael@0: return NS_OK; michael@0: michael@0: // Ought to set a style sheet here ... michael@0: // Probably should keep around an mPlaintextStyleSheet for this purpose. michael@0: dom::Element *rootElement = GetRoot(); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Get the current style for this root element: michael@0: nsAutoString styleValue; michael@0: rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue); michael@0: michael@0: // We'll replace styles for these values: michael@0: CutStyle("white-space", styleValue); michael@0: CutStyle("width", styleValue); michael@0: CutStyle("font-family", styleValue); michael@0: michael@0: // If we have other style left, trim off any existing semicolons michael@0: // or whitespace, then add a known semicolon-space: michael@0: if (!styleValue.IsEmpty()) michael@0: { michael@0: styleValue.Trim("; \t", false, true); michael@0: styleValue.AppendLiteral("; "); michael@0: } michael@0: michael@0: // Make sure we have fixed-width font. This should be done for us, michael@0: // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;". michael@0: // Only do this if we're wrapping. michael@0: if (IsWrapHackEnabled() && aWrapColumn >= 0) michael@0: styleValue.AppendLiteral("font-family: -moz-fixed; "); michael@0: michael@0: // If "mail.compose.wrap_to_window_width" is set, and we're a mail editor, michael@0: // then remember our wrap width (for output purposes) but set the visual michael@0: // wrapping to window width. michael@0: // We may reset mWrapToWindow here, based on the pref's current value. michael@0: if (IsMailEditor()) michael@0: { michael@0: mWrapToWindow = michael@0: Preferences::GetBool("mail.compose.wrap_to_window_width", mWrapToWindow); michael@0: } michael@0: michael@0: // and now we're ready to set the new whitespace/wrapping style. michael@0: if (aWrapColumn > 0 && !mWrapToWindow) // Wrap to a fixed column michael@0: { michael@0: styleValue.AppendLiteral("white-space: pre-wrap; width: "); michael@0: styleValue.AppendInt(aWrapColumn); michael@0: styleValue.AppendLiteral("ch;"); michael@0: } michael@0: else if (mWrapToWindow || aWrapColumn == 0) michael@0: styleValue.AppendLiteral("white-space: pre-wrap;"); michael@0: else michael@0: styleValue.AppendLiteral("white-space: pre;"); michael@0: michael@0: return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue, true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::SetWrapColumn(int32_t aWrapColumn) michael@0: { michael@0: mWrapColumn = aWrapColumn; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Get the newline handling for this editor michael@0: // michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetNewlineHandling(int32_t *aNewlineHandling) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNewlineHandling); michael@0: michael@0: *aNewlineHandling = mNewlineHandling; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Change the newline handling for this editor michael@0: // michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::SetNewlineHandling(int32_t aNewlineHandling) michael@0: { michael@0: mNewlineHandling = aNewlineHandling; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::Undo(uint32_t aCount) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsAutoUpdateViewBatch beginViewBatching(this); michael@0: michael@0: ForceCompositionEnd(); michael@0: michael@0: nsAutoRules beginRulesSniffing(this, EditAction::undo, nsIEditor::eNone); michael@0: michael@0: nsTextRulesInfo ruleInfo(EditAction::undo); michael@0: nsRefPtr selection = GetSelection(); michael@0: bool cancel, handled; michael@0: nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: michael@0: if (!cancel && NS_SUCCEEDED(result)) michael@0: { michael@0: result = nsEditor::Undo(aCount); michael@0: result = mRules->DidDoAction(selection, &ruleInfo, result); michael@0: } michael@0: michael@0: NotifyEditorObservers(); michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::Redo(uint32_t aCount) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsAutoUpdateViewBatch beginViewBatching(this); michael@0: michael@0: ForceCompositionEnd(); michael@0: michael@0: nsAutoRules beginRulesSniffing(this, EditAction::redo, nsIEditor::eNone); michael@0: michael@0: nsTextRulesInfo ruleInfo(EditAction::redo); michael@0: nsRefPtr selection = GetSelection(); michael@0: bool cancel, handled; michael@0: nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: michael@0: if (!cancel && NS_SUCCEEDED(result)) michael@0: { michael@0: result = nsEditor::Redo(aCount); michael@0: result = mRules->DidDoAction(selection, &ruleInfo, result); michael@0: } michael@0: michael@0: NotifyEditorObservers(); michael@0: return result; michael@0: } michael@0: michael@0: bool michael@0: nsPlaintextEditor::CanCutOrCopy() michael@0: { michael@0: nsCOMPtr selection; michael@0: if (NS_FAILED(GetSelection(getter_AddRefs(selection)))) michael@0: return false; michael@0: michael@0: return !selection->Collapsed(); michael@0: } michael@0: michael@0: bool michael@0: nsPlaintextEditor::FireClipboardEvent(int32_t aType, int32_t aSelectionType) michael@0: { michael@0: if (aType == NS_PASTE) michael@0: ForceCompositionEnd(); michael@0: michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: NS_ENSURE_TRUE(presShell, false); michael@0: michael@0: nsCOMPtr selection; michael@0: if (NS_FAILED(GetSelection(getter_AddRefs(selection)))) michael@0: return false; michael@0: michael@0: if (!nsCopySupport::FireClipboardEvent(aType, aSelectionType, presShell, selection)) michael@0: return false; michael@0: michael@0: // If the event handler caused the editor to be destroyed, return false. michael@0: // Otherwise return true to indicate that the event was not cancelled. michael@0: return !mDidPreDestroy; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::Cut() michael@0: { michael@0: if (FireClipboardEvent(NS_CUT, nsIClipboard::kGlobalClipboard)) michael@0: return DeleteSelection(eNone, eStrip); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::CanCut(bool *aCanCut) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanCut); michael@0: *aCanCut = IsModifiable() && CanCutOrCopy(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::Copy() michael@0: { michael@0: FireClipboardEvent(NS_COPY, nsIClipboard::kGlobalClipboard); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::CanCopy(bool *aCanCopy) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanCopy); michael@0: *aCanCopy = CanCutOrCopy(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Shared between OutputToString and OutputToStream michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetAndInitDocEncoder(const nsAString& aFormatType, michael@0: uint32_t aFlags, michael@0: const nsACString& aCharset, michael@0: nsIDocumentEncoder** encoder) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE); michael@0: LossyAppendUTF16toASCII(aFormatType, formatType); michael@0: nsCOMPtr docEncoder (do_CreateInstance(formatType.get(), &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr domDoc = do_QueryReferent(mDocWeak); michael@0: NS_ASSERTION(domDoc, "Need a document"); michael@0: michael@0: rv = docEncoder->Init(domDoc, aFormatType, aFlags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) { michael@0: docEncoder->SetCharset(aCharset); michael@0: } michael@0: michael@0: int32_t wc; michael@0: (void) GetWrapWidth(&wc); michael@0: if (wc >= 0) michael@0: (void) docEncoder->SetWrapColumn(wc); michael@0: michael@0: // Set the selection, if appropriate. michael@0: // We do this either if the OutputSelectionOnly flag is set, michael@0: // in which case we use our existing selection ... michael@0: if (aFlags & nsIDocumentEncoder::OutputSelectionOnly) michael@0: { michael@0: nsCOMPtr selection; michael@0: rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (selection) { michael@0: rv = docEncoder->SetSelection(selection); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: // ... or if the root element is not a body, michael@0: // in which case we set the selection to encompass the root. michael@0: else michael@0: { michael@0: dom::Element* rootElement = GetRoot(); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE); michael@0: if (!rootElement->IsHTML(nsGkAtoms::body)) { michael@0: rv = docEncoder->SetNativeContainerNode(rootElement); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: docEncoder.forget(encoder); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::OutputToString(const nsAString& aFormatType, michael@0: uint32_t aFlags, michael@0: nsAString& aOutputString) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsString resultString; michael@0: nsTextRulesInfo ruleInfo(EditAction::outputText); michael@0: ruleInfo.outString = &resultString; michael@0: // XXX Struct should store a nsAReadable* michael@0: nsAutoString str(aFormatType); michael@0: ruleInfo.outputFormat = &str; michael@0: bool cancel, handled; michael@0: nsresult rv = mRules->WillDoAction(nullptr, &ruleInfo, &cancel, &handled); michael@0: if (cancel || NS_FAILED(rv)) { return rv; } michael@0: if (handled) michael@0: { // this case will get triggered by password fields michael@0: aOutputString.Assign(*(ruleInfo.outString)); michael@0: return rv; michael@0: } michael@0: michael@0: nsAutoCString charsetStr; michael@0: rv = GetDocumentCharacterSet(charsetStr); michael@0: if(NS_FAILED(rv) || charsetStr.IsEmpty()) michael@0: charsetStr.AssignLiteral("ISO-8859-1"); michael@0: michael@0: nsCOMPtr encoder; michael@0: rv = GetAndInitDocEncoder(aFormatType, aFlags, charsetStr, getter_AddRefs(encoder)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return encoder->EncodeToString(aOutputString); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::OutputToStream(nsIOutputStream* aOutputStream, michael@0: const nsAString& aFormatType, michael@0: const nsACString& aCharset, michael@0: uint32_t aFlags) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // special-case for empty document when requesting plain text, michael@0: // to account for the bogus text node. michael@0: // XXX Should there be a similar test in OutputToString? michael@0: if (aFormatType.EqualsLiteral("text/plain")) michael@0: { michael@0: bool docEmpty; michael@0: rv = GetDocumentIsEmpty(&docEmpty); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (docEmpty) michael@0: return NS_OK; // output nothing michael@0: } michael@0: michael@0: nsCOMPtr encoder; michael@0: rv = GetAndInitDocEncoder(aFormatType, aFlags, aCharset, michael@0: getter_AddRefs(encoder)); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return encoder->EncodeToStream(aOutputStream); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::InsertTextWithQuotations(const nsAString &aStringToInsert) michael@0: { michael@0: return InsertText(aStringToInsert); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::PasteAsQuotation(int32_t aSelectionType) michael@0: { michael@0: // Get Clipboard Service michael@0: nsresult rv; michael@0: nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get the nsITransferable interface for getting the data from the clipboard michael@0: nsCOMPtr trans; michael@0: rv = PrepareTransferable(getter_AddRefs(trans)); michael@0: if (NS_SUCCEEDED(rv) && trans) michael@0: { michael@0: // Get the Data from the clipboard michael@0: clipboard->GetData(trans, aSelectionType); michael@0: michael@0: // Now we ask the transferable for the data michael@0: // it still owns the data, we just have a pointer to it. michael@0: // If it can't support a "text" output of the data the call will fail michael@0: nsCOMPtr genericDataObj; michael@0: uint32_t len; michael@0: char* flav = nullptr; michael@0: rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj), michael@0: &len); michael@0: if (NS_FAILED(rv) || !flav) michael@0: { michael@0: #ifdef DEBUG_akkana michael@0: printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv); michael@0: #endif michael@0: return rv; michael@0: } michael@0: #ifdef DEBUG_clipboard michael@0: printf("Got flavor [%s]\n", flav); michael@0: #endif michael@0: if (0 == nsCRT::strcmp(flav, kUnicodeMime) || michael@0: 0 == nsCRT::strcmp(flav, kMozTextInternal)) michael@0: { michael@0: nsCOMPtr textDataObj ( do_QueryInterface(genericDataObj) ); michael@0: if (textDataObj && len > 0) michael@0: { michael@0: nsAutoString stuffToPaste; michael@0: textDataObj->GetData ( stuffToPaste ); michael@0: nsAutoEditBatch beginBatching(this); michael@0: rv = InsertAsQuotation(stuffToPaste, 0); michael@0: } michael@0: } michael@0: NS_Free(flav); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::InsertAsQuotation(const nsAString& aQuotedText, michael@0: nsIDOMNode **aNodeInserted) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: // Let the citer quote it for us: michael@0: nsString quotedStuff; michael@0: nsresult rv = nsInternetCiter::GetCiteString(aQuotedText, quotedStuff); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // It's best to put a blank line after the quoted text so that mails michael@0: // written without thinking won't be so ugly. michael@0: if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) michael@0: quotedStuff.Append(char16_t('\n')); michael@0: michael@0: // get selection michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertText, nsIEditor::eNext); michael@0: michael@0: // give rules a chance to handle or cancel michael@0: nsTextRulesInfo ruleInfo(EditAction::insertElement); michael@0: bool cancel, handled; michael@0: rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (cancel) return NS_OK; // rules canceled the operation michael@0: if (!handled) michael@0: { michael@0: rv = InsertText(quotedStuff); michael@0: michael@0: // XXX Should set *aNodeInserted to the first node inserted michael@0: if (aNodeInserted && NS_SUCCEEDED(rv)) michael@0: { michael@0: *aNodeInserted = 0; michael@0: //NS_IF_ADDREF(*aNodeInserted); michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::PasteAsCitedQuotation(const nsAString& aCitation, michael@0: int32_t aSelectionType) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::InsertAsCitedQuotation(const nsAString& aQuotedText, michael@0: const nsAString& aCitation, michael@0: bool aInsertHTML, michael@0: nsIDOMNode **aNodeInserted) michael@0: { michael@0: return InsertAsQuotation(aQuotedText, aNodeInserted); michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::SharedOutputString(uint32_t aFlags, michael@0: bool* aIsCollapsed, michael@0: nsAString& aResult) michael@0: { michael@0: nsCOMPtr selection; michael@0: nsresult rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: *aIsCollapsed = selection->Collapsed(); michael@0: michael@0: if (!*aIsCollapsed) michael@0: aFlags |= nsIDocumentEncoder::OutputSelectionOnly; michael@0: // If the selection isn't collapsed, we'll use the whole document. michael@0: michael@0: return OutputToString(NS_LITERAL_STRING("text/plain"), aFlags, aResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::Rewrap(bool aRespectNewlines) michael@0: { michael@0: int32_t wrapCol; michael@0: nsresult rv = GetWrapWidth(&wrapCol); michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); michael@0: michael@0: // Rewrap makes no sense if there's no wrap column; default to 72. michael@0: if (wrapCol <= 0) michael@0: wrapCol = 72; michael@0: michael@0: #ifdef DEBUG_akkana michael@0: printf("nsPlaintextEditor::Rewrap to %ld columns\n", (long)wrapCol); michael@0: #endif michael@0: michael@0: nsAutoString current; michael@0: bool isCollapsed; michael@0: rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted michael@0: | nsIDocumentEncoder::OutputLFLineBreak, michael@0: &isCollapsed, current); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString wrapped; michael@0: uint32_t firstLineOffset = 0; // XXX need to reset this if there is a selection michael@0: rv = nsInternetCiter::Rewrap(current, wrapCol, firstLineOffset, aRespectNewlines, michael@0: wrapped); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isCollapsed) // rewrap the whole document michael@0: SelectAll(); michael@0: michael@0: return InsertTextWithQuotations(wrapped); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::StripCites() michael@0: { michael@0: #ifdef DEBUG_akkana michael@0: printf("nsPlaintextEditor::StripCites()\n"); michael@0: #endif michael@0: michael@0: nsAutoString current; michael@0: bool isCollapsed; michael@0: nsresult rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted, michael@0: &isCollapsed, current); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString stripped; michael@0: rv = nsInternetCiter::StripCites(current, stripped); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isCollapsed) // rewrap the whole document michael@0: { michael@0: rv = SelectAll(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return InsertText(stripped); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList) michael@0: { michael@0: *aNodeList = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** All editor operations which alter the doc should be prefaced michael@0: * with a call to StartOperation, naming the action and direction */ michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::StartOperation(EditAction opID, michael@0: nsIEditor::EDirection aDirection) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection michael@0: if (mRules) return mRules->BeforeEdit(mAction, mDirection); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** All editor operations which alter the doc should be followed michael@0: * with a call to EndOperation */ michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::EndOperation() michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: // post processing michael@0: nsresult res = NS_OK; michael@0: if (mRules) res = mRules->AfterEdit(mAction, mDirection); michael@0: nsEditor::EndOperation(); // will clear mAction, mDirection michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPlaintextEditor::SelectEntireDocument(nsISelection *aSelection) michael@0: { michael@0: if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: // is doc empty? michael@0: bool bDocIsEmpty; michael@0: if (NS_SUCCEEDED(mRules->DocumentIsEmpty(&bDocIsEmpty)) && bDocIsEmpty) michael@0: { michael@0: // get root node michael@0: nsCOMPtr rootElement = do_QueryInterface(GetRoot()); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE); michael@0: michael@0: // if it's empty don't select entire doc - that would select the bogus node michael@0: return aSelection->Collapse(rootElement, 0); michael@0: } michael@0: michael@0: nsresult rv = nsEditor::SelectEntireDocument(aSelection); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Don't select the trailing BR node if we have one michael@0: int32_t selOffset; michael@0: nsCOMPtr selNode; michael@0: rv = GetEndNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr childNode = GetChildAt(selNode, selOffset - 1); michael@0: michael@0: if (childNode && nsTextEditUtils::IsMozBR(childNode)) { michael@0: int32_t parentOffset; michael@0: nsCOMPtr parentNode = GetNodeLocation(childNode, &parentOffset); michael@0: michael@0: return aSelection->Extend(parentNode, parentOffset); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsPlaintextEditor::GetDOMEventTarget() michael@0: { michael@0: nsCOMPtr copy = mEventTarget; michael@0: return copy.forget(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement, michael@0: const nsAString & aAttribute, michael@0: const nsAString & aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: return nsEditor::SetAttribute(aElement, aAttribute, aValue); michael@0: } michael@0: michael@0: nsresult michael@0: nsPlaintextEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement, michael@0: const nsAString & aAttribute, michael@0: bool aSuppressTransaction) michael@0: { michael@0: return nsEditor::RemoveAttribute(aElement, aAttribute); michael@0: }