Quellcode-Bibliothek HTMLEditSubActionHandler.cpp
Sprache: C
/* -*- 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/. */
usingnamespace dom; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions; using LeafNodeType = HTMLEditUtils::LeafNodeType; using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; using WalkTextOption = HTMLEditUtils::WalkTextOption; using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection; using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/******************************************************** * first some helpful functors we will use
********************************************************/
staticbool IsPendingStyleCachePreservingSubAction(
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::eFormatBlockForHTMLCommand: case EditSubAction::eMergeBlockContents: case EditSubAction::eRemoveList: case EditSubAction::eCreateOrChangeDefinitionListItem: case EditSubAction::eInsertElement: case EditSubAction::eInsertQuotation: case EditSubAction::eInsertQuotedText: returntrue; default: returnfalse;
}
}
// 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()` without // checking the range count, it may cause multiple selection ranges. if (!SelectionRef().RangeCount()) {
nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument(); if (NS_FAILED(rv)) {
NS_WARNING( "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() " "failed"); return rv;
}
}
if (IsPlaintextMailComposer()) { // XXX Should we do this in HTMLEditor? It's odd to guarantee that last // empty line is visible only when it's in the plain text mode.
nsresult rv = EnsurePaddingBRElementInMultilineEditor(); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed"); return rv;
}
}
// Let's work with the latest layout information after (maybe) dispatching // `beforeinput` event.
RefPtr<Document> document = GetDocument(); if (NS_WARN_IF(!document)) {
aRv.Throw(NS_ERROR_UNEXPECTED); return;
}
document->FlushPendingNotifications(FlushType::Frames); if (NS_WARN_IF(Destroyed())) {
aRv.Throw(NS_ERROR_EDITOR_DESTROYED); return;
}
// Remember where our selection was before edit action took place: constauto atCompositionStart =
GetFirstIMESelectionStartPoint<EditorRawDOMPoint>(); if (atCompositionStart.IsSet()) { // If there is composition string, let's remember current composition // range.
TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>());
} 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 constbool cacheInlineStyles = [&]() { switch (aTopLevelEditSubAction) { case EditSubAction::eInsertText: case EditSubAction::eInsertTextComingFromIME: case EditSubAction::eDeleteSelectedContent: returntrue; default: return IsPendingStyleCachePreservingSubAction(aTopLevelEditSubAction);
}
}(); if (cacheInlineStyles) { const RefPtr<Element> editingHost =
ComputeEditingHost(LimitInBodyElement::No); if (NS_WARN_IF(!editingHost)) {
aRv.Throw(NS_ERROR_FAILURE); return;
}
// Stabilize the document against contenteditable count changes 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 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;
}
// If we just maintained the DOM tree for consistent behavior even after // web apps modified the DOM, we should not touch the DOM in this // post-processor. if (GetTopLevelEditSubAction() ==
EditSubAction::eMaintainWhiteSpaceVisibility) { return NS_OK;
}
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);
{
EditorDOMRange changedRange(
*TopLevelEditSubActionDataRef().mChangedRange); if (changedRange.IsPositioned() &&
changedRange.EnsureNotInNativeAnonymousSubtree()) { bool isBlockLevelSubAction = false; 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(changedRange); if (extendedChangedRange) {
MOZ_ASSERT(extendedChangedRange->IsPositioned()); // Use extended range temporarily.
TopLevelEditSubActionDataRef().mChangedRange =
std::move(extendedChangedRange);
} break;
} case EditSubAction::eCreateOrChangeList: case EditSubAction::eCreateOrChangeDefinitionListItem: case EditSubAction::eRemoveList: case EditSubAction::eFormatBlockForHTMLCommand: case EditSubAction::eCreateOrRemoveBlock: case EditSubAction::eIndent: case EditSubAction::eOutdent: case EditSubAction::eSetOrClearAlignment: case EditSubAction::eSetPositionToAbsolute: case EditSubAction::eSetPositionToStatic: case EditSubAction::eDecreaseZIndex: case EditSubAction::eIncreaseZIndex:
isBlockLevelSubAction = true;
[[fallthrough]]; default: {
Element* editingHost = ComputeEditingHost(); if (MOZ_UNLIKELY(!editingHost)) { break;
}
RefPtr<nsRange> extendedChangedRange = AutoClonedRangeArray::
CreateRangeWrappingStartAndEndLinesContainingBoundaries(
changedRange, GetTopLevelEditSubAction(),
isBlockLevelSubAction
? BlockInlineCheck::UseHTMLDefaultStyle
: BlockInlineCheck::UseComputedDisplayOutsideStyle,
*editingHost); if (!extendedChangedRange) { break;
}
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) { constauto newCaretPosition =
GetFirstSelectionStartPoint<EditorDOMPoint>(); if (!newCaretPosition.IsSet()) {
NS_WARNING("There was no selection range"); return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
Result<CreateLineBreakResult, nsresult>
insertPaddingBRElementResultOrError =
InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded(
newCaretPosition); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
NS_WARNING( "HTMLEditor::" "InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded() failed"); return insertPaddingBRElementResultOrError.unwrapErr();
}
nsresult rv =
insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion}); if (NS_FAILED(rv)) {
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); return rv;
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "CaretPoint::SuggestCaretPointTo() failed, but ignored");
}
// 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 changed range unless they are inserted // intentionally. if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) {
nsresult rv = RemoveEmptyNodesIn(
EditorDOMRange(*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: { // Due to the replacement of white-spaces in // WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(), // selection ranges may be changed since DOM ranges track the DOM // mutation by themselves. However, we want to keep selection as-is. // Therefore, we should restore `Selection` after replacing // white-spaces.
AutoSelectionRestorer restoreSelection(this); // 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. auto pointToAdjust = GetLastIMESelectionEndPoint<EditorDOMPoint>(); if (!pointToAdjust.IsInContentNode()) { // Otherwise, adjust current selection start point.
pointToAdjust = GetFirstSelectionStartPoint<EditorDOMPoint>(); if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) { return NS_ERROR_FAILURE;
}
} const RefPtr<Element> editingHost =
ComputeEditingHost(LimitInBodyElement::No); if (MOZ_UNLIKELY(!editingHost)) { break;
} if (EditorUtils::IsEditableContent(
*pointToAdjust.ContainerAs<nsIContent>(), EditorType::HTML)) {
AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
&pointToAdjust);
nsresult rv =
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
*this, pointToAdjust, *editingHost); 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->IsPositioned())) { return NS_ERROR_FAILURE;
}
EditorDOMPoint atStart =
TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(); if (atStart != pointToAdjust && atStart.IsInContentNode() &&
EditorUtils::IsEditableContent(*atStart.ContainerAs<nsIContent>(),
EditorType::HTML)) {
AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
&pointToAdjust);
AutoTrackDOMPoint trackStartPoint(RangeUpdaterRef(), &atStart);
nsresult rv =
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
*this, atStart, *editingHost); 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->Collapsed() &&
atEnd != pointToAdjust && atEnd != atStart &&
atEnd.IsInContentNode() &&
EditorUtils::IsEditableContent(*atEnd.ContainerAs<nsIContent>(),
EditorType::HTML)) {
nsresult rv =
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
*this, atEnd, *editingHost); 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;
}
// 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::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 =
IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction()); break;
}
// If the selection is in empty inline HTML elements, we should delete // them unless it's inserted intentionally. if (mPlaceholderBatch &&
TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements &&
SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) {
RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr; for (Element* ancestor :
SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) { if (!ancestor->IsHTMLElement() ||
!HTMLEditUtils::IsRemovableFromParentNode(*ancestor) ||
!HTMLEditUtils::IsEmptyInlineContainer(
*ancestor, {EmptyCheckOption::TreatSingleBRElementAsVisible},
BlockInlineCheck::UseComputedDisplayStyle)) { break;
}
mostDistantEmptyInlineAncestor = ancestor;
} if (mostDistantEmptyInlineAncestor) {
nsresult rv =
DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::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 =
mPendingStylesToApplyToNewContent->UpdateSelState(*this);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "PendingStyles::UpdateSelState() failed, but ignored");
rvIgnored = ReapplyCachedStyles();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "HTMLEditor::ReapplyCachedStyles() failed, but ignored");
TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
}
}
// 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();
}
if (selStartNode == selEndNode) { if (aCheckSelectionInReplacedElement ==
CheckSelectionInReplacedElement::Yes &&
HTMLEditUtils::IsNonEditableReplacedContent(
*selStartNode->AsContent())) { return EditActionResult::CanceledResult();
} return EditActionResult::IgnoredResult();
}
if (HTMLEditUtils::IsNonEditableReplacedContent(*selStartNode->AsContent()) ||
HTMLEditUtils::IsNonEditableReplacedContent(*selEndNode->AsContent())) { return EditActionResult::CanceledResult();
}
if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) { return EditActionResult::CanceledResult();
}
// If anchor node is in an HTML element which has inert attribute, we should // do nothing. // XXX HTMLEditor typically uses first range instead of anchor/focus range. // Therefore, referring first range here is more reasonable than // anchor/focus range of Selection.
nsIContent* const selAnchorContent = SelectionRef().GetDirection() == eDirNext
? nsIContent::FromNode(selStartNode)
: nsIContent::FromNode(selEndNode); if (selAnchorContent &&
HTMLEditUtils::ContentIsInert(*selAnchorContent->AsContent())) { return EditActionResult::CanceledResult();
}
// 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 (MOZ_UNLIKELY(!commonAncestor)) {
NS_WARNING( "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr"); return Err(NS_ERROR_FAILURE);
} return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
? EditActionResult::IgnoredResult()
: EditActionResult::CanceledResult();
}
// 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;
}
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), "EditorBase::CollapseSelectionTo() failed"); return rv;
}
if (mPaddingBRElementForEmptyEditor) { return NS_OK;
}
// XXX I think that we should not insert a <br> element if we're for a web // content. Probably, this is required only by chrome editors such as // the mail composer of Thunderbird and the composer of SeaMonkey.
// Skip adding the padding <br> element for empty editor if body // is read-only. if (!HTMLEditUtils::IsSimplyEditableNode(*bodyOrDocumentElement)) { 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(*bodyOrDocumentElement, editorType); for (nsIContent* child = bodyOrDocumentElement->GetFirstChild(); child;
child = child->GetNextSibling()) { if (EditorUtils::IsPaddingBRElementForEmptyEditor(*child) ||
!isRootEditable || EditorUtils::IsEditableContent(*child, editorType) ||
HTMLEditUtils::IsBlockElement(
*child, BlockInlineCheck::UseComputedDisplayStyle)) { 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(), "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
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);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::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;
}
// 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 (!IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
} return NS_OK;
}
nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " "failed, but ignored");
if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(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 Err(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 Err(NS_ERROR_FAILURE);
}
constbool isHandlingComposition =
aEditSubAction == EditSubAction::eInsertTextComingFromIME; auto pointToInsert = isHandlingComposition
? GetFirstIMESelectionStartPoint<EditorDOMPoint>()
: GetFirstSelectionStartPoint<EditorDOMPoint>(); if (!pointToInsert.IsSet()) { if (MOZ_LIKELY(isHandlingComposition)) {
pointToInsert = GetFirstSelectionStartPoint<EditorDOMPoint>();
} if (NS_WARN_IF(!pointToInsert.IsSet())) { return Err(NS_ERROR_FAILURE);
}
}
// for every property that is set, insert a new inline style node // XXX I think that if this is second or later composition update, we should // not change the style because we won't update composition with keeping // inline elements in composing range.
Result<EditorDOMPoint, nsresult> setStyleResult =
CreateStyleForInsertText(pointToInsert, *editingHost); if (MOZ_UNLIKELY(setStyleResult.isErr())) {
NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed"); return setStyleResult.propagateErr();
} if (setStyleResult.inspect().IsSet()) {
pointToInsert = setStyleResult.unwrap();
}
if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
NS_WARN_IF(!pointToInsert.IsInContentNode())) { return Err(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()) { while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
*nsGkAtoms::textTagName)) { if (NS_WARN_IF(pointToInsert.GetContainer() == editingHost) ||
NS_WARN_IF(!pointToInsert.GetContainerParentAs<nsIContent>())) {
NS_WARNING("Selection start point couldn't have text nodes"); return Err(NS_ERROR_FAILURE);
}
pointToInsert.Set(pointToInsert.ContainerAs<nsIContent>());
}
}
if (isHandlingComposition) { 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.
Result<InsertTextResult, nsresult> insertEmptyTextResultOrError =
InsertTextWithTransaction(*document, aInsertionString, pointToInsert,
InsertTextTo::ExistingTextNodeIfAvailable); if (MOZ_UNLIKELY(insertEmptyTextResultOrError.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed"); return insertEmptyTextResultOrError.propagateErr();
} const InsertTextResult insertEmptyTextResult =
insertEmptyTextResultOrError.unwrap();
nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
insertEmptyTextResult.EndOfInsertedTextRef()); if (NS_FAILED(rv)) {
NS_WARNING( "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); return Err(rv);
} // If we replaced non-empty composition string with an empty string, // its preceding character may be a collapsible ASCII white-space. // Therefore, we may need to insert a padding <br> after the white-space.
Result<CreateLineBreakResult, nsresult>
insertPaddingBRElementResultOrError = InsertPaddingBRElementIfNeeded(
insertEmptyTextResult.EndOfInsertedTextRef(), nsIEditor::eNoStrip,
*editingHost); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
NS_WARNING( "HTMLEditor::InsertPaddingBRElementIfNeeded(eNoStrip) failed");
insertEmptyTextResult.IgnoreCaretPointSuggestion(); return insertPaddingBRElementResultOrError.propagateErr();
}
insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion();
rv = insertEmptyTextResult.SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) {
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); return Err(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "CaretPoint::SuggestCaretPointTo() failed, but ignored"); return EditActionResult::HandledResult();
}
auto compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>(); if (!compositionEndPoint.IsSet()) {
compositionEndPoint = pointToInsert;
}
Result<InsertTextResult, nsresult> replaceTextResult =
WhiteSpaceVisibilityKeeper::ReplaceText(
*this, aInsertionString,
EditorDOMRange(pointToInsert, compositionEndPoint),
InsertTextTo::ExistingTextNodeIfAvailable); if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed"); return replaceTextResult.propagateErr();
}
InsertTextResult unwrappedReplacedTextResult = replaceTextResult.unwrap();
nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
unwrappedReplacedTextResult.EndOfInsertedTextRef()); if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); return Err(rv);
} // CompositionTransaction should've set selection so that we should ignore // caret suggestion.
unwrappedReplacedTextResult.IgnoreCaretPointSuggestion();
constauto newCompositionStartPoint =
GetFirstIMESelectionStartPoint<EditorDOMPoint>(); constauto newCompositionEndPoint =
GetLastIMESelectionEndPoint<EditorDOMPoint>(); if (NS_WARN_IF(!newCompositionStartPoint.IsSet()) ||
NS_WARN_IF(!newCompositionEndPoint.IsSet())) { // Mutation event listener has changed the DOM tree... return EditActionResult::HandledResult();
}
rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
newCompositionStartPoint.ToRawRangeBoundary(),
newCompositionEndPoint.ToRawRangeBoundary()); if (NS_FAILED(rv)) {
NS_WARNING("nsRange::SetStartAndEnd() failed"); return Err(rv);
} return EditActionResult::HandledResult();
}
// 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. constbool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
*pointToInsert.ContainerAs<nsIContent>()); const Maybe<LineBreakType> lineBreakType = GetPreferredLineBreakType(
*pointToInsert.ContainerAs<nsIContent>(), *editingHost); if (NS_WARN_IF(lineBreakType.isNothing())) { return Err(NS_ERROR_FAILURE);
}
// 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);
{
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
constauto GetInsertTextTo = [](int32_t aInclusiveNextLinefeedOffset,
uint32_t aLineStartOffset) { if (aInclusiveNextLinefeedOffset > 0) { return aLineStartOffset > 0 // If we'll insert a <br> and we're inserting 2nd or later // line, we should always create new `Text` since it'll be // between 2 <br> elements.
? InsertTextTo::AlwaysCreateNewTextNode // If we'll insert a <br> and we're inserting first line, // we should append text to preceding text node, but // we don't want to insert it to a a following text node // because of avoiding to split the `Text`.
: InsertTextTo::ExistingTextNodeIfAvailableAndNotStart;
} // If we're inserting the last line, the text should be inserted to // start of the following `Text` if there is or middle of the `Text` // at insertion position if we're inserting only the line. return InsertTextTo::ExistingTextNodeIfAvailable;
};
// 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 || IsPlaintextMailComposer()) { if (*lineBreakType == LineBreakType::Linefeed) { // Both Chrome and us inserts a preformatted linefeed with its own // `Text` node in various cases. However, when inserting multiline // text, we should insert a `Text` because Chrome does so and the // comment field in https://discussions.apple.com/ handles the new // `Text` to split each line into a paragraph. At that time, it's // not assumed that inserted text is split at every linefeed.
MOZ_ASSERT(*lineBreakType == LineBreakType::Linefeed);
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(
*document, aInsertionString, currentPoint,
InsertTextTo::ExistingTextNodeIfAvailable); if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed"); return insertTextResult.propagateErr();
} // Ignore the caret suggestion because of `dontChangeMySelection` // above.
insertTextResult.inspect().IgnoreCaretPointSuggestion(); if (insertTextResult.inspect().Handled()) {
pointToInsert = currentPoint = insertTextResult.unwrap()
.EndOfInsertedTextRef()
.To<EditorDOMPoint>();
} else {
pointToInsert = currentPoint;
}
} else {
MOZ_ASSERT(*lineBreakType == LineBreakType::BRElement);
uint32_t nextOffset = 0; while (nextOffset < aInsertionString.Length()) { const uint32_t lineStartOffset = nextOffset; const int32_t inclusiveNextLinefeedOffset =
aInsertionString.FindChar(nsCRT::LF, lineStartOffset); const uint32_t lineLength =
inclusiveNextLinefeedOffset != -1
? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
lineStartOffset
: aInsertionString.Length() - lineStartOffset; if (lineLength) { // lineText does not include the preformatted line break. const nsDependentSubstring lineText(aInsertionString,
lineStartOffset, lineLength);
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(
*document, lineText, currentPoint,
GetInsertTextTo(inclusiveNextLinefeedOffset,
lineStartOffset)); if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed"); return insertTextResult.propagateErr();
} // Ignore the caret suggestion because of `dontChangeMySelection` // above.
insertTextResult.inspect().IgnoreCaretPointSuggestion(); if (insertTextResult.inspect().Handled()) {
pointToInsert = currentPoint = insertTextResult.unwrap()
.EndOfInsertedTextRef()
.To<EditorDOMPoint>();
} else {
pointToInsert = currentPoint;
} if (inclusiveNextLinefeedOffset < 0) { break; // We reached the last line
}
}
MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
InsertLineBreak(WithTransaction::Yes, *lineBreakType,
currentPoint); if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
NS_WARNING(nsPrintfCString("HTMLEditor::InsertLineBreak(" "WithTransaction::Yes, %s) failed",
ToString(*lineBreakType).c_str())
.get()); return insertLineBreakResultOrError.propagateErr();
}
CreateLineBreakResult insertLineBreakResult =
insertLineBreakResultOrError.unwrap(); // We don't want to update selection here because we've blocked // InsertNodeTransaction updating selection with // dontChangeMySelection.
insertLineBreakResult.IgnoreCaretPointSuggestion();
MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
if (lineLength) { auto insertTextResult =
[&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> { // lineText does not include the preformatted line break. const nsDependentSubstring lineText(aInsertionString,
lineStartOffset, lineLength); if (!lineText.Contains(u'\t')) { return WhiteSpaceVisibilityKeeper::InsertText(
*this, lineText, currentPoint,
GetInsertTextTo(inclusiveNextLinefeedOffset,
lineStartOffset));
}
nsAutoString formattedLineText(lineText);
formattedLineText.ReplaceSubstring(u"\t"_ns, u" "_ns); return WhiteSpaceVisibilityKeeper::InsertText(
*this, formattedLineText, currentPoint,
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset));
}(); if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed"); return insertTextResult.propagateErr();
} // Ignore the caret suggestion because of `dontChangeMySelection` // above.
insertTextResult.inspect().IgnoreCaretPointSuggestion(); if (insertTextResult.inspect().Handled()) {
pointToInsert = currentPoint =
insertTextResult.unwrap().EndOfInsertedTextRef();
} else {
pointToInsert = currentPoint;
} if (inclusiveNextLinefeedOffset < 0) { break; // We reached the last line
}
}
Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
WhiteSpaceVisibilityKeeper::InsertLineBreak(*lineBreakType, *this,
currentPoint); if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
NS_WARNING(
nsPrintfCString( "WhiteSpaceVisibilityKeeper::InsertLineBreak(%s) failed",
ToString(*lineBreakType).c_str())
.get()); return insertLineBreakResultOrError.propagateErr();
}
CreateLineBreakResult insertLineBreakResult =
insertLineBreakResultOrError.unwrap(); // TODO: Some methods called for handling non-preformatted text use // ComputeEditingHost(). Therefore, they depend on the latest // selection. So we cannot skip updating selection here.
nsresult rv = insertLineBreakResult.SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) {
NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed"); return Err(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
nextOffset = inclusiveNextLinefeedOffset + 1;
pointToInsert = insertLineBreakResult.AfterLineBreak<EditorDOMPoint>();
currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef()); if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) ||
NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
}
// After this block, pointToInsert is updated by AutoTrackDOMPoint.
}
if (currentPoint.IsSet()) { // If we appended a collapsible white-space to the end of the text node, // its following content may be removed by the web app. Then, we need to // keep it visible even if it becomes immediately before a block boundary. // For referring the node from our mutation observer, we need to store the // text node temporarily. if (currentPoint.IsInTextNode() &&
MOZ_LIKELY(!currentPoint.IsStartOfContainer()) &&
currentPoint.IsEndOfContainer() &&
currentPoint.IsPreviousCharCollapsibleASCIISpace()) {
mLastCollapsibleWhiteSpaceAppendedTextNode =
currentPoint.ContainerAs<Text>();
}
nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(currentPoint); if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); return Err(rv);
}
currentPoint.SetInterlinePosition(InterlinePosition::EndOfLine);
rv = CollapseSelectionTo(currentPoint); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(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.
rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary()); if (NS_FAILED(rv)) {
NS_WARNING("nsRange::SetStartAndEnd() failed"); return Err(rv);
} return EditActionResult::HandledResult();
}
if (NS_WARN_IF(!mInitSucceeded)) { return NS_ERROR_NOT_INITIALIZED;
}
{
Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); return result.unwrapErr();
} if (result.inspect().Canceled()) { return NS_OK;
}
}
// 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,
__FUNCTION__);
// 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))) {
--> --------------------
--> maximum size reached
--> --------------------
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.14Angebot
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.