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: #include "mozilla/Assertions.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/TextComposition.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsAString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsCRT.h" michael@0: #include "nsCRTGlue.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDebug.h" michael@0: #include "nsEditor.h" michael@0: #include "nsEditorUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMCharacterData.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMNodeFilter.h" michael@0: #include "nsIDOMNodeIterator.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIDOMText.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsINode.h" michael@0: #include "nsIPlaintextEditor.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsISupportsBase.h" michael@0: #include "nsLiteralString.h" michael@0: #include "mozilla/dom/NodeIterator.h" michael@0: #include "nsTextEditRules.h" michael@0: #include "nsTextEditUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \ michael@0: if (IsReadonly() || IsDisabled()) \ michael@0: { \ michael@0: *aCancel = true; \ michael@0: return NS_OK; \ michael@0: }; michael@0: michael@0: michael@0: /******************************************************** michael@0: * Constructor/Destructor michael@0: ********************************************************/ michael@0: michael@0: nsTextEditRules::nsTextEditRules() michael@0: { michael@0: InitFields(); michael@0: } michael@0: michael@0: void michael@0: nsTextEditRules::InitFields() michael@0: { michael@0: mEditor = nullptr; michael@0: mPasswordText.Truncate(); michael@0: mPasswordIMEText.Truncate(); michael@0: mPasswordIMEIndex = 0; michael@0: mBogusNode = nullptr; michael@0: mCachedSelectionNode = nullptr; michael@0: mCachedSelectionOffset = 0; michael@0: mActionNesting = 0; michael@0: mLockRulesSniffing = false; michael@0: mDidExplicitlySetInterline = false; michael@0: mDeleteBidiImmediately = false; michael@0: mTheAction = EditAction::none; michael@0: mTimer = nullptr; michael@0: mLastStart = 0; michael@0: mLastLength = 0; michael@0: } michael@0: michael@0: nsTextEditRules::~nsTextEditRules() michael@0: { michael@0: // do NOT delete mEditor here. We do not hold a ref count to mEditor. mEditor owns our lifespan. michael@0: michael@0: if (mTimer) michael@0: mTimer->Cancel(); michael@0: } michael@0: michael@0: /******************************************************** michael@0: * XPCOM Cruft michael@0: ********************************************************/ michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsTextEditRules, mBogusNode, mCachedSelectionNode) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTextEditRules) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEditRules) michael@0: NS_INTERFACE_MAP_ENTRY(nsITimerCallback) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditRules) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextEditRules) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextEditRules) michael@0: michael@0: /******************************************************** michael@0: * Public methods michael@0: ********************************************************/ michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::Init(nsPlaintextEditor *aEditor) michael@0: { michael@0: if (!aEditor) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: InitFields(); michael@0: michael@0: mEditor = aEditor; // we hold a non-refcounted reference back to our editor michael@0: nsCOMPtr selection; michael@0: mEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_WARN_IF_FALSE(selection, "editor cannot get selection"); michael@0: michael@0: // Put in a magic br if needed. This method handles null selection, michael@0: // which should never happen anyway michael@0: nsresult res = CreateBogusNodeIfNeeded(selection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // If the selection hasn't been set up yet, set it up collapsed to the end of michael@0: // our editable content. michael@0: int32_t rangeCount; michael@0: res = selection->GetRangeCount(&rangeCount); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!rangeCount) { michael@0: res = mEditor->EndOfDocument(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: if (IsPlaintextEditor()) michael@0: { michael@0: // ensure trailing br node michael@0: res = CreateTrailingBRIfNeeded(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: mDeleteBidiImmediately = michael@0: Preferences::GetBool("bidi.edit.delete_immediately", false); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::SetInitialValue(const nsAString& aValue) michael@0: { michael@0: if (IsPasswordEditor()) { michael@0: mPasswordText = aValue; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::DetachEditor() michael@0: { michael@0: if (mTimer) michael@0: mTimer->Cancel(); michael@0: michael@0: mEditor = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::BeforeEdit(EditAction action, michael@0: nsIEditor::EDirection aDirection) michael@0: { michael@0: if (mLockRulesSniffing) return NS_OK; michael@0: michael@0: nsAutoLockRulesSniffing lockIt(this); michael@0: mDidExplicitlySetInterline = false; michael@0: if (!mActionNesting) michael@0: { michael@0: // let rules remember the top level action michael@0: mTheAction = action; michael@0: } michael@0: mActionNesting++; michael@0: michael@0: // get the selection and cache the position before editing michael@0: nsCOMPtr selection; michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsresult res = mEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: selection->GetAnchorNode(getter_AddRefs(mCachedSelectionNode)); michael@0: selection->GetAnchorOffset(&mCachedSelectionOffset); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::AfterEdit(EditAction action, michael@0: nsIEditor::EDirection aDirection) michael@0: { michael@0: if (mLockRulesSniffing) return NS_OK; michael@0: michael@0: nsAutoLockRulesSniffing lockIt(this); michael@0: michael@0: NS_PRECONDITION(mActionNesting>0, "bad action nesting!"); michael@0: nsresult res = NS_OK; michael@0: if (!--mActionNesting) michael@0: { michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->HandleInlineSpellCheck(action, selection, michael@0: mCachedSelectionNode, mCachedSelectionOffset, michael@0: nullptr, 0, nullptr, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // if only trailing
remaining remove it michael@0: res = RemoveRedundantTrailingBR(); michael@0: if (NS_FAILED(res)) michael@0: return res; michael@0: michael@0: // detect empty doc michael@0: res = CreateBogusNodeIfNeeded(selection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // ensure trailing br node michael@0: res = CreateTrailingBRIfNeeded(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // collapse the selection to the trailing BR if it's at the end of our text node michael@0: CollapseSelectionToTrailingBRIfNeeded(selection); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::WillDoAction(Selection* aSelection, michael@0: nsRulesInfo* aInfo, michael@0: bool* aCancel, michael@0: bool* aHandled) michael@0: { michael@0: // null selection is legal michael@0: MOZ_ASSERT(aInfo && aCancel && aHandled); michael@0: michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: // my kingdom for dynamic cast michael@0: nsTextRulesInfo *info = static_cast(aInfo); michael@0: michael@0: switch (info->action) { michael@0: case EditAction::insertBreak: michael@0: return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength); michael@0: case EditAction::insertText: michael@0: case EditAction::insertIMEText: michael@0: return WillInsertText(info->action, aSelection, aCancel, aHandled, michael@0: info->inString, info->outString, info->maxLength); michael@0: case EditAction::deleteSelection: michael@0: return WillDeleteSelection(aSelection, info->collapsedAction, michael@0: aCancel, aHandled); michael@0: case EditAction::undo: michael@0: return WillUndo(aSelection, aCancel, aHandled); michael@0: case EditAction::redo: michael@0: return WillRedo(aSelection, aCancel, aHandled); michael@0: case EditAction::setTextProperty: michael@0: return WillSetTextProperty(aSelection, aCancel, aHandled); michael@0: case EditAction::removeTextProperty: michael@0: return WillRemoveTextProperty(aSelection, aCancel, aHandled); michael@0: case EditAction::outputText: michael@0: return WillOutputText(aSelection, info->outputFormat, info->outString, michael@0: aCancel, aHandled); michael@0: case EditAction::insertElement: michael@0: // i had thought this would be html rules only. but we put pre elements michael@0: // into plaintext mail when doing quoting for reply! doh! michael@0: return WillInsert(aSelection, aCancel); michael@0: default: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::DidDoAction(nsISelection *aSelection, michael@0: nsRulesInfo *aInfo, nsresult aResult) michael@0: { michael@0: NS_ENSURE_STATE(mEditor); michael@0: // don't let any txns in here move the selection around behind our back. michael@0: // Note that this won't prevent explicit selection setting from working. michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); michael@0: michael@0: NS_ENSURE_TRUE(aSelection && aInfo, NS_ERROR_NULL_POINTER); michael@0: michael@0: // my kingdom for dynamic cast michael@0: nsTextRulesInfo *info = static_cast(aInfo); michael@0: michael@0: switch (info->action) michael@0: { michael@0: case EditAction::insertBreak: michael@0: return DidInsertBreak(aSelection, aResult); michael@0: case EditAction::insertText: michael@0: case EditAction::insertIMEText: michael@0: return DidInsertText(aSelection, aResult); michael@0: case EditAction::deleteSelection: michael@0: return DidDeleteSelection(aSelection, info->collapsedAction, aResult); michael@0: case EditAction::undo: michael@0: return DidUndo(aSelection, aResult); michael@0: case EditAction::redo: michael@0: return DidRedo(aSelection, aResult); michael@0: case EditAction::setTextProperty: michael@0: return DidSetTextProperty(aSelection, aResult); michael@0: case EditAction::removeTextProperty: michael@0: return DidRemoveTextProperty(aSelection, aResult); michael@0: case EditAction::outputText: michael@0: return DidOutputText(aSelection, aResult); michael@0: default: michael@0: // Don't fail on transactions we don't handle here! michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::DocumentIsEmpty(bool *aDocumentIsEmpty) michael@0: { michael@0: NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aDocumentIsEmpty = (mBogusNode != nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /******************************************************** michael@0: * Protected methods michael@0: ********************************************************/ michael@0: michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillInsert(nsISelection *aSelection, bool *aCancel) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER); michael@0: michael@0: CANCEL_OPERATION_IF_READONLY_OR_DISABLED michael@0: michael@0: // initialize out param michael@0: *aCancel = false; michael@0: michael@0: // check for the magic content node and delete it if it exists michael@0: if (mBogusNode) michael@0: { michael@0: NS_ENSURE_STATE(mEditor); michael@0: mEditor->DeleteNode(mBogusNode); michael@0: mBogusNode = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillInsertBreak(Selection* aSelection, michael@0: bool *aCancel, michael@0: bool *aHandled, michael@0: int32_t aMaxLength) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: CANCEL_OPERATION_IF_READONLY_OR_DISABLED michael@0: *aHandled = false; michael@0: if (IsSingleLineEditor()) { michael@0: *aCancel = true; michael@0: } michael@0: else michael@0: { michael@0: // handle docs with a max length michael@0: // NOTE, this function copies inString into outString for us. michael@0: NS_NAMED_LITERAL_STRING(inString, "\n"); michael@0: nsAutoString outString; michael@0: bool didTruncate; michael@0: nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString, michael@0: aMaxLength, &didTruncate); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (didTruncate) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aCancel = false; michael@0: michael@0: // if the selection isn't collapsed, delete it. michael@0: bool bCollapsed; michael@0: res = aSelection->GetIsCollapsed(&bCollapsed); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!bCollapsed) michael@0: { michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection) michael@0: { michael@0: // we only need to execute the stuff below if we are a plaintext editor. michael@0: // html editors have a different mechanism for putting in mozBR's michael@0: // (because there are a bunch more places you have to worry about it in html) michael@0: if (!IsPlaintextEditor()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // if we are at the end of the textarea, we need to set the michael@0: // selection to stick to the mozBR at the end of the textarea. michael@0: int32_t selOffset; michael@0: nsCOMPtr selNode; michael@0: nsresult res; michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr nodeAsText = do_QueryInterface(selNode); michael@0: if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node michael@0: michael@0: uint32_t length; michael@0: res = nodeAsText->GetLength(&length); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // nothing to do if we're not at the end of the text node michael@0: if (selOffset != int32_t(length)) michael@0: return NS_OK; michael@0: michael@0: int32_t parentOffset; michael@0: nsCOMPtr parentNode = nsEditor::GetNodeLocation(selNode, &parentOffset); michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsCOMPtr root = do_QueryInterface(mEditor->GetRoot()); michael@0: NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); michael@0: if (parentNode != root) return NS_OK; michael@0: michael@0: nsCOMPtr nextNode = mEditor->GetChildAt(parentNode, michael@0: parentOffset + 1); michael@0: if (nextNode && nsTextEditUtils::IsMozBR(nextNode)) michael@0: { michael@0: res = aSelection->Collapse(parentNode, parentOffset + 1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: static inline already_AddRefed michael@0: GetTextNode(nsISelection *selection, nsEditor *editor) { michael@0: int32_t selOffset; michael@0: nsCOMPtr selNode; michael@0: nsresult res = editor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, nullptr); michael@0: if (!editor->IsTextNode(selNode)) { michael@0: // Get an nsINode from the nsIDOMNode michael@0: nsCOMPtr node = do_QueryInterface(selNode); michael@0: // if node is null, return it to indicate there's no text michael@0: NS_ENSURE_TRUE(node, nullptr); michael@0: // This should be the root node, walk the tree looking for text nodes michael@0: mozilla::dom::NodeFilterHolder filter; michael@0: mozilla::dom::NodeIterator iter(node, nsIDOMNodeFilter::SHOW_TEXT, filter); michael@0: while (!editor->IsTextNode(selNode)) { michael@0: if (NS_FAILED(res = iter.NextNode(getter_AddRefs(selNode))) || !selNode) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: } michael@0: return selNode.forget(); michael@0: } michael@0: #ifdef DEBUG michael@0: #define ASSERT_PASSWORD_LENGTHS_EQUAL() \ michael@0: if (IsPasswordEditor() && mEditor->GetRoot()) { \ michael@0: int32_t txtLen; \ michael@0: mEditor->GetTextLength(&txtLen); \ michael@0: NS_ASSERTION(mPasswordText.Length() == uint32_t(txtLen), \ michael@0: "password length not equal to number of asterisks"); \ michael@0: } michael@0: #else michael@0: #define ASSERT_PASSWORD_LENGTHS_EQUAL() michael@0: #endif michael@0: michael@0: // static michael@0: void michael@0: nsTextEditRules::HandleNewLines(nsString &aString, michael@0: int32_t aNewlineHandling) michael@0: { michael@0: if (aNewlineHandling < 0) { michael@0: int32_t caretStyle; michael@0: nsPlaintextEditor::GetDefaultEditorPrefs(aNewlineHandling, caretStyle); michael@0: } michael@0: michael@0: switch(aNewlineHandling) michael@0: { michael@0: case nsIPlaintextEditor::eNewlinesReplaceWithSpaces: michael@0: // Strip trailing newlines first so we don't wind up with trailing spaces michael@0: aString.Trim(CRLF, false, true); michael@0: aString.ReplaceChar(CRLF, ' '); michael@0: break; michael@0: case nsIPlaintextEditor::eNewlinesStrip: michael@0: aString.StripChars(CRLF); michael@0: break; michael@0: case nsIPlaintextEditor::eNewlinesPasteToFirst: michael@0: default: michael@0: { michael@0: int32_t firstCRLF = aString.FindCharInSet(CRLF); michael@0: michael@0: // we get first *non-empty* line. michael@0: int32_t offset = 0; michael@0: while (firstCRLF == offset) michael@0: { michael@0: offset++; michael@0: firstCRLF = aString.FindCharInSet(CRLF, offset); michael@0: } michael@0: if (firstCRLF > 0) michael@0: aString.Truncate(firstCRLF); michael@0: if (offset > 0) michael@0: aString.Cut(0, offset); michael@0: } michael@0: break; michael@0: case nsIPlaintextEditor::eNewlinesReplaceWithCommas: michael@0: aString.Trim(CRLF, true, true); michael@0: aString.ReplaceChar(CRLF, ','); michael@0: break; michael@0: case nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace: michael@0: { michael@0: nsString result; michael@0: uint32_t offset = 0; michael@0: while (offset < aString.Length()) michael@0: { michael@0: int32_t nextCRLF = aString.FindCharInSet(CRLF, offset); michael@0: if (nextCRLF < 0) { michael@0: result.Append(nsDependentSubstring(aString, offset)); michael@0: break; michael@0: } michael@0: uint32_t wsBegin = nextCRLF; michael@0: // look backwards for the first non-whitespace char michael@0: while (wsBegin > offset && NS_IS_SPACE(aString[wsBegin - 1])) michael@0: --wsBegin; michael@0: result.Append(nsDependentSubstring(aString, offset, wsBegin - offset)); michael@0: offset = nextCRLF + 1; michael@0: while (offset < aString.Length() && NS_IS_SPACE(aString[offset])) michael@0: ++offset; michael@0: } michael@0: aString = result; michael@0: } michael@0: break; michael@0: case nsIPlaintextEditor::eNewlinesPasteIntact: michael@0: // even if we're pasting newlines, don't paste leading/trailing ones michael@0: aString.Trim(CRLF, true, true); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillInsertText(EditAction aAction, michael@0: Selection* aSelection, michael@0: bool *aCancel, michael@0: bool *aHandled, michael@0: const nsAString *inString, michael@0: nsAString *outString, michael@0: int32_t aMaxLength) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: if (inString->IsEmpty() && aAction != EditAction::insertIMEText) { michael@0: // HACK: this is a fix for bug 19395 michael@0: // I can't outlaw all empty insertions michael@0: // because IME transaction depend on them michael@0: // There is more work to do to make the michael@0: // world safe for IME. michael@0: *aCancel = true; michael@0: *aHandled = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: michael@0: // handle docs with a max length michael@0: // NOTE, this function copies inString into outString for us. michael@0: bool truncated = false; michael@0: nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, michael@0: aMaxLength, &truncated); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // If we're exceeding the maxlength when composing IME, we need to clean up michael@0: // the composing text, so we shouldn't return early. michael@0: if (truncated && outString->IsEmpty() && michael@0: aAction != EditAction::insertIMEText) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t start = 0; michael@0: int32_t end = 0; michael@0: michael@0: // handle password field docs michael@0: if (IsPasswordEditor()) { michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(), michael@0: start, end); michael@0: } michael@0: michael@0: // if the selection isn't collapsed, delete it. michael@0: bool bCollapsed; michael@0: res = aSelection->GetIsCollapsed(&bCollapsed); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!bCollapsed) michael@0: { michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: michael@0: // handle password field data michael@0: // this has the side effect of changing all the characters in aOutString michael@0: // to the replacement character michael@0: if (IsPasswordEditor()) michael@0: { michael@0: if (aAction == EditAction::insertIMEText) { michael@0: RemoveIMETextFromPWBuf(start, outString); michael@0: } michael@0: } michael@0: michael@0: // People have lots of different ideas about what text fields michael@0: // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935. michael@0: // The six possible options are: michael@0: // 0. paste newlines intact michael@0: // 1. paste up to the first newline (default) michael@0: // 2. replace newlines with spaces michael@0: // 3. strip newlines michael@0: // 4. replace with commas michael@0: // 5. strip newlines and surrounding whitespace michael@0: // So find out what we're expected to do: michael@0: if (IsSingleLineEditor()) michael@0: { michael@0: nsAutoString tString(*outString); michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: HandleNewLines(tString, mEditor->mNewlineHandling); michael@0: michael@0: outString->Assign(tString); michael@0: } michael@0: michael@0: if (IsPasswordEditor()) michael@0: { michael@0: // manage the password buffer michael@0: mPasswordText.Insert(*outString, start); michael@0: michael@0: if (LookAndFeel::GetEchoPassword() && !DontEchoPassword()) { michael@0: HideLastPWInput(); michael@0: mLastStart = start; michael@0: mLastLength = outString->Length(); michael@0: if (mTimer) michael@0: { michael@0: mTimer->Cancel(); michael@0: } michael@0: else michael@0: { michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: mTimer->InitWithCallback(this, LookAndFeel::GetPasswordMaskDelay(), michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: else michael@0: { michael@0: FillBufWithPWChars(outString, outString->Length()); michael@0: } michael@0: } michael@0: michael@0: // get the (collapsed) selection location michael@0: nsCOMPtr selNode; michael@0: int32_t selOffset; michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->GetStartNodeAndOffset(aSelection, 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: NS_ENSURE_STATE(mEditor); michael@0: if (!mEditor->IsTextNode(selNode) && michael@0: !mEditor->CanContainTag(selNode, nsGkAtoms::textTagName)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // we need to get the doc michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsCOMPtr doc = mEditor->GetDOMDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (aAction == EditAction::insertIMEText) { michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: // aAction == EditAction::insertText; find where we are michael@0: nsCOMPtr curNode = selNode; michael@0: int32_t curOffset = selOffset; michael@0: michael@0: // don't spaz my selection in subtransactions michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); michael@0: michael@0: res = mEditor->InsertTextImpl(*outString, address_of(curNode), michael@0: &curOffset, doc); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (curNode) michael@0: { michael@0: // Make the caret attach to the inserted text, unless this text ends with a LF, michael@0: // in which case make the caret attach to the next line. michael@0: bool endsWithLF = michael@0: !outString->IsEmpty() && outString->Last() == nsCRT::LF; michael@0: aSelection->SetInterlinePosition(endsWithLF); michael@0: michael@0: aSelection->Collapse(curNode, curOffset); michael@0: } michael@0: } michael@0: ASSERT_PASSWORD_LENGTHS_EQUAL() michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidInsertText(nsISelection *aSelection, michael@0: nsresult aResult) michael@0: { michael@0: return DidInsert(aSelection, aResult); michael@0: } michael@0: michael@0: michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillSetTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) michael@0: { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: // XXX: should probably return a success value other than NS_OK that means "not allowed" michael@0: if (IsPlaintextEditor()) { michael@0: *aCancel = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidSetTextProperty(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillRemoveTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) michael@0: { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: // XXX: should probably return a success value other than NS_OK that means "not allowed" michael@0: if (IsPlaintextEditor()) { michael@0: *aCancel = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidRemoveTextProperty(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillDeleteSelection(Selection* aSelection, michael@0: nsIEditor::EDirection aCollapsedAction, michael@0: bool *aCancel, michael@0: bool *aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: CANCEL_OPERATION_IF_READONLY_OR_DISABLED michael@0: michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: // if there is only bogus content, cancel the operation michael@0: if (mBogusNode) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult res = NS_OK; michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: michael@0: if (IsPasswordEditor()) michael@0: { michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // manage the password buffer michael@0: int32_t start, end; michael@0: nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(), michael@0: start, end); michael@0: michael@0: if (LookAndFeel::GetEchoPassword()) { michael@0: HideLastPWInput(); michael@0: mLastStart = start; michael@0: mLastLength = 0; michael@0: if (mTimer) michael@0: { michael@0: mTimer->Cancel(); michael@0: } michael@0: } michael@0: michael@0: if (end == start) michael@0: { // collapsed selection michael@0: if (nsIEditor::ePrevious==aCollapsedAction && 0 startNode; michael@0: int32_t startOffset; michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: michael@0: bool bCollapsed; michael@0: res = aSelection->GetIsCollapsed(&bCollapsed); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!bCollapsed) michael@0: return NS_OK; michael@0: michael@0: // Test for distance between caret and text that will be deleted michael@0: res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aCollapsedAction, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (*aCancel) return NS_OK; michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: res = mEditor->DeleteSelectionImpl(aCollapsedAction, nsIEditor::eStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: *aHandled = true; michael@0: ASSERT_PASSWORD_LENGTHS_EQUAL() michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidDeleteSelection(nsISelection *aSelection, michael@0: nsIEditor::EDirection aCollapsedAction, michael@0: nsresult aResult) michael@0: { michael@0: nsCOMPtr startNode; michael@0: int32_t startOffset; michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: michael@0: // delete empty text nodes at selection michael@0: if (mEditor->IsTextNode(startNode)) michael@0: { michael@0: nsCOMPtr textNode = do_QueryInterface(startNode); michael@0: uint32_t strLength; michael@0: res = textNode->GetLength(&strLength); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // are we in an empty text node? michael@0: if (!strLength) michael@0: { michael@0: res = mEditor->DeleteNode(startNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: if (!mDidExplicitlySetInterline) michael@0: { michael@0: // We prevent the caret from sticking on the left of prior BR michael@0: // (i.e. the end of previous line) after this deletion. Bug 92124 michael@0: nsCOMPtr selPriv = do_QueryInterface(aSelection); michael@0: if (selPriv) res = selPriv->SetInterlinePosition(true); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillUndo(nsISelection *aSelection, bool *aCancel, bool *aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: CANCEL_OPERATION_IF_READONLY_OR_DISABLED michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* the idea here is to see if the magic empty node has suddenly reappeared as the result of the undo. michael@0: * if it has, set our state so we remember it. michael@0: * There is a tradeoff between doing here and at redo, or doing it everywhere else that might care. michael@0: * Since undo and redo are relatively rare, it makes sense to take the (small) performance hit here. michael@0: */ michael@0: nsresult michael@0: nsTextEditRules::DidUndo(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); michael@0: // If aResult is an error, we return it. michael@0: NS_ENSURE_SUCCESS(aResult, aResult); michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: dom::Element* theRoot = mEditor->GetRoot(); michael@0: NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); michael@0: nsIContent* node = mEditor->GetLeftmostChild(theRoot); michael@0: if (node && mEditor->IsMozEditorBogusNode(node)) { michael@0: mBogusNode = do_QueryInterface(node); michael@0: } else { michael@0: mBogusNode = nullptr; michael@0: } michael@0: return aResult; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillRedo(nsISelection *aSelection, bool *aCancel, bool *aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: CANCEL_OPERATION_IF_READONLY_OR_DISABLED michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: nsresult res = aResult; // if aResult is an error, we return it. michael@0: if (!aSelection) { return NS_ERROR_NULL_POINTER; } michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsCOMPtr theRoot = do_QueryInterface(mEditor->GetRoot()); michael@0: NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr nodeList; michael@0: res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"), michael@0: getter_AddRefs(nodeList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (nodeList) michael@0: { michael@0: uint32_t len; michael@0: nodeList->GetLength(&len); michael@0: michael@0: if (len != 1) { michael@0: // only in the case of one br could there be the bogus node michael@0: mBogusNode = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr node; michael@0: nodeList->Item(0, getter_AddRefs(node)); michael@0: nsCOMPtr content = do_QueryInterface(node); michael@0: MOZ_ASSERT(content); michael@0: if (mEditor->IsMozEditorBogusNode(content)) { michael@0: mBogusNode = node; michael@0: } else { michael@0: mBogusNode = nullptr; michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::WillOutputText(nsISelection *aSelection, michael@0: const nsAString *aOutputFormat, michael@0: nsAString *aOutString, michael@0: bool *aCancel, michael@0: bool *aHandled) michael@0: { michael@0: // null selection ok michael@0: if (!aOutString || !aOutputFormat || !aCancel || !aHandled) michael@0: { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: nsAutoString outputFormat(*aOutputFormat); michael@0: ToLowerCase(outputFormat); michael@0: if (outputFormat.EqualsLiteral("text/plain")) michael@0: { // only use these rules for plain text output michael@0: if (IsPasswordEditor()) michael@0: { michael@0: *aOutString = mPasswordText; michael@0: *aHandled = true; michael@0: } michael@0: else if (mBogusNode) michael@0: { // this means there's no content, so output null string michael@0: aOutString->Truncate(); michael@0: *aHandled = true; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::DidOutputText(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::RemoveRedundantTrailingBR() michael@0: { michael@0: // If the bogus node exists, we have no work to do michael@0: if (mBogusNode) michael@0: return NS_OK; michael@0: michael@0: // Likewise, nothing to be done if we could never have inserted a trailing br michael@0: if (IsSingleLineEditor()) michael@0: return NS_OK; michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsRefPtr body = mEditor->GetRoot(); michael@0: if (!body) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: uint32_t childCount = body->GetChildCount(); michael@0: if (childCount > 1) { michael@0: // The trailing br is redundant if it is the only remaining child node michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr child = body->GetFirstChild(); michael@0: if (!child || !child->IsElement()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: dom::Element* elem = child->AsElement(); michael@0: if (!nsTextEditUtils::IsMozBR(elem)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Rather than deleting this node from the DOM tree we should instead michael@0: // morph this br into the bogus node michael@0: elem->UnsetAttr(kNameSpaceID_None, nsGkAtoms::type, true); michael@0: michael@0: // set mBogusNode to be this
michael@0: mBogusNode = do_QueryInterface(elem); michael@0: michael@0: // give it the bogus node attribute michael@0: elem->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, michael@0: kMOZEditorBogusNodeValue, false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::CreateTrailingBRIfNeeded() michael@0: { michael@0: // but only if we aren't a single line edit field michael@0: if (IsSingleLineEditor()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: dom::Element* body = mEditor->GetRoot(); michael@0: NS_ENSURE_TRUE(body, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsIContent* lastChild = body->GetLastChild(); michael@0: // assuming CreateBogusNodeIfNeeded() has been called first michael@0: NS_ENSURE_TRUE(lastChild, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (!lastChild->IsHTML(nsGkAtoms::br)) { michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); michael@0: nsCOMPtr domBody = do_QueryInterface(body); michael@0: return CreateMozBR(domBody, body->Length()); michael@0: } michael@0: michael@0: // Check to see if the trailing BR is a former bogus node - this will have michael@0: // stuck around if we previously morphed a trailing node into a bogus node. michael@0: if (!mEditor->IsMozEditorBogusNode(lastChild)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Morph it back to a mozBR michael@0: lastChild->UnsetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, false); michael@0: lastChild->SetAttr(kNameSpaceID_None, nsGkAtoms::type, michael@0: NS_LITERAL_STRING("_moz"), true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection *aSelection) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(mEditor, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (mBogusNode) { michael@0: // Let's not create more than one, ok? michael@0: return NS_OK; michael@0: } michael@0: michael@0: // tell rules system to not do any post-processing michael@0: nsAutoRules beginRulesSniffing(mEditor, EditAction::ignore, nsIEditor::eNone); michael@0: michael@0: nsCOMPtr body = mEditor->GetRoot(); michael@0: if (!body) { michael@0: // We don't even have a body yet, don't insert any bogus nodes at michael@0: // this point. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Now we've got the body element. Iterate over the body element's children, michael@0: // looking for editable content. If no editable content is found, insert the michael@0: // bogus node. michael@0: for (nsCOMPtr bodyChild = body->GetFirstChild(); michael@0: bodyChild; michael@0: bodyChild = bodyChild->GetNextSibling()) { michael@0: if (mEditor->IsMozEditorBogusNode(bodyChild) || michael@0: !mEditor->IsEditable(body) || // XXX hoist out of the loop? michael@0: mEditor->IsEditable(bodyChild)) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Skip adding the bogus node if body is read-only. michael@0: if (!mEditor->IsModifiableNode(body)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Create a br. michael@0: nsCOMPtr newContent; michael@0: nsresult rv = mEditor->CreateHTMLContent(NS_LITERAL_STRING("br"), getter_AddRefs(newContent)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // set mBogusNode to be the newly created
michael@0: mBogusNode = do_QueryInterface(newContent); michael@0: NS_ENSURE_TRUE(mBogusNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Give it a special attribute. michael@0: newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, michael@0: kMOZEditorBogusNodeValue, false); michael@0: michael@0: // Put the node in the document. michael@0: nsCOMPtr bodyNode = do_QueryInterface(body); michael@0: rv = mEditor->InsertNode(mBogusNode, bodyNode, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Set selection. michael@0: aSelection->CollapseNative(body, 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsTextEditRules::TruncateInsertionIfNeeded(Selection* aSelection, michael@0: const nsAString *aInString, michael@0: nsAString *aOutString, michael@0: int32_t aMaxLength, michael@0: bool *aTruncated) michael@0: { michael@0: if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;} michael@0: michael@0: nsresult res = NS_OK; michael@0: *aOutString = *aInString; michael@0: if (aTruncated) { michael@0: *aTruncated = false; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() ) michael@0: { michael@0: // Get the current text length. michael@0: // Get the length of inString. michael@0: // Get the length of the selection. michael@0: // If selection is collapsed, it is length 0. michael@0: // Subtract the length of the selection from the len(doc) michael@0: // since we'll delete the selection on insert. michael@0: // This is resultingDocLength. michael@0: // Get old length of IME composing string michael@0: // which will be replaced by new one. michael@0: // If (resultingDocLength) is at or over max, cancel the insert michael@0: // If (resultingDocLength) + (length of input) > max, michael@0: // set aOutString to subset of inString so length = max michael@0: int32_t docLength; michael@0: res = mEditor->GetTextLength(&docLength); michael@0: if (NS_FAILED(res)) { return res; } michael@0: michael@0: int32_t start, end; michael@0: nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(), michael@0: start, end); michael@0: michael@0: TextComposition* composition = mEditor->GetComposition(); michael@0: int32_t oldCompStrLength = composition ? composition->String().Length() : 0; michael@0: michael@0: const int32_t selectionLength = end - start; michael@0: const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength; michael@0: if (resultingDocLength >= aMaxLength) michael@0: { michael@0: aOutString->Truncate(); michael@0: if (aTruncated) { michael@0: *aTruncated = true; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: int32_t inCount = aOutString->Length(); michael@0: if (inCount + resultingDocLength > aMaxLength) michael@0: { michael@0: aOutString->Truncate(aMaxLength - resultingDocLength); michael@0: if (aTruncated) { michael@0: *aTruncated = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: void michael@0: nsTextEditRules::ResetIMETextPWBuf() michael@0: { michael@0: mPasswordIMEText.Truncate(); michael@0: } michael@0: michael@0: void michael@0: nsTextEditRules::RemoveIMETextFromPWBuf(int32_t &aStart, nsAString *aIMEString) michael@0: { michael@0: MOZ_ASSERT(aIMEString); michael@0: michael@0: // initialize PasswordIME michael@0: if (mPasswordIMEText.IsEmpty()) { michael@0: mPasswordIMEIndex = aStart; michael@0: } michael@0: else { michael@0: // manage the password buffer michael@0: mPasswordText.Cut(mPasswordIMEIndex, mPasswordIMEText.Length()); michael@0: aStart = mPasswordIMEIndex; michael@0: } michael@0: michael@0: mPasswordIMEText.Assign(*aIMEString); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTextEditRules::Notify(nsITimer *) michael@0: { michael@0: MOZ_ASSERT(mTimer); michael@0: michael@0: // Check whether our text editor's password flag was changed before this michael@0: // "hide password character" timer actually fires. michael@0: nsresult res = IsPasswordEditor() ? HideLastPWInput() : NS_OK; michael@0: ASSERT_PASSWORD_LENGTHS_EQUAL(); michael@0: mLastLength = 0; michael@0: return res; michael@0: } michael@0: michael@0: nsresult nsTextEditRules::HideLastPWInput() { michael@0: if (!mLastLength) { michael@0: // Special case, we're trying to replace a range that no longer exists michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString hiddenText; michael@0: FillBufWithPWChars(&hiddenText, mLastLength); michael@0: michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsRefPtr selection = mEditor->GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: int32_t start, end; michael@0: nsContentUtils::GetSelectionInTextControl(selection, mEditor->GetRoot(), michael@0: start, end); michael@0: michael@0: nsCOMPtr selNode = GetTextNode(selection, mEditor); michael@0: NS_ENSURE_TRUE(selNode, NS_OK); michael@0: michael@0: nsCOMPtr nodeAsText(do_QueryInterface(selNode)); michael@0: NS_ENSURE_TRUE(nodeAsText, NS_OK); michael@0: michael@0: nodeAsText->ReplaceData(mLastStart, mLastLength, hiddenText); michael@0: selection->Collapse(selNode, start); michael@0: if (start != end) michael@0: selection->Extend(selNode, end); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsTextEditRules::FillBufWithPWChars(nsAString *aOutString, int32_t aLength) michael@0: { michael@0: MOZ_ASSERT(aOutString); michael@0: michael@0: // change the output to the platform password character michael@0: char16_t passwordChar = LookAndFeel::GetPasswordCharacter(); michael@0: michael@0: int32_t i; michael@0: aOutString->Truncate(); michael@0: for (i=0; i < aLength; i++) michael@0: aOutString->Append(passwordChar); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // CreateMozBR: put a BR node with moz attribute at {aNode, aOffset} michael@0: // michael@0: nsresult michael@0: nsTextEditRules::CreateMozBR(nsIDOMNode* inParent, int32_t inOffset, michael@0: nsIDOMNode** outBRNode) michael@0: { michael@0: NS_ENSURE_TRUE(inParent, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mEditor); michael@0: nsresult res = mEditor->CreateBR(inParent, inOffset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // give it special moz attr michael@0: nsCOMPtr brElem = do_QueryInterface(brNode); michael@0: if (brElem) { michael@0: res = mEditor->SetAttribute(brElem, NS_LITERAL_STRING("type"), NS_LITERAL_STRING("_moz")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: if (outBRNode) { michael@0: brNode.forget(outBRNode); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextEditRules::DocumentModified() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: }