Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* 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 "HTMLEditor.h"
#include <algorithm>
#include <utility>
#include "HTMLEditUtils.h"
#include "WSRunObject.h"
#include "mozilla/Assertions.h"
#include "mozilla/CSSEditUtils.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/EditAction.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/RangeUtils.h"
#include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
#include "mozilla/TextComposition.h"
#include "mozilla/TypeInState.h" // for SpecifiedStyle
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "mozilla/mozalloc.h"
#include "nsAString.h"
#include "nsAlgorithm.h"
#include "nsAtom.h"
#include "nsCRT.h"
#include "nsCRTGlue.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsHTMLDocument.h"
#include "nsIContent.h"
#include "nsID.h"
#include "nsIFrame.h"
#include "nsINode.h"
#include "nsLiteralString.h"
#include "nsRange.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsStyledElement.h"
#include "nsTArray.h"
#include "nsTextNode.h"
#include "nsThreadUtils.h"
#include "nsUnicharUtils.h"
// Workaround for windows headers
#ifdef SetProp
# undef SetProp
#endif
class nsISupports;
namespace mozilla {
using namespace dom;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeType = HTMLEditUtils::LeafNodeType;
using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using StyleDifference = HTMLEditUtils::StyleDifference;
using WalkTextOption = HTMLEditUtils::WalkTextOption;
using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/********************************************************
* first some helpful functors we will use
********************************************************/
static bool IsStyleCachePreservingSubAction(EditSubAction aEditSubAction) {
switch (aEditSubAction) {
case EditSubAction::eDeleteSelectedContent:
case EditSubAction::eInsertLineBreak:
case EditSubAction::eInsertParagraphSeparator:
case EditSubAction::eCreateOrChangeList:
case EditSubAction::eIndent:
case EditSubAction::eOutdent:
case EditSubAction::eSetOrClearAlignment:
case EditSubAction::eCreateOrRemoveBlock:
case EditSubAction::eMergeBlockContents:
case EditSubAction::eRemoveList:
case EditSubAction::eCreateOrChangeDefinitionListItem:
case EditSubAction::eInsertElement:
case EditSubAction::eInsertQuotation:
case EditSubAction::eInsertQuotedText:
return true;
default:
return false;
}
}
template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
RangeBoundary& aStartRef, RangeBoundary& aEndRef) const;
template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
RawRangeBoundary& aStartRef, RangeBoundary& aEndRef) const;
template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
RangeBoundary& aStartRef, RawRangeBoundary& aEndRef) const;
template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
RawRangeBoundary& aStartRef, RawRangeBoundary& aEndRef) const;
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
const RangeBoundary& aStartRef, const RangeBoundary& aEndRef);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
const RawRangeBoundary& aStartRef, const RangeBoundary& aEndRef);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
const RangeBoundary& aStartRef, const RawRangeBoundary& aEndRef);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
const RangeBoundary& aStartRef, const RangeBoundary& aEndRef,
EditSubAction aEditSubAction) const;
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
const RawRangeBoundary& aStartRef, const RangeBoundary& aEndRef,
EditSubAction aEditSubAction) const;
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
const RangeBoundary& aStartRef, const RawRangeBoundary& aEndRef,
EditSubAction aEditSubAction) const;
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef,
EditSubAction aEditSubAction) const;
template EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
const RangeBoundary& aPoint, EditSubAction aEditSubAction) const;
template EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
const RawRangeBoundary& aPoint, EditSubAction aEditSubAction) const;
template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
const RangeBoundary& aPoint) const;
template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
const RawRangeBoundary& aPoint) const;
nsresult HTMLEditor::InitEditorContentAndSelection() {
MOZ_ASSERT(IsEditActionDataAvailable());
nsresult rv = EditorBase::InitEditorContentAndSelection();
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::InitEditorContentAndSelection() failed");
return rv;
}
Element* bodyOrDocumentElement = GetRoot();
if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
return NS_ERROR_FAILURE;
}
if (!bodyOrDocumentElement) {
return NS_OK;
}
rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
RawRangeBoundary(bodyOrDocumentElement, 0u),
RawRangeBoundary(bodyOrDocumentElement,
bodyOrDocumentElement->GetChildCount()));
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
"failed, but ignored");
return NS_OK;
}
void HTMLEditor::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 (!mInitSucceeded) {
return; // We should do nothing if we're being initialized.
}
NS_WARNING_ASSERTION(
!aRv.Failed(),
"EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
// Remember where our selection was before edit action took place:
if (GetCompositionStartPoint().IsSet()) {
// If there is composition string, let's remember current composition
// range.
TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
GetCompositionStartPoint(), GetCompositionEndPoint());
} else {
// Get the selection location
// XXX This may occur so that I think that we shouldn't throw exception
// in this case.
if (NS_WARN_IF(!SelectionRef().RangeCount())) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
if (const nsRange* range = SelectionRef().GetRangeAt(0)) {
TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range);
}
}
// Register with range updater to track this as we perturb the doc
RangeUpdaterRef().RegisterRangeItem(
*TopLevelEditSubActionDataRef().mSelectedRange);
// Remember current inline styles for deletion and normal insertion ops
bool cacheInlineStyles;
switch (aTopLevelEditSubAction) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eDeleteSelectedContent:
cacheInlineStyles = true;
break;
default:
cacheInlineStyles =
IsStyleCachePreservingSubAction(aTopLevelEditSubAction);
break;
}
if (cacheInlineStyles) {
nsCOMPtr<nsIContent> containerContent = nsIContent::FromNodeOrNull(
aDirectionOfTopLevelEditSubAction == nsIEditor::eNext
? TopLevelEditSubActionDataRef().mSelectedRange->mEndContainer
: TopLevelEditSubActionDataRef().mSelectedRange->mStartContainer);
if (NS_WARN_IF(!containerContent)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
nsresult rv = CacheInlineStyles(*containerContent);
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
aRv.Throw(rv);
return;
}
}
// Stabilize the document against contenteditable count changes
Document* document = GetDocument();
if (NS_WARN_IF(!document)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (document->GetEditingState() == Document::EditingState::eContentEditable) {
document->ChangeContentEditableCount(nullptr, +1);
TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;
}
// Check that selection is in subtree defined by body node
nsresult rv = EnsureSelectionInBodyOrDocumentElement();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
return;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
"failed, but ignored");
}
nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
nsresult rv;
while (true) {
if (NS_WARN_IF(Destroyed())) {
rv = NS_ERROR_EDITOR_DESTROYED;
break;
}
if (!mInitSucceeded) {
rv = NS_OK; // We should do nothing if we're being initialized.
break;
}
// Do all the tricky stuff
rv = OnEndHandlingTopLevelEditSubActionInternal();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
// Perhaps, we need to do the following jobs even if the editor has been
// destroyed since they adjust some states of HTML document but don't
// modify the DOM tree nor Selection.
// Free up selectionState range item
if (TopLevelEditSubActionDataRef().mSelectedRange) {
RangeUpdaterRef().DropRangeItem(
*TopLevelEditSubActionDataRef().mSelectedRange);
}
// Reset the contenteditable count to its previous value
if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) {
Document* document = GetDocument();
if (NS_WARN_IF(!document)) {
rv = NS_ERROR_FAILURE;
break;
}
if (document->GetEditingState() ==
Document::EditingState::eContentEditable) {
document->ChangeContentEditableCount(nullptr, -1);
}
}
break;
}
DebugOnly<nsresult> rvIgnored =
EditorBase::OnEndHandlingTopLevelEditSubAction();
NS_WARNING_ASSERTION(
NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored),
"EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
MOZ_ASSERT(!GetTopLevelEditSubAction());
MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
return rv;
}
nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
nsresult rv = EnsureSelectionInBodyOrDocumentElement();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
"failed, but ignored");
switch (GetTopLevelEditSubAction()) {
case EditSubAction::eReplaceHeadWithHTMLSource:
case EditSubAction::eCreatePaddingBRElementForEmptyEditor:
return NS_OK;
default:
break;
}
if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() &&
GetTopLevelEditSubAction() != EditSubAction::eUndo &&
GetTopLevelEditSubAction() != EditSubAction::eRedo) {
// don't let any txns in here move the selection around behind our back.
// Note that this won't prevent explicit selection setting from working.
AutoTransactionsConserveSelection dontChangeMySelection(*this);
switch (GetTopLevelEditSubAction()) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eInsertLineBreak:
case EditSubAction::eInsertParagraphSeparator:
case EditSubAction::eDeleteText: {
// XXX We should investigate whether this is really needed because it
// seems that the following code does not handle the white-spaces.
RefPtr<nsRange> extendedChangedRange =
CreateRangeIncludingAdjuscentWhiteSpaces(
*TopLevelEditSubActionDataRef().mChangedRange);
if (extendedChangedRange) {
MOZ_ASSERT(extendedChangedRange->IsPositioned());
// Use extended range temporarily.
TopLevelEditSubActionDataRef().mChangedRange =
std::move(extendedChangedRange);
}
break;
}
default: {
RefPtr<nsRange> extendedChangedRange =
CreateRangeExtendedToHardLineStartAndEnd(
*TopLevelEditSubActionDataRef().mChangedRange,
GetTopLevelEditSubAction());
if (extendedChangedRange) {
MOZ_ASSERT(extendedChangedRange->IsPositioned());
// Use extended range temporarily.
TopLevelEditSubActionDataRef().mChangedRange =
std::move(extendedChangedRange);
}
break;
}
}
// if we did a ranged deletion or handling backspace key, make sure we have
// a place to put caret.
// Note we only want to do this if the overall operation was deletion,
// not if deletion was done along the way for
// EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
// That's why this is here rather than DeleteSelectionAsSubAction().
// However, we shouldn't insert <br> elements if we've already removed
// empty block parents because users may want to disappear the line by
// the deletion.
// XXX We should make HandleDeleteSelection() store expected container
// for handling this here since we cannot trust current selection is
// collapsed at deleted point.
if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent &&
TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange &&
!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) {
EditorDOMPoint newCaretPosition =
EditorBase::GetStartPoint(SelectionRef());
if (!newCaretPosition.IsSet()) {
NS_WARNING("There was no selection range");
return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
nsresult rv = InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
newCaretPosition);
if (NS_FAILED(rv)) {
NS_WARNING(
"HTMLEditor::"
"InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
"failed");
return rv;
}
}
// add in any needed <br>s, and remove any unneeded ones.
nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(),
TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw());
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
" failed, but ignored");
// merge any adjacent text nodes
switch (GetTopLevelEditSubAction()) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
break;
default: {
nsresult rv = CollapseAdjacentTextNodes(
MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
return rv;
}
break;
}
}
// clean up any empty nodes in the selection
rv = RemoveEmptyNodesIn(
MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
return rv;
}
// attempt to transform any unneeded nbsp's into spaces after doing various
// operations
switch (GetTopLevelEditSubAction()) {
case EditSubAction::eDeleteSelectedContent:
if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
break;
}
[[fallthrough]];
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eInsertLineBreak:
case EditSubAction::eInsertParagraphSeparator:
case EditSubAction::ePasteHTMLContent:
case EditSubAction::eInsertHTMLSource: {
// TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
// white-spaces with NPSPs and then, we'll replace them with ASCII
// white-spaces here. We should avoid this overwriting things as
// far as possible because replacing characters in text nodes
// causes running mutation event listeners which are really
// expensive.
// Adjust end of composition string if there is composition string.
EditorDOMPoint pointToAdjust(GetCompositionEndPoint());
if (!pointToAdjust.IsInContentNode()) {
// Otherwise, adjust current selection start point.
pointToAdjust = EditorBase::GetStartPoint(SelectionRef());
if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) {
return NS_ERROR_FAILURE;
}
}
if (EditorUtils::IsEditableContent(*pointToAdjust.ContainerAsContent(),
EditorType::HTML)) {
AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
&pointToAdjust);
nsresult rv =
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
*this, pointToAdjust);
if (NS_FAILED(rv)) {
NS_WARNING(
"WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
"failed");
return rv;
}
}
// also do this for original selection endpoints.
// XXX Hmm, if `NormalizeVisibleWhiteSpacesAt()` runs mutation event
// listener and that causes changing `mSelectedRange`, what we
// should do?
if (NS_WARN_IF(
!TopLevelEditSubActionDataRef().mSelectedRange->IsSet())) {
return NS_ERROR_FAILURE;
}
EditorDOMPoint atStart =
TopLevelEditSubActionDataRef().mSelectedRange->StartPoint();
if (atStart != pointToAdjust && atStart.IsInContentNode() &&
EditorUtils::IsEditableContent(*atStart.ContainerAsContent(),
EditorType::HTML)) {
AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
&pointToAdjust);
AutoTrackDOMPoint trackStartPoint(RangeUpdaterRef(), &atStart);
nsresult rv =
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
*this, atStart);
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
"failed, but ignored");
}
// we only need to handle old selection endpoint if it was different
// from start
EditorDOMPoint atEnd =
TopLevelEditSubActionDataRef().mSelectedRange->EndPoint();
if (!TopLevelEditSubActionDataRef().mSelectedRange->IsCollapsed() &&
atEnd != pointToAdjust && atEnd != atStart &&
atEnd.IsInContentNode() &&
EditorUtils::IsEditableContent(*atEnd.ContainerAsContent(),
EditorType::HTML)) {
nsresult rv =
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(*this,
atEnd);
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
"failed, but ignored");
}
break;
}
default:
break;
}
// If we created a new block, make sure caret is in it.
if (TopLevelEditSubActionDataRef().mNewBlockElement &&
SelectionRef().IsCollapsed()) {
nsresult rv = EnsureCaretInBlockElement(
MOZ_KnownLive(*TopLevelEditSubActionDataRef().mNewBlockElement));
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::EnsureSelectionInBlockElement() failed, but ignored");
}
// Adjust selection for insert text, html paste, and delete actions if
// we haven't removed new empty blocks. Note that if empty block parents
// are removed, Selection should've been adjusted by the method which
// did it.
if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks &&
SelectionRef().IsCollapsed()) {
switch (GetTopLevelEditSubAction()) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eDeleteSelectedContent:
case EditSubAction::eInsertLineBreak:
case EditSubAction::eInsertParagraphSeparator:
case EditSubAction::ePasteHTMLContent:
case EditSubAction::eInsertHTMLSource:
// XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
// does not create padding `<br>` element for empty editor.
// Investigate which is better that whether this should does it
// or wait MaybeCreatePaddingBRElementForEmptyEditor().
rv = AdjustCaretPositionAndEnsurePaddingBRElement(
GetDirectionOfTopLevelEditSubAction());
if (NS_FAILED(rv)) {
NS_WARNING(
"HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
"failed");
return rv;
}
break;
default:
break;
}
}
// check for any styles which were removed inappropriately
bool reapplyCachedStyle;
switch (GetTopLevelEditSubAction()) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eDeleteSelectedContent:
reapplyCachedStyle = true;
break;
default:
reapplyCachedStyle =
IsStyleCachePreservingSubAction(GetTopLevelEditSubAction());
break;
}
// If the selection is in empty inline HTML elements, we should delete
// them.
if (mPlaceholderBatch && SelectionRef().IsCollapsed() &&
SelectionRef().GetFocusNode()) {
RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr;
for (Element* ancestor :
SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) {
if (!ancestor->IsHTMLElement() ||
!HTMLEditUtils::IsRemovableFromParentNode(*ancestor) ||
!HTMLEditUtils::IsEmptyInlineContent(*ancestor)) {
break;
}
mostDistantEmptyInlineAncestor = ancestor;
}
if (mostDistantEmptyInlineAncestor) {
nsresult rv =
DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor);
if (Destroyed()) {
NS_WARNING(
"HTMLEditor::DeleteNodeWithTransaction() caused destroying the "
"editor at deleting empty inline ancestors");
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_FAILED(rv)) {
NS_WARNING(
"HTMLEditor::DeleteNodeWithTransaction() failed at deleting "
"empty inline ancestors");
return rv;
}
}
}
// But the cached inline styles should be restored from type-in-state later.
if (reapplyCachedStyle) {
DebugOnly<nsresult> rvIgnored =
mTypeInState->UpdateSelState(SelectionRef());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"TypeInState::UpdateSelState() failed, but ignored");
rvIgnored = ReapplyCachedStyles();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"HTMLEditor::ReapplyCachedStyles() failed, but ignored");
TopLevelEditSubActionDataRef().mCachedInlineStyles->Clear();
}
}
rv = HandleInlineSpellCheck(
TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(),
TopLevelEditSubActionDataRef().mChangedRange);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
return rv;
}
// detect empty doc
// XXX Need to investigate when the padding <br> element is removed because
// I don't see the <br> element with testing manually. If it won't be
// used, we can get rid of this cost.
rv = MaybeCreatePaddingBRElementForEmptyEditor();
if (NS_FAILED(rv)) {
NS_WARNING(
"EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
return rv;
}
// adjust selection HINT if needed
if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
SelectionRef().IsCollapsed()) {
SetSelectionInterlinePosition();
}
return NS_OK;
}
EditActionResult HTMLEditor::CanHandleHTMLEditSubAction() const {
MOZ_ASSERT(IsEditActionDataAvailable());
if (NS_WARN_IF(Destroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
// If there is not selection ranges, we should ignore the result.
if (!SelectionRef().RangeCount()) {
return EditActionCanceled();
}
const nsRange* range = SelectionRef().GetRangeAt(0);
nsINode* selStartNode = range->GetStartContainer();
if (NS_WARN_IF(!selStartNode) || NS_WARN_IF(!selStartNode->IsContent())) {
return EditActionResult(NS_ERROR_FAILURE);
}
if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode) ||
HTMLEditUtils::IsNonEditableReplacedContent(*selStartNode->AsContent())) {
return EditActionCanceled();
}
nsINode* selEndNode = range->GetEndContainer();
if (NS_WARN_IF(!selEndNode) || NS_WARN_IF(!selEndNode->IsContent())) {
return EditActionResult(NS_ERROR_FAILURE);
}
if (selStartNode == selEndNode) {
return EditActionIgnored();
}
if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode) ||
HTMLEditUtils::IsNonEditableReplacedContent(*selEndNode->AsContent())) {
return EditActionCanceled();
}
// XXX What does it mean the common ancestor is editable? I have no idea.
// It should be in same (active) editing host, and even if it's editable,
// there may be non-editable contents in the range.
nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor();
if (!commonAncestor) {
NS_WARNING(
"AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
return EditActionResult(NS_ERROR_FAILURE);
}
return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
? EditActionIgnored()
: EditActionCanceled();
}
MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent(
nsIContent& aContent) {
nsAutoString direction;
DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
aContent, *nsGkAtoms::direction, direction);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
" failed, but ignored");
return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
: *nsGkAtoms::marginLeft;
}
nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(SelectionRef().IsCollapsed());
// If we are after a padding `<br>` element for empty last line in the same
// block, then move selection to be before it
const nsRange* firstRange = SelectionRef().GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return NS_ERROR_FAILURE;
}
EditorRawDOMPoint atSelectionStart(firstRange->StartRef());
if (NS_WARN_IF(!atSelectionStart.IsSet())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(atSelectionStart.IsSetAndValid());
if (!atSelectionStart.IsInContentNode()) {
return NS_OK;
}
Element* editingHost = GetActiveEditingHost();
if (!editingHost) {
NS_WARNING(
"HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() did nothing "
"because of no editing host");
return NS_OK;
}
nsIContent* previousBRElement =
HTMLEditUtils::GetPreviousContent(atSelectionStart, {}, editingHost);
if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) ||
!previousBRElement->GetParent() ||
!EditorUtils::IsEditableContent(*previousBRElement->GetParent(),
EditorType::HTML) ||
!HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) {
return NS_OK;
}
const RefPtr<const Element> blockElementAtSelectionStart =
HTMLEditUtils::GetInclusiveAncestorElement(
*atSelectionStart.ContainerAsContent(),
HTMLEditUtils::ClosestBlockElement);
const RefPtr<const Element> parentBlockElementOfBRElement =
HTMLEditUtils::GetAncestorElement(*previousBRElement,
HTMLEditUtils::ClosestBlockElement);
if (!blockElementAtSelectionStart ||
blockElementAtSelectionStart != parentBlockElementOfBRElement) {
return NS_OK;
}
// If we are here then the selection is right after a padding <br>
// element for empty last line that is in the same block as the
// selection. We need to move the selection start to be before the
// padding <br> element.
EditorRawDOMPoint atInvisibleBRElement(previousBRElement);
nsresult rv = CollapseSelectionTo(atInvisibleBRElement);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::CollapseSelectionTo() failed");
return rv;
}
nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
MOZ_ASSERT(IsEditActionDataAvailable());
if (mPaddingBRElementForEmptyEditor) {
return NS_OK;
}
IgnoredErrorResult ignoredError;
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eCreatePaddingBRElementForEmptyEditor,
nsIEditor::eNone, ignoredError);
if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
return ignoredError.StealNSResult();
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
"TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
ignoredError.SuppressException();
RefPtr<Element> rootElement = GetRoot();
if (!rootElement) {
return NS_OK;
}
// Now we've got the body element. Iterate over the body element's children,
// looking for editable content. If no editable content is found, insert the
// padding <br> element.
EditorType editorType = GetEditorType();
bool isRootEditable =
EditorUtils::IsEditableContent(*rootElement, editorType);
for (nsIContent* rootChild = rootElement->GetFirstChild(); rootChild;
rootChild = rootChild->GetNextSibling()) {
if (EditorUtils::IsPaddingBRElementForEmptyEditor(*rootChild) ||
!isRootEditable ||
EditorUtils::IsEditableContent(*rootChild, editorType) ||
HTMLEditUtils::IsBlockElement(*rootChild)) {
return NS_OK;
}
}
// Skip adding the padding <br> element for empty editor if body
// is read-only.
if (IsHTMLEditor() && !HTMLEditUtils::IsSimplyEditableNode(*rootElement)) {
return NS_OK;
}
// Create a br.
RefPtr<Element> newBRElement = CreateHTMLContent(nsGkAtoms::br);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_WARN_IF(!newBRElement)) {
return NS_ERROR_FAILURE;
}
mPaddingBRElementForEmptyEditor =
static_cast<HTMLBRElement*>(newBRElement.get());
// Give it a special attribute.
newBRElement->SetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
// Put the node in the document.
nsresult rv =
InsertNodeWithTransaction(*newBRElement, EditorDOMPoint(rootElement, 0));
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
return rv;
}
// Set selection.
SelectionRef().CollapseInLimiter(EditorRawDOMPoint(rootElement, 0),
ignoredError);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(!ignoredError.Failed(),
"Selection::CollapseInLimiter() failed, but ignored");
return NS_OK;
}
nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
MOZ_ASSERT(IsEditActionDataAvailable());
if (!mPaddingBRElementForEmptyEditor) {
return NS_OK;
}
// If we're an HTML editor, a mutation event listener may recreate padding
// <br> element for empty editor again during the call of
// DeleteNodeWithTransaction(). So, move it first.
RefPtr<HTMLBRElement> paddingBRElement(
std::move(mPaddingBRElementForEmptyEditor));
nsresult rv = DeleteNodeWithTransaction(*paddingBRElement);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::DeleteNodeWithTransaction() failed");
return rv;
}
nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() {
if (NS_WARN_IF(!mRootElement)) {
NS_WARNING("Failed to handle padding BR element due to no root element");
return NS_ERROR_FAILURE;
}
// The idea here is to see if the magic empty node has suddenly reappeared. If
// it has, set our state so we remember it. There is a tradeoff between doing
// here and at redo, or doing it everywhere else that might care. Since undo
// and redo are relatively rare, it makes sense to take the (small)
// performance hit here.
nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent(
*mRootElement, {LeafNodeType::OnlyLeafNode});
if (firstLeafChild &&
EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) {
mPaddingBRElementForEmptyEditor =
static_cast<HTMLBRElement*>(firstLeafChild);
} else {
mPaddingBRElementForEmptyEditor = nullptr;
}
return NS_OK;
}
nsresult HTMLEditor::PrepareInlineStylesForCaret() {
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
MOZ_ASSERT(SelectionRef().IsCollapsed());
// XXX This method works with the top level edit sub-action, but this
// must be wrong if we are handling nested edit action.
if (TopLevelEditSubActionDataRef().mDidDeleteSelection) {
switch (GetTopLevelEditSubAction()) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eDeleteSelectedContent: {
nsresult rv = ReapplyCachedStyles();
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
return rv;
}
break;
}
default:
break;
}
}
// For most actions we want to clear the cached styles, but there are
// exceptions
if (!IsStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
TopLevelEditSubActionDataRef().mCachedInlineStyles->Clear();
}
return NS_OK;
}
EditActionResult HTMLEditor::HandleInsertText(
EditSubAction aEditSubAction, const nsAString& aInsertionString,
SelectionHandling aSelectionHandling) {
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
aEditSubAction == EditSubAction::eInsertTextComingFromIME);
MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore,
aEditSubAction == EditSubAction::eInsertTextComingFromIME);
EditActionResult result = CanHandleHTMLEditSubAction();
if (result.Failed() || result.Canceled()) {
NS_WARNING_ASSERTION(result.Succeeded(),
"HTMLEditor::CanHandleHTMLEditSubAction() failed");
return result;
}
UndefineCaretBidiLevel();
// If the selection isn't collapsed, delete it. Don't delete existing inline
// tags, because we're hopefully going to insert text (bug 787432).
if (!SelectionRef().IsCollapsed() &&
aSelectionHandling == SelectionHandling::Delete) {
nsresult rv =
DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
if (NS_FAILED(rv)) {
NS_WARNING(
"EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
"nsIEditor::eNoStrip) failed");
return EditActionHandled(rv);
}
}
nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
"failed, but ignored");
if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
"failed, but ignored");
if (NS_SUCCEEDED(rv)) {
nsresult rv = PrepareInlineStylesForCaret();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
}
}
RefPtr<Document> document = GetDocument();
if (NS_WARN_IF(!document)) {
return EditActionHandled(NS_ERROR_FAILURE);
}
RefPtr<const nsRange> firstRange = SelectionRef().GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return EditActionHandled(NS_ERROR_FAILURE);
}
// for every property that is set, insert a new inline style node
// XXX CreateStyleForInsertText() adjusts selection automatically, but
// it should just return the insertion point instead.
rv = CreateStyleForInsertText(*firstRange);
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
return EditActionHandled(rv);
}
firstRange = SelectionRef().GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return EditActionHandled(NS_ERROR_FAILURE);
}
EditorDOMPoint pointToInsert(firstRange->StartRef());
if (NS_WARN_IF(!pointToInsert.IsSet()) ||
NS_WARN_IF(!pointToInsert.IsInContentNode())) {
return EditActionHandled(NS_ERROR_FAILURE);
}
MOZ_ASSERT(pointToInsert.IsSetAndValid());
// If the point is not in an element which can contain text nodes, climb up
// the DOM tree.
if (!pointToInsert.IsInTextNode()) {
Element* editingHost = GetActiveEditingHost(GetDocument()->IsXMLDocument()
? LimitInBodyElement::No
: LimitInBodyElement::Yes);
if (NS_WARN_IF(!editingHost)) {
return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
*nsGkAtoms::textTagName)) {
if (NS_WARN_IF(pointToInsert.GetContainer() == editingHost) ||
NS_WARN_IF(!pointToInsert.GetContainerParentAsContent())) {
NS_WARNING("Selection start point couldn't have text nodes");
return EditActionHandled(NS_ERROR_FAILURE);
}
pointToInsert.Set(pointToInsert.ContainerAsContent());
}
}
if (aEditSubAction == EditSubAction::eInsertTextComingFromIME) {
EditorRawDOMPoint compositionStartPoint = GetCompositionStartPoint();
if (!compositionStartPoint.IsSet()) {
compositionStartPoint = pointToInsert;
}
if (aInsertionString.IsEmpty()) {
// Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
// but IME needs the InsertTextWithTransaction() call to still happen
// since empty strings are meaningful there.
nsresult rv = InsertTextWithTransaction(*document, aInsertionString,
compositionStartPoint);
if (NS_WARN_IF(Destroyed())) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::InsertTextWithTransaction() failed");
return EditActionHandled(rv);
}
EditorRawDOMPoint compositionEndPoint = GetCompositionEndPoint();
if (!compositionEndPoint.IsSet()) {
compositionEndPoint = compositionStartPoint;
}
nsresult rv = WhiteSpaceVisibilityKeeper::ReplaceText(
*this, aInsertionString,
EditorDOMRange(compositionStartPoint, compositionEndPoint));
if (NS_FAILED(rv)) {
NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
return EditActionHandled(rv);
}
compositionStartPoint = GetCompositionStartPoint();
compositionEndPoint = GetCompositionEndPoint();
if (NS_WARN_IF(!compositionStartPoint.IsSet()) ||
NS_WARN_IF(!compositionEndPoint.IsSet())) {
// Mutation event listener has changed the DOM tree...
return EditActionHandled();
}
rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
compositionStartPoint.ToRawRangeBoundary(),
compositionEndPoint.ToRawRangeBoundary());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
return EditActionHandled(rv);
}
MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
// find where we are
EditorDOMPoint currentPoint(pointToInsert);
// is our text going to be PREformatted?
// We remember this so that we know how to handle tabs.
const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
*pointToInsert.ContainerAsContent());
// turn off the edit listener: we know how to
// build the "doc changed range" ourselves, and it's
// must faster to do it once here than to track all
// the changes one at a time.
AutoRestore<bool> disableListener(
EditSubActionDataRef().mAdjustChangedRangeFromListener);
EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
// don't change my selection in subtransactions
AutoTransactionsConserveSelection dontChangeMySelection(*this);
int32_t pos = 0;
constexpr auto newlineStr = NS_LITERAL_STRING_FROM_CSTRING(LFSTR);
{
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
// for efficiency, break out the pre case separately. This is because
// its a lot cheaper to search the input string for only newlines than
// it is to search for both tabs and newlines.
if (!isWhiteSpaceCollapsible || IsInPlaintextMode()) {
while (pos != -1 &&
pos < AssertedCast<int32_t>(aInsertionString.Length())) {
int32_t oldPos = pos;
int32_t subStrLen;
pos = aInsertionString.FindChar(nsCRT::LF, oldPos);
if (pos != -1) {
subStrLen = pos - oldPos;
// if first char is newline, then use just it
if (!subStrLen) {
subStrLen = 1;
}
} else {
subStrLen = aInsertionString.Length() - oldPos;
pos = aInsertionString.Length();
}
nsDependentSubstring subStr(aInsertionString, oldPos, subStrLen);
// is it a return?
if (subStr.Equals(newlineStr)) {
Result<RefPtr<Element>, nsresult> resultOfInsertingBRElement =
InsertBRElementWithTransaction(currentPoint, nsIEditor::eNone);
if (resultOfInsertingBRElement.isErr()) {
NS_WARNING(
"HTMLEditor::InsertBRElementWithTransaction(eNone) failed");
return EditActionHandled(resultOfInsertingBRElement.unwrapErr());
}
pos++;
RefPtr<Element> brElement(
resultOfInsertingBRElement.unwrap().forget());
if (brElement->GetNextSibling()) {
pointToInsert.Set(brElement->GetNextSibling());
} else {
pointToInsert.SetToEndOf(currentPoint.GetContainer());
}
// XXX In most cases, pointToInsert and currentPoint are same here.
// But if the <br> element has been moved to different point by
// mutation observer, those points become different.
currentPoint.SetAfter(brElement);
NS_WARNING_ASSERTION(currentPoint.IsSet(),
"Failed to set after the <br> element");
NS_WARNING_ASSERTION(currentPoint == pointToInsert,
"Perhaps, <br> element position has been moved "
"to different point "
"by mutation observer");
} else {
EditorRawDOMPoint pointAfterInsertedString;
nsresult rv = InsertTextWithTransaction(
*document, subStr, EditorRawDOMPoint(currentPoint),
&pointAfterInsertedString);
if (NS_WARN_IF(Destroyed())) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
return EditActionHandled(rv);
}
currentPoint = pointAfterInsertedString;
pointToInsert = pointAfterInsertedString;
}
}
} else {
constexpr auto tabStr = u"\t"_ns;
constexpr auto spacesStr = u" "_ns;
char specialChars[] = {TAB, nsCRT::LF, 0};
nsAutoString insertionString(aInsertionString); // For FindCharInSet().
while (pos != -1 &&
pos < AssertedCast<int32_t>(insertionString.Length())) {
int32_t oldPos = pos;
int32_t subStrLen;
pos = insertionString.FindCharInSet(specialChars, oldPos);
if (pos != -1) {
subStrLen = pos - oldPos;
// if first char is newline, then use just it
if (!subStrLen) {
subStrLen = 1;
}
} else {
subStrLen = insertionString.Length() - oldPos;
pos = insertionString.Length();
}
nsDependentSubstring subStr(insertionString, oldPos, subStrLen);
// is it a tab?
if (subStr.Equals(tabStr)) {
EditorRawDOMPoint pointAfterInsertedSpaces;
nsresult rv = WhiteSpaceVisibilityKeeper::InsertText(
*this, spacesStr, currentPoint, &pointAfterInsertedSpaces);
if (NS_FAILED(rv)) {
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
return EditActionHandled(rv);
}
pos++;
MOZ_ASSERT(pointAfterInsertedSpaces.IsSet());
currentPoint = pointAfterInsertedSpaces;
pointToInsert = pointAfterInsertedSpaces;
}
// is it a return?
else if (subStr.Equals(newlineStr)) {
Result<RefPtr<Element>, nsresult> result =
WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint);
if (result.isErr()) {
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
return EditActionHandled(result.inspectErr());
}
pos++;
RefPtr<Element> newBRElement = result.unwrap();
MOZ_DIAGNOSTIC_ASSERT(newBRElement);
if (newBRElement->GetNextSibling()) {
pointToInsert.Set(newBRElement->GetNextSibling());
} else {
pointToInsert.SetToEndOf(currentPoint.GetContainer());
}
currentPoint.SetAfter(newBRElement);
NS_WARNING_ASSERTION(currentPoint.IsSet(),
"Failed to set after the new <br> element");
// XXX If the newBRElement has been moved or removed by mutation
// observer, we hit this assert. We need to check if
// newBRElement is in expected point, though, we must have
// a lot of same bugs...
NS_WARNING_ASSERTION(
currentPoint == pointToInsert,
"Perhaps, newBRElement has been moved or removed unexpectedly");
} else {
EditorRawDOMPoint pointAfterInsertedString;
nsresult rv = WhiteSpaceVisibilityKeeper::InsertText(
*this, subStr, currentPoint, &pointAfterInsertedString);
if (NS_FAILED(rv)) {
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
return EditActionHandled(rv);
}
MOZ_ASSERT(pointAfterInsertedString.IsSet());
currentPoint = pointAfterInsertedString;
pointToInsert = pointAfterInsertedString;
}
}
}
// After this block, pointToInsert is updated by AutoTrackDOMPoint.
}
IgnoredErrorResult ignoredError;
SelectionRef().SetInterlinePosition(false, ignoredError);
NS_WARNING_ASSERTION(!ignoredError.Failed(),
"Failed to unset interline position");
if (currentPoint.IsSet()) {
nsresult rv = CollapseSelectionTo(currentPoint);
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Selection::Collapse() failed, but ignored");
}
// manually update the doc changed range so that AfterEdit will clean up
// the correct portion of the document.
if (currentPoint.IsSet()) {
nsresult rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
return EditActionHandled(rv);
}
rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed");
return EditActionHandled(rv);
}
nsresult HTMLEditor::InsertLineBreakAsSubAction() {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
if (NS_WARN_IF(!mInitSucceeded)) {
return NS_ERROR_NOT_INITIALIZED;
}
EditActionResult result = CanHandleHTMLEditSubAction();
if (result.Failed() || result.Canceled()) {
NS_WARNING_ASSERTION(result.Succeeded(),
"HTMLEditor::CanHandleHTMLEditSubAction() failed");
return result.Rv();
}
// XXX This may be called by execCommand() with "insertLineBreak".
// In such case, naming the transaction "TypingTxnName" is odd.
AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
ScrollSelectionIntoView::Yes);
// calling it text insertion to trigger moz br treatment by rules
// XXX Why do we use EditSubAction::eInsertText here? Looks like
// EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
// is better.
IgnoredErrorResult ignoredError;
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
return ignoredError.StealNSResult();
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
"HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
UndefineCaretBidiLevel();
// If the selection isn't collapsed, delete it.
if (!SelectionRef().IsCollapsed()) {
nsresult rv =
DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
if (NS_FAILED(rv)) {
NS_WARNING(
"EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
return rv;
}
}
const nsRange* firstRange = SelectionRef().GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return NS_ERROR_FAILURE;
}
EditorDOMPoint atStartOfSelection(firstRange->StartRef());
if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
RefPtr<Element> editingHost = GetActiveEditingHost();
if (NS_WARN_IF(!editingHost)) {
return NS_ERROR_FAILURE;
}
// For backward compatibility, we should not insert a linefeed if
// paragraph separator is set to "br" which is Gecko-specific mode.
if (GetDefaultParagraphSeparator() == ParagraphSeparator::br ||
!HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection,
*editingHost)) {
// InsertBRElementWithTransaction() will set selection after the new <br>
// element.
Result<RefPtr<Element>, nsresult> resultOfInsertingBRElement =
InsertBRElementWithTransaction(atStartOfSelection, nsIEditor::eNext);
if (resultOfInsertingBRElement.isErr()) {
NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
return resultOfInsertingBRElement.unwrapErr();
}
MOZ_ASSERT(resultOfInsertingBRElement.inspect());
return NS_OK;
}
nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
"failed, but ignored");
if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
"failed, but ignored");
if (NS_SUCCEEDED(rv)) {
nsresult rv = PrepareInlineStylesForCaret();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
}
}
firstRange = SelectionRef().GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return NS_ERROR_FAILURE;
}
atStartOfSelection = EditorDOMPoint(firstRange->StartRef());
if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
// Do nothing if the node is read-only
if (!HTMLEditUtils::IsSimplyEditableNode(
*atStartOfSelection.GetContainer())) {
return NS_SUCCESS_DOM_NO_OPERATION;
}
rv = HandleInsertLinefeed(atStartOfSelection, *editingHost);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::HandleInsertLinefeed() failed");
return rv;
}
EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
if (NS_WARN_IF(!mInitSucceeded)) {
return EditActionIgnored(NS_ERROR_NOT_INITIALIZED);
}
EditActionResult result = CanHandleHTMLEditSubAction();
if (result.Failed() || result.Canceled()) {
NS_WARNING_ASSERTION(result.Succeeded(),
"HTMLEditor::CanHandleHTMLEditSubAction() failed");
return result;
}
// XXX This may be called by execCommand() with "insertParagraph".
// In such case, naming the transaction "TypingTxnName" is odd.
AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
ScrollSelectionIntoView::Yes);
IgnoredErrorResult ignoredError;
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext,
ignoredError);
if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
return EditActionResult(ignoredError.StealNSResult());
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
"HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
UndefineCaretBidiLevel();
// If the selection isn't collapsed, delete it.
if (!SelectionRef().IsCollapsed()) {
nsresult rv =
DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
if (NS_FAILED(rv)) {
NS_WARNING(
"EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
return EditActionIgnored(rv);
}
}
nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
"failed, but ignored");
if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
"failed, but ignored");
if (NS_SUCCEEDED(rv)) {
nsresult rv = PrepareInlineStylesForCaret();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");