/* -*- 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 "mozilla/Assertions.h" #include "mozilla/EditAction.h" #include "mozilla/EditorDOMPoint.h" #include "mozilla/EditorUtils.h" #include "mozilla/HTMLEditor.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 "nsISupportsBase.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 EditActionCanceled(NS_OK); \ } nsresult TextEditor::InitEditorContentAndSelection() { MOZ_ASSERT(IsEditActionDataAvailable()); nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor(); if (NS_FAILED(rv)) { NS_WARNING( "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed"); return rv; } // If the selection hasn't been set up yet, set it up collapsed to the end of // our editable content. // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe // removed by the web app and if they call `Selection::AddRange()`, // it may cause multiple selection ranges. if (!SelectionRefPtr()->RangeCount()) { nsresult rv = CollapseSelectionToEnd(); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::CollapseSelectionToEnd() failed"); return rv; } } if (IsPlaintextEditor() && !IsSingleLineEditor()) { nsresult rv = EnsurePaddingBRElementInMultilineEditor(); if (NS_FAILED(rv)) { NS_WARNING( "TextEditor::EnsurePaddingBRElementInMultilineEditor() failed"); return rv; } } return NS_OK; } void TextEditor::OnStartToHandleTopLevelEditSubAction( EditSubAction aTopLevelEditSubAction, nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) { MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(!AsHTMLEditor()); 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. EditorRawDOMPoint point = FindBetterInsertionPoint( EditorRawDOMPoint(SelectionRefPtr()->AnchorRef())); NS_WARNING_ASSERTION( point.IsSet(), "EditorBase::FindBetterInsertionPoint() failed, but ignored"); if (point.IsSet()) { SetSpellCheckRestartPoint(point); return; } } if (SelectionRefPtr()->AnchorRef().IsSet()) { SetSpellCheckRestartPoint( EditorRawDOMPoint(SelectionRefPtr()->AnchorRef())); } } nsresult TextEditor::OnEndHandlingTopLevelEditSubAction() { MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); MOZ_ASSERT(!AsHTMLEditor()); 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 (NS_FAILED(rv = EnsurePaddingBRElementForEmptyEditor())) { NS_WARNING("TextEditor::EnsurePaddingBRElementForEmptyEditor() failed"); break; } if (!IsSingleLineEditor() && NS_FAILED(rv = EnsurePaddingBRElementInMultilineEditor())) { NS_WARNING( "TextEditor::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; } EditActionResult TextEditor::InsertLineFeedCharacterAtSelection() { MOZ_ASSERT(IsEditActionDataAvailable()); MOZ_ASSERT(IsTextEditor()); MOZ_ASSERT(!IsSingleLineEditor()); UndefineCaretBidiLevel(); CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY if (mMaxTextLength >= 0) { nsAutoString insertionString(u"\n"_ns); EditActionResult result = MaybeTruncateInsertionStringForMaxLength(insertionString); if (result.Failed()) { NS_WARNING( "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed"); return result; } if (result.Handled()) { // Don't return as handled since we stopped inserting the line break. return EditActionCanceled(); } } // if the selection isn't collapsed, delete it. if (!SelectionRefPtr()->IsCollapsed()) { nsresult rv = DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip); if (NS_FAILED(rv)) { NS_WARNING( "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed"); return EditActionIgnored(rv); } } nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed"); return EditActionIgnored(rv); } // get the (collapsed) selection location const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0); if (NS_WARN_IF(!firstRange)) { return EditActionIgnored(NS_ERROR_FAILURE); } EditorRawDOMPoint pointToInsert(firstRange->StartRef()); if (NS_WARN_IF(!pointToInsert.IsSet())) { return EditActionIgnored(NS_ERROR_FAILURE); } MOZ_ASSERT(pointToInsert.IsSetAndValid()); MOZ_ASSERT(!pointToInsert.IsContainerHTMLElement(nsGkAtoms::br)); RefPtr document = GetDocument(); if (NS_WARN_IF(!document)) { return EditActionIgnored(NS_ERROR_NOT_INITIALIZED); } // Don't change my selection in sub-transactions. AutoTransactionsConserveSelection dontChangeMySelection(*this); // Insert a linefeed character. EditorRawDOMPoint pointAfterInsertedLineFeed; rv = InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert, &pointAfterInsertedLineFeed); if (!pointAfterInsertedLineFeed.IsSet()) { NS_WARNING( "EditorBase::InsertTextWithTransaction(\\n) didn't return position of " "inserted linefeed"); return EditActionIgnored(NS_ERROR_FAILURE); } if (NS_FAILED(rv)) { NS_WARNING("TextEditor::InsertTextWithTransaction(\\n) failed"); return EditActionIgnored(rv); } // set the selection to the correct location MOZ_ASSERT( !pointAfterInsertedLineFeed.GetChild(), "After inserting text into a text node, pointAfterInsertedLineFeed." "GetChild() should be nullptr"); rv = MOZ_KnownLive(SelectionRefPtr()) ->CollapseInLimiter(pointAfterInsertedLineFeed); if (NS_FAILED(rv)) { NS_WARNING("Selection::CollapseInLimiter() failed"); return EditActionIgnored(rv); } // XXX I don't think we still need this. This must have been required when // `