Quelle WhiteSpaceVisibilityKeeper.cpp
Sprache: unbekannt
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include"WhiteSpaceVisibilityKeeper.h"
#include"EditorDOMPoint.h" #include"EditorUtils.h" #include"ErrorList.h" #include"HTMLEditHelpers.h"// for MoveNodeResult, SplitNodeResult #include"HTMLEditor.h" #include"HTMLEditorNestedClasses.h"// for AutoMoveOneLineHandler #include"HTMLEditUtils.h" #include"SelectionState.h"
#include"mozilla/Assertions.h" #include"mozilla/SelectionState.h" #include"mozilla/OwningNonNull.h" #include"mozilla/StaticPrefs_editor.h"// for StaticPrefs::editor_* #include"mozilla/InternalMutationEvent.h" #include"mozilla/dom/AncestorIterator.h"
// The container of aPointToSplit may be not splittable, e.g., selection // may be collapsed **in** a `<br>` element or a comment node. So, look // for splittable point with climbing the tree up.
EditorDOMPoint pointToSplit(aPointToSplit); for (nsIContent* content : aPointToSplit.ContainerAs<nsIContent>()
->InclusiveAncestorsOfType<nsIContent>()) { if (content == &aSplittingBlockElement) { break;
} if (HTMLEditUtils::IsSplittableNode(*content)) { break;
}
pointToSplit.Set(content);
}
// NOTE: This method may extend deletion range: // - to delete invisible white-spaces at end of aLeftBlockElement // - to delete invisible white-spaces at start of // afterRightBlockChild.GetChild() // - to delete invisible white-spaces before afterRightBlockChild.GetChild() // - to delete invisible `<br>` element at end of aLeftBlockElement
{
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement)); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() " "failed"); return caretPointOrError.propagateErr();
} // Ignore caret suggestion because there was // AutoTransactionsConserveSelection.
caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
}
// Check whether aLeftBlockElement is a descendant of aRightBlockElement. if (aHTMLEditor.MayHaveMutationEventListeners()) {
EditorDOMPoint leftBlockContainingPointInRightBlockElement; if (aHTMLEditor.MayHaveMutationEventListeners() &&
MOZ_UNLIKELY(!EditorUtils::IsDescendantOf(
aLeftBlockElement, aRightBlockElement,
&leftBlockContainingPointInRightBlockElement))) {
NS_WARNING( "Deleting invisible whitespace at end of left block element caused " "moving the left block element outside the right block element"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
if (MOZ_UNLIKELY(leftBlockContainingPointInRightBlockElement !=
aAtRightBlockChild)) {
NS_WARNING( "Deleting invisible whitespace at end of left block element caused " "changing the left block element in the right block element"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aRightBlockElement,
EditorType::HTML))) {
NS_WARNING( "Deleting invisible whitespace at end of left block element caused " "making the right block element non-editable"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aLeftBlockElement,
EditorType::HTML))) {
NS_WARNING( "Deleting invisible whitespace at end of left block element caused " "making the left block element non-editable"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
OwningNonNull<Element> rightBlockElement = aRightBlockElement;
EditorDOMPoint afterRightBlockChild = aAtRightBlockChild.NextPoint();
{ // We can't just track rightBlockElement because it's an Element.
AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(),
&afterRightBlockChild);
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
aHTMLEditor, afterRightBlockChild); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() " "failed"); return caretPointOrError.propagateErr();
} // Ignore caret suggestion because there was // AutoTransactionsConserveSelection.
caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
// XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here. // Do we really need to do update rightBlockElement here?? // XXX And afterRightBlockChild.GetContainerAs<Element>() always returns // an element pointer so that probably here should not use // accessors of EditorDOMPoint, should use DOM API directly instead. if (afterRightBlockChild.GetContainerAs<Element>()) {
rightBlockElement = *afterRightBlockChild.ContainerAs<Element>();
} elseif (NS_WARN_IF(
!afterRightBlockChild.GetContainerParentAs<Element>())) { return Err(NS_ERROR_UNEXPECTED);
} else {
rightBlockElement = *afterRightBlockChild.GetContainerParentAs<Element>();
}
}
// Do br adjustment. // XXX Why don't we delete the <br> first? If so, we can skip to track the // MoveNodeResult at last. const RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
WSRunScanner::Scan::EditableNodes,
EditorDOMPoint::AtEndOf(aLeftBlockElement),
BlockInlineCheck::UseComputedDisplayStyle);
NS_ASSERTION(
aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement, "The preceding invisible BR element computation was different"); auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
-> Result<MoveNodeResult, nsresult> { // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of // AutoInclusiveAncestorBlockElementsJoiner. if (NS_WARN_IF(aListElementTagName.isSome())) { // Since 2002, here was the following comment: // > The idea here is to take all children in rightListElement that are // > past offset, and pull them into leftlistElement. // However, this has never been performed because we are here only when // neither left list nor right list is a descendant of the other but // in such case, getting a list item in the right list node almost // always failed since a variable for offset of // rightListElement->GetChildAt() was not initialized. So, it might be // a bug, but we should keep this traditional behavior for now. If you // find when we get here, please remove this comment if we don't need to // do it. Otherwise, please move children of the right list node to the // end of the left list node.
// XXX Although, we do nothing here, but for keeping traditional // behavior, we should mark as handled. return MoveNodeResult::HandledResult(
EditorDOMPoint::AtEndOf(aLeftBlockElement));
}
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); // XXX Why do we ignore the result of AutoMoveOneLineHandler::Run()?
NS_ASSERTION(rightBlockElement == afterRightBlockChild.GetContainer(), "The relation is not guaranteed but assumed"); #ifdef DEBUG
Result<bool, nsresult> firstLineHasContent =
HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()),
aEditingHost); #endif// #ifdef DEBUG
HTMLEditor::AutoMoveOneLineHandler lineMoverToEndOfLeftBlock(
aLeftBlockElement);
nsresult rv = lineMoverToEndOfLeftBlock.Prepare(
aHTMLEditor,
EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()),
aEditingHost); if (NS_FAILED(rv)) {
NS_WARNING("AutoMoveOneLineHandler::Prepare() failed"); return Err(rv);
}
MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(
EditorDOMPoint::AtEndOf(aLeftBlockElement));
AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
&moveResult);
Result<MoveNodeResult, nsresult> moveFirstLineResult =
lineMoverToEndOfLeftBlock.Run(aHTMLEditor, aEditingHost); if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
NS_WARNING("AutoMoveOneLineHandler::Run() failed"); return moveFirstLineResult.propagateErr();
}
trackMoveResult.FlushAndStopTracking();
#ifdef DEBUG
MOZ_ASSERT(!firstLineHasContent.isErr()); if (firstLineHasContent.inspect()) {
NS_ASSERTION(moveFirstLineResult.inspect().Handled(), "Failed to consider whether moving or not something");
} else {
NS_ASSERTION(moveFirstLineResult.inspect().Ignored(), "Failed to consider whether moving or not something");
} #endif// #ifdef DEBUG
moveResult |= moveFirstLineResult.unwrap(); // Now, all children of rightBlockElement were moved to leftBlockElement. // So, afterRightBlockChild is now invalid.
afterRightBlockChild.Clear();
return std::move(moveResult);
}(); if (MOZ_UNLIKELY(moveContentResult.isErr())) { return moveContentResult;
}
if (!invisibleBRElementAtEndOfLeftBlockElement ||
!invisibleBRElementAtEndOfLeftBlockElement->IsInComposedDoc()) { return moveContentResult;
}
// NOTE: This method may extend deletion range: // - to delete invisible white-spaces at start of aRightBlockElement // - to delete invisible white-spaces before aRightBlockElement // - to delete invisible white-spaces at start of aAtLeftBlockChild.GetChild() // - to delete invisible white-spaces before aAtLeftBlockChild.GetChild() // - to delete invisible `<br>` element before aAtLeftBlockChild.GetChild()
{
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0)); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() " "failed"); return caretPointOrError.propagateErr();
} // Ignore caret suggestion because there was // AutoTransactionsConserveSelection.
caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
}
// Check whether aRightBlockElement is a descendant of aLeftBlockElement. if (aHTMLEditor.MayHaveMutationEventListeners()) {
EditorDOMPoint rightBlockContainingPointInLeftBlockElement; if (aHTMLEditor.MayHaveMutationEventListeners() &&
MOZ_UNLIKELY(!EditorUtils::IsDescendantOf(
aRightBlockElement, aLeftBlockElement,
&rightBlockContainingPointInLeftBlockElement))) {
NS_WARNING( "Deleting invisible whitespace at start of right block element " "caused moving the right block element outside the left block " "element"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
if (MOZ_UNLIKELY(rightBlockContainingPointInLeftBlockElement !=
aAtLeftBlockChild)) {
NS_WARNING( "Deleting invisible whitespace at start of right block element " "caused changing the right block element position in the left block " "element"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aLeftBlockElement,
EditorType::HTML))) {
NS_WARNING( "Deleting invisible whitespace at start of right block element " "caused making the left block element non-editable"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aRightBlockElement,
EditorType::HTML))) {
NS_WARNING( "Deleting invisible whitespace at start of right block element " "caused making the right block element non-editable"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
OwningNonNull<Element> originalLeftBlockElement = aLeftBlockElement;
OwningNonNull<Element> leftBlockElement = aLeftBlockElement;
EditorDOMPoint atLeftBlockChild(aAtLeftBlockChild);
{ // We can't just track leftBlockElement because it's an Element, so track // something else.
AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &atLeftBlockChild);
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
aHTMLEditor, EditorDOMPoint(atLeftBlockChild.GetContainer(),
atLeftBlockChild.Offset())); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() " "failed"); return caretPointOrError.propagateErr();
} // Ignore caret suggestion because there was // AutoTransactionsConserveSelection.
caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
} if (MOZ_UNLIKELY(!atLeftBlockChild.IsSetAndValid())) {
NS_WARNING( "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused " "unexpected DOM tree"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
} // XXX atLeftBlockChild.GetContainerAs<Element>() should always return // an element pointer so that probably here should not use // accessors of EditorDOMPoint, should use DOM API directly instead. if (Element* nearestAncestor =
atLeftBlockChild.GetContainerOrContainerParentElement()) {
leftBlockElement = *nearestAncestor;
} else { return Err(NS_ERROR_UNEXPECTED);
}
// Do br adjustment. // XXX Why don't we delete the <br> first? If so, we can skip to track the // MoveNodeResult at last. const RefPtr<HTMLBRElement> invisibleBRElementBeforeLeftBlockElement =
WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
WSRunScanner::Scan::EditableNodes, atLeftBlockChild,
BlockInlineCheck::UseComputedDisplayStyle);
NS_ASSERTION(
aPrecedingInvisibleBRElement == invisibleBRElementBeforeLeftBlockElement, "The preceding invisible BR element computation was different"); auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
-> Result<MoveNodeResult, nsresult> { // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of // AutoInclusiveAncestorBlockElementsJoiner. if (aListElementTagName.isSome()) { // XXX Why do we ignore the error from MoveChildrenWithTransaction()?
MOZ_ASSERT(originalLeftBlockElement == atLeftBlockChild.GetContainer(), "This is not guaranteed, but assumed"); #ifdef DEBUG
Result<bool, nsresult> rightBlockHasContent =
aHTMLEditor.CanMoveChildren(aRightBlockElement, aLeftBlockElement); #endif// #ifdef DEBUG
MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(EditorDOMPoint(
atLeftBlockChild.GetContainer(), atLeftBlockChild.Offset()));
AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
&moveResult); // TODO: Stop using HTMLEditor::PreserveWhiteSpaceStyle::No due to no // tests.
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
Result<MoveNodeResult, nsresult> moveChildrenResult =
aHTMLEditor.MoveChildrenWithTransaction(
aRightBlockElement, moveResult.NextInsertionPointRef(),
HTMLEditor::PreserveWhiteSpaceStyle::No,
HTMLEditor::RemoveIfCommentNode::Yes); if (MOZ_UNLIKELY(moveChildrenResult.isErr())) { if (NS_WARN_IF(moveChildrenResult.inspectErr() ==
NS_ERROR_EDITOR_DESTROYED)) { return moveChildrenResult;
}
NS_WARNING( "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
} else { #ifdef DEBUG
MOZ_ASSERT(!rightBlockHasContent.isErr()); if (rightBlockHasContent.inspect()) {
NS_ASSERTION(moveChildrenResult.inspect().Handled(), "Failed to consider whether moving or not children");
} else {
NS_ASSERTION(moveChildrenResult.inspect().Ignored(), "Failed to consider whether moving or not children");
} #endif// #ifdef DEBUG
trackMoveResult.FlushAndStopTracking();
moveResult |= moveChildrenResult.unwrap();
} // atLeftBlockChild was moved to rightListElement. So, it's invalid now.
atLeftBlockChild.Clear();
return std::move(moveResult);
}
// Left block is a parent of right block, and the parent of the previous // visible content. Right block is a child and contains the contents we // want to move.
EditorDOMPoint pointToMoveFirstLineContent; if (&aLeftContentInBlock == leftBlockElement) { // We are working with valid HTML, aLeftContentInBlock is a block // element, and is therefore allowed to contain aRightBlockElement. This // is the simple case, we will simply move the content in // aRightBlockElement out of its block.
pointToMoveFirstLineContent = atLeftBlockChild;
MOZ_ASSERT(pointToMoveFirstLineContent.GetContainer() ==
&aLeftBlockElement);
} else { if (NS_WARN_IF(!aLeftContentInBlock.IsInComposedDoc())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
} // We try to work as well as possible with HTML that's already invalid. // Although "right block" is a block, and a block must not be contained // in inline elements, reality is that broken documents do exist. The // DIRECT parent of "left NODE" might be an inline element. Previous // versions of this code skipped inline parents until the first block // parent was found (and used "left block" as the destination). // However, in some situations this strategy moves the content to an // unexpected position. (see bug 200416) The new idea is to make the // moving content a sibling, next to the previous visible content.
pointToMoveFirstLineContent.SetAfter(&aLeftContentInBlock); if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
if (&aLeftContentInBlock != &aEditingHost) {
Result<SplitNodeResult, nsresult> splitNodeResult =
aHTMLEditor.SplitAncestorStyledInlineElementsAt(
pointToMoveFirstLineContent, EditorInlineStyle::RemoveAllStyles(),
HTMLEditor::SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed"); return splitNodeResult.propagateErr();
}
SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
nsresult rv = unwrappedSplitNodeResult.SuggestCaretPointTo(
aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); if (NS_FAILED(rv)) {
NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); return Err(rv);
} if (!unwrappedSplitNodeResult.DidSplit()) { // If nothing was split, we should move the first line content to // after the parent inline elements. for (EditorDOMPoint parentPoint = pointToMoveFirstLineContent;
pointToMoveFirstLineContent.IsEndOfContainer() &&
pointToMoveFirstLineContent.IsInContentNode();
pointToMoveFirstLineContent = EditorDOMPoint::After(
*pointToMoveFirstLineContent.ContainerAs<nsIContent>())) { if (pointToMoveFirstLineContent.GetContainer() ==
&aLeftBlockElement ||
NS_WARN_IF(pointToMoveFirstLineContent.GetContainer() ==
&aEditingHost)) { break;
}
} if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { return Err(NS_ERROR_FAILURE);
}
} elseif (unwrappedSplitNodeResult.Handled()) { // If se split something, we should move the first line contents // before the right elements. if (nsIContent* nextContentAtSplitPoint =
unwrappedSplitNodeResult.GetNextContent()) {
pointToMoveFirstLineContent.Set(nextContentAtSplitPoint); if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { return Err(NS_ERROR_FAILURE);
}
} else {
pointToMoveFirstLineContent =
unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>(); if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { return Err(NS_ERROR_FAILURE);
}
}
}
MOZ_DIAGNOSTIC_ASSERT(pointToMoveFirstLineContent.IsSetAndValid());
}
#ifdef DEBUG
MOZ_ASSERT(!firstLineHasContent.isErr()); if (firstLineHasContent.inspect()) {
NS_ASSERTION(moveFirstLineResult.inspect().Handled(), "Failed to consider whether moving or not something");
} else {
NS_ASSERTION(moveFirstLineResult.inspect().Ignored(), "Failed to consider whether moving or not something");
} #endif// #ifdef DEBUG
// NOTE: This method may extend deletion range: // - to delete invisible white-spaces at end of aLeftBlockElement // - to delete invisible white-spaces at start of aRightBlockElement // - to delete invisible `<br>` element at end of aLeftBlockElement
// Adjust white-space at block boundaries
{
Result<CaretPoint, nsresult> caretPointOrError =
WhiteSpaceVisibilityKeeper::
MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
aHTMLEditor,
EditorDOMRange(EditorDOMPoint::AtEndOf(aLeftBlockElement),
EditorDOMPoint(&aRightBlockElement, 0)),
aEditingHost); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "WhiteSpaceVisibilityKeeper::" "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() " "failed"); return caretPointOrError.propagateErr();
} // Ignore caret point suggestion because there was // AutoTransactionsConserveSelection.
caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
} // Do br adjustment. // XXX Why don't we delete the <br> first? If so, we can skip to track the // MoveNodeResult at last. const RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
WSRunScanner::Scan::EditableNodes,
EditorDOMPoint::AtEndOf(aLeftBlockElement),
BlockInlineCheck::UseComputedDisplayStyle);
NS_ASSERTION(
aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement, "The preceding invisible BR element computation was different"); auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
-> Result<MoveNodeResult, nsresult> { if (aListElementTagName.isSome() || // TODO: We should stop merging entire blocks even if they have same // white-space style because Chrome behave so. However, it's risky to // change our behavior in the major cases so that we should do it in // a bug to manage only the change.
(aLeftBlockElement.NodeInfo()->NameAtom() ==
aRightBlockElement.NodeInfo()->NameAtom() &&
EditorUtils::GetComputedWhiteSpaceStyles(aLeftBlockElement) ==
EditorUtils::GetComputedWhiteSpaceStyles(aRightBlockElement))) {
MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(
EditorDOMPoint::AtEndOf(aLeftBlockElement));
AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
&moveResult);
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); // Nodes are same type. merge them.
EditorDOMPoint atFirstChildOfRightNode;
nsresult rv = aHTMLEditor.JoinNearestEditableNodesWithTransaction(
aLeftBlockElement, aRightBlockElement, &atFirstChildOfRightNode); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "HTMLEditor::JoinNearestEditableNodesWithTransaction()" " failed, but ignored"); if (aListElementTagName.isSome() && atFirstChildOfRightNode.IsSet()) {
Result<CreateElementResult, nsresult> convertListTypeResult =
aHTMLEditor.ChangeListElementType( // XXX Shouldn't be aLeftBlockElement here?
aRightBlockElement, MOZ_KnownLive(*aListElementTagName.ref()),
*nsGkAtoms::li); if (MOZ_UNLIKELY(convertListTypeResult.isErr())) { if (NS_WARN_IF(convertListTypeResult.inspectErr() ==
NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING("HTMLEditor::ChangeListElementType() failed, but ignored");
} else { // There is AutoTransactionConserveSelection above, therefore, we // don't need to update selection here.
convertListTypeResult.inspect().IgnoreCaretPointSuggestion();
}
}
trackMoveResult.FlushAndStopTracking();
moveResult |= MoveNodeResult::HandledResult(
EditorDOMPoint::AtEndOf(aLeftBlockElement)); return std::move(moveResult);
}
#ifdef DEBUG
MOZ_ASSERT(!firstLineHasContent.isErr()); if (firstLineHasContent.inspect()) {
NS_ASSERTION(moveFirstLineResult.inspect().Handled(), "Failed to consider whether moving or not something");
} else {
NS_ASSERTION(moveFirstLineResult.inspect().Ignored(), "Failed to consider whether moving or not something");
} #endif// #ifdef DEBUG
{
AutoTrackDOMMoveNodeResult trackMoveContentResult(
aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult);
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
*invisibleBRElementAtEndOfLeftBlockElement); // XXX In other top level if blocks, the result of // DeleteNodeWithTransaction() is ignored. Why does only this result // is respected? if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
unwrappedMoveContentResult.IgnoreCaretPointSuggestion(); return Err(rv);
}
} return std::move(unwrappedMoveContentResult);
}
// MOOSE: for now, we always assume non-PRE formatting. Fix this later. // meanwhile, the pre case is handled in HandleInsertText() in // HTMLEditSubActionHandler.cpp
// static
Result<InsertTextResult, nsresult> WhiteSpaceVisibilityKeeper::ReplaceText(
HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert, const EditorDOMRange& aRangeToBeReplaced, InsertTextTo aInsertTextTo) { // MOOSE: for now, we always assume non-PRE formatting. Fix this later. // meanwhile, the pre case is handled in HandleInsertText() in // HTMLEditSubActionHandler.cpp
// MOOSE: for now, just getting the ws logic straight. This implementation // is very slow. Will need to replace edit rules impl with a more efficient // text sink here that does the minimal amount of searching/replacing/copying
if (aStringToInsert.IsEmpty()) {
MOZ_ASSERT(aRangeToBeReplaced.Collapsed()); return InsertTextResult();
}
EditorDOMPoint pointToPutCaret;
EditorDOMPoint pointToInsert(aRangeToBeReplaced.StartRef());
EditorDOMPoint atNBSPReplaceableWithSP; if (!invisibleTrailingWhiteSpaceRangeAtEnd.IsPositioned() &&
(pointPositionWithVisibleWhiteSpacesAtStart ==
PointPosition::MiddleOfFragment ||
pointPositionWithVisibleWhiteSpacesAtStart ==
PointPosition::EndOfFragment)) {
atNBSPReplaceableWithSP =
textFragmentDataAtStart
.GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
pointToInsert)
.To<EditorDOMPoint>();
}
nsAutoString theString(aStringToInsert);
{ if (invisibleTrailingWhiteSpaceRangeAtEnd.IsPositioned()) { if (!invisibleTrailingWhiteSpaceRangeAtEnd.Collapsed()) {
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
&pointToInsert);
AutoTrackDOMPoint trackPrecedingNBSP(aHTMLEditor.RangeUpdaterRef(),
&atNBSPReplaceableWithSP);
AutoTrackDOMRange trackInvisibleLeadingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleLeadingWhiteSpaceRangeAtStart);
AutoTrackDOMRange trackInvisibleTrailingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleTrailingWhiteSpaceRangeAtEnd); // XXX Why don't we remove all of the invisible white-spaces?
MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd.StartRef() ==
pointToInsert);
Result<CaretPoint, nsresult> caretPointOrError =
aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
invisibleTrailingWhiteSpaceRangeAtEnd.StartRef(),
invisibleTrailingWhiteSpaceRangeAtEnd.EndRef(),
HTMLEditor::TreatEmptyTextNodes::
KeepIfContainerOfRangeBoundaries); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); return caretPointOrError.propagateErr();
}
caretPointOrError.unwrap().MoveCaretPointTo(
pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
}
} // Replace an NBSP at inclusive next character of replacing range to an // ASCII white-space if inserting into a visible white-space sequence. // XXX With modifying the inserting string later, this creates a line break // opportunity after the inserting string, but this causes // inconsistent result with inserting order. E.g., type white-space // n times with various order. elseif (pointPositionWithVisibleWhiteSpacesAtEnd ==
PointPosition::StartOfFragment ||
pointPositionWithVisibleWhiteSpacesAtEnd ==
PointPosition::MiddleOfFragment) {
EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace =
textFragmentDataAtEnd
.GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
aRangeToBeReplaced.EndRef()); if (atNBSPReplacedWithASCIIWhiteSpace.IsSet()) {
AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
&pointToInsert);
AutoTrackDOMPoint trackPrecedingNBSP(aHTMLEditor.RangeUpdaterRef(),
&atNBSPReplaceableWithSP);
AutoTrackDOMRange trackInvisibleLeadingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleLeadingWhiteSpaceRangeAtStart);
AutoTrackDOMRange trackInvisibleTrailingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleTrailingWhiteSpaceRangeAtEnd);
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns); if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); return replaceTextResult.propagateErr();
} // Ignore caret suggestion because there was // AutoTransactionsConserveSelection.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
}
}
if (invisibleLeadingWhiteSpaceRangeAtStart.IsPositioned()) { if (!invisibleLeadingWhiteSpaceRangeAtStart.Collapsed()) {
AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
&pointToInsert);
AutoTrackDOMRange trackInvisibleTrailingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleTrailingWhiteSpaceRangeAtEnd); // XXX Why don't we remove all of the invisible white-spaces?
MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeAtStart.EndRef() ==
pointToInsert);
Result<CaretPoint, nsresult> caretPointOrError =
aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
invisibleLeadingWhiteSpaceRangeAtStart.StartRef(),
invisibleLeadingWhiteSpaceRangeAtStart.EndRef(),
HTMLEditor::TreatEmptyTextNodes::
KeepIfContainerOfRangeBoundaries); if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING( "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); return caretPointOrError.propagateErr();
}
trackPointToPutCaret.FlushAndStopTracking();
caretPointOrError.unwrap().MoveCaretPointTo(
pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); // Don't refer the following variables anymore unless tracking the // change.
atNBSPReplaceableWithSP.Clear();
invisibleLeadingWhiteSpaceRangeAtStart.Clear();
}
} // Replace an NBSP at previous character of insertion point to an ASCII // white-space if inserting into a visible white-space sequence. // XXX With modifying the inserting string later, this creates a line break // opportunity before the inserting string, but this causes // inconsistent result with inserting order. E.g., type white-space // n times with various order. elseif (atNBSPReplaceableWithSP.IsInTextNode()) {
EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace =
atNBSPReplaceableWithSP.AsInText(); if (!atNBSPReplacedWithASCIIWhiteSpace.IsEndOfContainer() &&
atNBSPReplacedWithASCIIWhiteSpace.IsCharNBSP()) {
AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
&pointToInsert);
AutoTrackDOMRange trackInvisibleTrailingWhiteSpaceRange(
aHTMLEditor.RangeUpdaterRef(),
&invisibleTrailingWhiteSpaceRangeAtEnd);
Result<InsertTextResult, nsresult> replaceTextResult =
aHTMLEditor.ReplaceTextWithTransaction(
MOZ_KnownLive(
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns); if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed"); return replaceTextResult.propagateErr();
} // Ignore caret suggestion because there was // AutoTransactionsConserveSelection.
replaceTextResult.unwrap().IgnoreCaretPointSuggestion(); // Don't refer the following variables anymore unless tracking the // change.
atNBSPReplaceableWithSP.Clear();
invisibleLeadingWhiteSpaceRangeAtStart.Clear();
}
}
}
// If white-space and/or linefeed characters are collapsible, and inserting // string starts and/or ends with a collapsible characters, we need to // replace them with NBSP for making sure the collapsible characters visible. // FYI: There is no case only linefeeds are collapsible. So, we need to // do the things only when white-spaces are collapsible.
MOZ_DIAGNOSTIC_ASSERT(!theString.IsEmpty()); if (NS_WARN_IF(!pointToInsert.IsInContentNode()) ||
!EditorUtils::IsWhiteSpacePreformatted(
*pointToInsert.ContainerAs<nsIContent>())) { constbool isNewLineCollapsible =
!pointToInsert.IsInContentNode() ||
!EditorUtils::IsNewLinePreformatted(
*pointToInsert.ContainerAs<nsIContent>()); auto IsCollapsibleChar = [&isNewLineCollapsible](char16_t aChar) -> bool { return nsCRT::IsAsciiSpace(aChar) &&
(isNewLineCollapsible || aChar != HTMLEditUtils::kNewLine);
}; if (IsCollapsibleChar(theString[0])) { // If inserting string will follow some invisible leading white-spaces, // the string needs to start with an NBSP. if (isInvisibleLeadingWhiteSpaceRangeAtStartPositioned) {
theString.SetCharAt(HTMLEditUtils::kNBSP, 0);
} // If inserting around visible white-spaces, check whether the previous // character of insertion point is an NBSP or an ASCII white-space. elseif (pointPositionWithVisibleWhiteSpacesAtStart ==
PointPosition::MiddleOfFragment ||
pointPositionWithVisibleWhiteSpacesAtStart ==
PointPosition::EndOfFragment) { constauto atPreviousChar =
textFragmentDataAtStart
.GetPreviousCharPoint<EditorRawDOMPointInText>(
pointToInsert, IgnoreNonEditableNodes::Yes); if (atPreviousChar.IsSet() && !atPreviousChar.IsEndOfContainer() &&
atPreviousChar.IsCharASCIISpace()) {
theString.SetCharAt(HTMLEditUtils::kNBSP, 0);
}
} // If the insertion point is (was) before the start of text and it's // immediately after a hard line break, the first ASCII white-space should // be replaced with an NBSP for making it visible. elseif ((textFragmentDataAtStart.StartsFromHardLineBreak() ||
textFragmentDataAtStart
.StartsFromInlineEditingHostBoundary()) &&
isInsertionPointEqualsOrIsBeforeStartOfText) {
theString.SetCharAt(HTMLEditUtils::kNBSP, 0);
}
}
// Then the tail. Note that it may be the first character. const uint32_t lastCharIndex = theString.Length() - 1; if (IsCollapsibleChar(theString[lastCharIndex])) { // If inserting string will be followed by some invisible trailing // white-spaces, the string needs to end with an NBSP. if (isInvisibleTrailingWhiteSpaceRangeAtEndPositioned) {
theString.SetCharAt(HTMLEditUtils::kNBSP, lastCharIndex);
} // If inserting around visible white-spaces, check whether the inclusive // next character of end of replaced range is an NBSP or an ASCII // white-space. if (pointPositionWithVisibleWhiteSpacesAtEnd ==
PointPosition::StartOfFragment ||
pointPositionWithVisibleWhiteSpacesAtEnd ==
PointPosition::MiddleOfFragment) { constauto atNextChar =
textFragmentDataAtEnd
.GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
pointToInsert, IgnoreNonEditableNodes::Yes); if (atNextChar.IsSet() && !atNextChar.IsEndOfContainer() &&
atNextChar.IsCharASCIISpace()) {
theString.SetCharAt(HTMLEditUtils::kNBSP, lastCharIndex);
}
} // If the end of replacing range is (was) after the end of text and it's // immediately before block boundary, the last ASCII white-space should // be replaced with an NBSP for making it visible. elseif ((textFragmentDataAtEnd.EndsByBlockBoundary() ||
textFragmentDataAtEnd.EndsByInlineEditingHostBoundary()) &&
isInsertionPointEqualsOrAfterEndOfText) {
theString.SetCharAt(HTMLEditUtils::kNBSP, lastCharIndex);
}
}
// Next, scan string for adjacent ws and convert to nbsp/space combos // MOOSE: don't need to convert tabs here since that is done by // WillInsertText() before we are called. Eventually, all that logic will // be pushed down into here and made more efficient. enumclass PreviousChar {
NonCollapsibleChar,
CollapsibleChar,
PreformattedNewLine,
};
PreviousChar previousChar = PreviousChar::NonCollapsibleChar; for (uint32_t i = 0; i <= lastCharIndex; i++) { if (IsCollapsibleChar(theString[i])) { // If current char is collapsible and 2nd or latter character of // collapsible characters, we need to make the previous character an // NBSP for avoiding current character to be collapsed to it. if (previousChar == PreviousChar::CollapsibleChar) {
MOZ_ASSERT(i > 0);
theString.SetCharAt(HTMLEditUtils::kNBSP, i - 1); // Keep previousChar as PreviousChar::CollapsibleChar. continue;
}
// If current character is a collapsbile white-space and the previous // character is a preformatted linefeed, we need to replace the current // character with an NBSP for avoiding collapsed with the previous // linefeed. if (previousChar == PreviousChar::PreformattedNewLine) {
MOZ_ASSERT(i > 0);
theString.SetCharAt(HTMLEditUtils::kNBSP, i);
previousChar = PreviousChar::NonCollapsibleChar; continue;
}
if (theString[i] != HTMLEditUtils::kNewLine) {
previousChar = PreviousChar::NonCollapsibleChar; continue;
}
// If current character is a preformatted linefeed and the previous // character is collapbile white-space, the previous character will be // collapsed into current linefeed. Therefore, we need to replace the // previous character with an NBSP.
MOZ_ASSERT(!isNewLineCollapsible); if (previousChar == PreviousChar::CollapsibleChar) {
--> --------------------
--> maximum size reached
--> --------------------
[ zur Elbe Produktseite wechseln0.35Quellennavigators
Analyse erneut starten
]