/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TextEditor.h" #include "AutoRangeArray.h" #include "EditAction.h" #include "EditorDOMPoint.h" #include "EditorUtils.h" #include "HTMLEditor.h" #include "mozilla/Assertions.h" #include "mozilla/LookAndFeel.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_editor.h" #include "mozilla/TextComposition.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLBRElement.h" #include "mozilla/dom/NodeFilterBinding.h" #include "mozilla/dom/NodeIterator.h" #include "mozilla/dom/Selection.h" #include "nsAString.h" #include "nsCOMPtr.h" #include "nsCRT.h" #include "nsCRTGlue.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsIContent.h" #include "nsIHTMLCollection.h" #include "nsINode.h" #include "nsISupports.h" #include "nsLiteralString.h" #include "nsNameSpaceManager.h" #include "nsPrintfCString.h" #include "nsTextNode.h" #include "nsUnicharUtils.h" namespace mozilla { using namespace dom; #define CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY \ if (IsReadonly()) { \ return EditActionResult::CanceledResult(); \ } void TextEditor::OnStartToHandleTopLevelEditSubAction( EditSubAction aTopLevelEditSubAction, nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) { MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(!aRv.Failed()); EditorBase::OnStartToHandleTopLevelEditSubAction( aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv); MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction); MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == aDirectionOfTopLevelEditSubAction); if (NS_WARN_IF(Destroyed())) { aRv.Throw(NS_ERROR_EDITOR_DESTROYED); return; } if (NS_WARN_IF(!mInitSucceeded)) { return; } if (aTopLevelEditSubAction == EditSubAction::eSetText) { // SetText replaces all text, so spell checker handles starting from the // start of new value. SetSpellCheckRestartPoint(EditorDOMPoint(mRootElement, 0)); return; } if (aTopLevelEditSubAction == EditSubAction::eInsertText || aTopLevelEditSubAction == EditSubAction::eInsertTextComingFromIME) { // For spell checker, previous selected node should be text node if // possible. If anchor is root of editor, it may become invalid offset // after inserting text. const EditorRawDOMPoint point = FindBetterInsertionPoint(EditorRawDOMPoint(SelectionRef().AnchorRef())); NS_WARNING_ASSERTION( point.IsSet(), "EditorBase::FindBetterInsertionPoint() failed, but ignored"); if (point.IsSet()) { SetSpellCheckRestartPoint(point); return; } } if (SelectionRef().AnchorRef().IsSet()) { SetSpellCheckRestartPoint(EditorRawDOMPoint(SelectionRef().AnchorRef())); } } nsresult TextEditor::OnEndHandlingTopLevelEditSubAction() { MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); nsresult rv; while (true) { if (NS_WARN_IF(Destroyed())) { rv = NS_ERROR_EDITOR_DESTROYED; break; } // XXX Probably, we should spellcheck again after edit action (not top-level // sub-action) is handled because the ranges can be referred only by // users. if (NS_FAILED(rv = HandleInlineSpellCheckAfterEdit())) { NS_WARNING("TextEditor::HandleInlineSpellCheckAfterEdit() failed"); break; } if (!IsSingleLineEditor() && NS_FAILED(rv = EnsurePaddingBRElementInMultilineEditor())) { NS_WARNING( "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed"); break; } rv = EnsureCaretNotAtEndOfTextNode(); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { break; } NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "TextEditor::EnsureCaretNotAtEndOfTextNode() failed, but ignored"); rv = NS_OK; break; } DebugOnly rvIgnored = EditorBase::OnEndHandlingTopLevelEditSubAction(); NS_WARNING_ASSERTION( NS_SUCCEEDED(rvIgnored), "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored"); MOZ_ASSERT(!GetTopLevelEditSubAction()); MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone); return rv; } nsresult TextEditor::InsertLineBreakAsSubAction() { MOZ_ASSERT(IsEditActionDataAvailable()); if (NS_WARN_IF(!mInitSucceeded)) { return NS_ERROR_NOT_INITIALIZED; } IgnoredErrorResult ignoredError; AutoEditSubActionNotifier startToHandleEditSubAction( *this, EditSubAction::eInsertLineBreak, nsIEditor::eNext, ignoredError); if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { return ignoredError.StealNSResult(); } NS_WARNING_ASSERTION( !ignoredError.Failed(), "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); Result result = InsertLineFeedCharacterAtSelection(); if (MOZ_UNLIKELY(result.isErr())) { NS_WARNING( "TextEditor::InsertLineFeedCharacterAtSelection() failed, but ignored"); return result.unwrapErr(); } return NS_OK; } Result TextEditor::InsertLineFeedCharacterAtSelection() { MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(!IsSingleLineEditor()); UndefineCaretBidiLevel(); CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY if (mMaxTextLength >= 0) { nsAutoString insertionString(u"\n"_ns); Result result = MaybeTruncateInsertionStringForMaxLength(insertionString); if (MOZ_UNLIKELY(result.isErr())) { NS_WARNING( "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed"); return result; } if (result.inspect().Handled()) { // Don't return as handled since we stopped inserting the line break. return EditActionResult::CanceledResult(); } } // if the selection isn't collapsed, delete it. if (!SelectionRef().IsCollapsed()) { nsresult rv = DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip); if (NS_FAILED(rv)) { NS_WARNING( "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed"); return Err(rv); } } const auto pointToInsert = GetFirstSelectionStartPoint(); if (NS_WARN_IF(!pointToInsert.IsSet())) { return Err(NS_ERROR_FAILURE); } MOZ_ASSERT(pointToInsert.IsSetAndValid()); MOZ_ASSERT(!pointToInsert.IsContainerHTMLElement(nsGkAtoms::br)); RefPtr document = GetDocument(); if (NS_WARN_IF(!document)) { return Err(NS_ERROR_NOT_INITIALIZED); } // Don't change my selection in sub-transactions. AutoTransactionsConserveSelection dontChangeMySelection(*this); // Insert a linefeed character. Result insertTextResult = InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert); if (MOZ_UNLIKELY(insertTextResult.isErr())) { NS_WARNING("TextEditor::InsertTextWithTransaction(\"\\n\") failed"); return insertTextResult.propagateErr(); } if (MOZ_UNLIKELY(!insertTextResult.inspect().IsSet())) { NS_WARNING( "EditorBase::InsertTextWithTransaction(\"\\n\") didn't return position " "of inserted linefeed"); return Err(NS_ERROR_FAILURE); } // set the selection to the correct location MOZ_ASSERT(insertTextResult.inspect().IsInTextNode(), "After inserting text into a text node, insertTextResult should " "return a point in a text node"); nsresult rv = CollapseSelectionTo(insertTextResult.inspect()); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::CollapseSelectionTo() failed"); return Err(rv); } // XXX I don't think we still need this. This must have been required when // `