/* 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 "EditorBase.h" #include "HTMLEditor.h" #include "HTMLEditorInlines.h" #include "HTMLEditorNestedClasses.h" #include #include "AutoClonedRangeArray.h" #include "CSSEditUtils.h" #include "EditAction.h" #include "EditorDOMPoint.h" #include "EditorLineBreak.h" #include "EditorUtils.h" #include "HTMLEditHelpers.h" #include "HTMLEditUtils.h" #include "PendingStyles.h" // for SpecifiedStyle #include "WhiteSpaceVisibilityKeeper.h" #include "WSRunScanner.h" #include "ErrorList.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/ContentIterator.h" #include "mozilla/EditorForwards.h" #include "mozilla/Maybe.h" #include "mozilla/PresShell.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLBRElement.h" #include "mozilla/dom/Selection.h" #include "nsAtom.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsIContent.h" #include "nsIFrame.h" #include "nsINode.h" #include "nsTArray.h" #include "nsTextNode.h" namespace mozilla { using namespace dom; using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions; using LeafNodeOption = HTMLEditUtils::LeafNodeOption; using LeafNodeOptions = HTMLEditUtils::LeafNodeOptions; Result HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) { if (NS_WARN_IF(!mInitSucceeded)) { return Err(NS_ERROR_NOT_INITIALIZED); } { Result result = CanHandleHTMLEditSubAction(CheckSelectionInReplacedElement::No); if (MOZ_UNLIKELY(result.isErr())) { NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); return result; } if (result.inspect().Canceled()) { 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, __FUNCTION__); IgnoredErrorResult ignoredError; AutoEditSubActionNotifier startToHandleEditSubAction( *this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext, ignoredError); if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { return Err(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 Err(rv); } } AutoInsertParagraphHandler insertParagraphHandler(*this, aEditingHost); Result insertParagraphResult = insertParagraphHandler.Run(); NS_WARNING_ASSERTION(insertParagraphResult.isOk(), "AutoInsertParagraphHandler::Run() failed"); return insertParagraphResult; } Result HTMLEditor::AutoInsertParagraphHandler::Run() { MOZ_ASSERT(mHTMLEditor.IsEditActionDataAvailable()); MOZ_ASSERT(mHTMLEditor.IsTopLevelEditSubActionDataAvailable()); nsresult rv = mHTMLEditor.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) && mHTMLEditor.SelectionRef().IsCollapsed()) { nsresult rv = mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(mEditingHost); 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 = mHTMLEditor.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"); } } AutoClonedSelectionRangeArray selectionRanges(mHTMLEditor.SelectionRef()); selectionRanges.EnsureOnlyEditableRanges(mEditingHost); auto pointToInsert = selectionRanges.GetFirstRangeStartPoint(); if (NS_WARN_IF(!pointToInsert.IsInContentNode())) { return Err(NS_ERROR_FAILURE); } // If the element can have a
element (it means that the element or its // container must be able to have
or

too), we can handle // insertParagraph at the point. pointToInsert = HTMLEditUtils::GetPossiblePointToInsert( pointToInsert, *nsGkAtoms::br, mEditingHost); if (NS_WARN_IF(!pointToInsert.IsSet())) { return Err(NS_ERROR_FAILURE); } MOZ_ASSERT(pointToInsert.IsInContentNode()); if (mHTMLEditor.IsMailEditor()) { if (const RefPtr mailCiteElement = mHTMLEditor.GetMostDistantAncestorMailCiteElement( *pointToInsert.ContainerAs())) { // Split any mailcites in the way. Should we abort this if we encounter // table cell boundaries? Result caretPointOrError = HandleInMailCiteElement(*mailCiteElement, pointToInsert); if (MOZ_UNLIKELY(caretPointOrError.isErr())) { NS_WARNING( "AutoInsertParagraphHandler::HandleInMailCiteElement() failed"); return caretPointOrError.propagateErr(); } CaretPoint caretPoint = caretPointOrError.unwrap(); MOZ_ASSERT(caretPoint.HasCaretPointSuggestion()); MOZ_ASSERT(caretPoint.CaretPointRef().GetInterlinePosition() == InterlinePosition::StartOfNextLine); MOZ_ASSERT(caretPoint.CaretPointRef().GetChild()); MOZ_ASSERT( caretPoint.CaretPointRef().GetChild()->IsHTMLElement(nsGkAtoms::br)); nsresult rv = caretPoint.SuggestCaretPointTo(mHTMLEditor, {}); if (NS_FAILED(rv)) { NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); return Err(rv); } return EditActionResult::HandledResult(); } } // If the active editing host is an inline element, or if the active editing // host is the block parent itself and we're configured to use
as a // paragraph separator, just append a
. // If the editing host parent element is editable, it means that the editing // host must be a element and the selection may be outside the body // element. If the selection is outside the editing host, we should not // insert new paragraph nor
element. // XXX Currently, we don't support editing outside element, but Blink // does it. if (mEditingHost.GetParentElement() && HTMLEditUtils::IsSimplyEditableNode(*mEditingHost.GetParentElement()) && !nsContentUtils::ContentIsFlattenedTreeDescendantOf( pointToInsert.ContainerAs(), &mEditingHost)) { return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE); } // Look for the nearest parent block. However, don't return error even if // there is no block parent here because in such case, i.e., editing host // is an inline element, we should insert
simply. RefPtr editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement( *pointToInsert.ContainerAs(), HTMLEditUtils::ClosestEditableBlockElementOrButtonElement, BlockInlineCheck::UseComputedDisplayOutsideStyle); // If we cannot insert a

/

element at the selection, we should insert // a
element or a linefeed instead. if (ShouldInsertLineBreakInstead(editableBlockElement, pointToInsert)) { const Maybe lineBreakType = mHTMLEditor.GetPreferredLineBreakType( *pointToInsert.ContainerAs(), mEditingHost); if (MOZ_UNLIKELY(!lineBreakType)) { // Cannot insert a line break there. return EditActionResult::IgnoredResult(); } if (lineBreakType.value() == LineBreakType::Linefeed) { Result insertLinefeedResultOrError = HandleInsertLinefeed(pointToInsert); NS_WARNING_ASSERTION( insertLinefeedResultOrError.isOk(), "AutoInsertParagraphHandler::HandleInsertLinefeed() failed"); return insertLinefeedResultOrError; } Result insertBRElementResultOrError = HandleInsertBRElement(pointToInsert); NS_WARNING_ASSERTION( insertBRElementResultOrError.isOk(), "AutoInsertParagraphHandler::HandleInsertBRElement() failed"); return insertBRElementResultOrError; } RefPtr blockElementToPutCaret; // If the default paragraph separator is not
and selection is not in // a splittable block element, we should wrap selected contents in a new // paragraph, then, split it. if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement) && mDefaultParagraphSeparator != ParagraphSeparator::br) { MOZ_ASSERT(mDefaultParagraphSeparator == ParagraphSeparator::div || mDefaultParagraphSeparator == ParagraphSeparator::p); // FIXME: If there is no splittable block element, the other browsers wrap // the right nodes into new paragraph, but keep the left node as-is. // We should follow them to make here simpler and better compatibility. Result, nsresult> suggestBlockElementToPutCaretOrError = mHTMLEditor.FormatBlockContainerWithTransaction( selectionRanges, MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName( mDefaultParagraphSeparator)), // For keeping the traditional behavior at insertParagraph command, // let's use the XUL paragraph state command targets even if we're // handling HTML insertParagraph command. FormatBlockMode::XULParagraphStateCommand, mEditingHost); if (MOZ_UNLIKELY(suggestBlockElementToPutCaretOrError.isErr())) { NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed"); return suggestBlockElementToPutCaretOrError.propagateErr(); } if (selectionRanges.HasSavedRanges()) { selectionRanges.RestoreFromSavedRanges(); } pointToInsert = selectionRanges.GetFirstRangeStartPoint(); if (NS_WARN_IF(!pointToInsert.IsInContentNode())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement( *pointToInsert.ContainerAs(), HTMLEditUtils::ClosestEditableBlockElementOrButtonElement, BlockInlineCheck::UseComputedDisplayOutsideStyle); if (NS_WARN_IF(!editableBlockElement)) { return Err(NS_ERROR_UNEXPECTED); } if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement)) { // Didn't create a new block for some reason, e.g., it's a inline-block, // fall back to
. Result insertBRElementResultOrError = HandleInsertBRElement(pointToInsert, blockElementToPutCaret); NS_WARNING_ASSERTION( insertBRElementResultOrError.isOk(), "AutoInsertParagraphHandler::HandleInsertBRElement() failed"); return insertBRElementResultOrError; } // We want to collapse selection in the editable block element. blockElementToPutCaret = editableBlockElement; } // If block is empty, populate with br. (For example, imagine a div that // contains the word "text". The user selects "text" and types return. // "Text" is deleted leaving an empty block. We want to put in one br to // make block have a line. Then code further below will put in a second br.) RefPtr insertedPaddingBRElement; { Result insertBRElementResultOrError = InsertBRElementIfEmptyBlockElement( *editableBlockElement, InsertBRElementIntoEmptyBlock::End, BlockInlineCheck::UseComputedDisplayOutsideStyle); if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { NS_WARNING( "AutoInsertParagraphHandler::InsertBRElementIfEmptyBlockElement(" "InsertBRElementIntoEmptyBlock::End, " "BlockInlineCheck::UseComputedDisplayOutsideStyle) failed"); return insertBRElementResultOrError.propagateErr(); } CreateLineBreakResult insertBRElementResult = insertBRElementResultOrError.unwrap(); insertBRElementResult.IgnoreCaretPointSuggestion(); if (insertBRElementResult.Handled()) { insertedPaddingBRElement = &insertBRElementResult->BRElementRef(); } pointToInsert = selectionRanges.GetFirstRangeStartPoint(); if (NS_WARN_IF(!pointToInsert.IsInContentNode())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } } RefPtr maybeNonEditableListItem = HTMLEditUtils::GetClosestInclusiveAncestorListItemElement( *editableBlockElement, &mEditingHost); if (maybeNonEditableListItem && HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem)) { Result insertParagraphInListItemResult = HandleInListItemElement(*maybeNonEditableListItem, pointToInsert); if (MOZ_UNLIKELY(insertParagraphInListItemResult.isErr())) { if (NS_WARN_IF(insertParagraphInListItemResult.unwrapErr() == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED); } NS_WARNING( "AutoInsertParagraphHandler::HandleInListItemElement() failed, but " "ignored"); return EditActionResult::HandledResult(); } InsertParagraphResult unwrappedInsertParagraphInListItemResult = insertParagraphInListItemResult.unwrap(); MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.Handled()); MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.GetNewNode()); const RefPtr listItemOrParagraphElement = unwrappedInsertParagraphInListItemResult.UnwrapNewNode(); const EditorDOMPoint pointToPutCaret = unwrappedInsertParagraphInListItemResult.UnwrapCaretPoint(); nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( pointToPutCaret, listItemOrParagraphElement, {SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) { NS_WARNING( "AutoInsertParagraphHandler::" "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() failed"); return Err(rv); } NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "CollapseSelection() failed, but ignored"); return EditActionResult::HandledResult(); } if (HTMLEditUtils::IsHeadingElement(*editableBlockElement)) { Result insertParagraphInHeadingElementResult = HandleInHeadingElement(*editableBlockElement, pointToInsert); if (MOZ_UNLIKELY(insertParagraphInHeadingElementResult.isErr())) { NS_WARNING( "AutoInsertParagraphHandler::HandleInHeadingElement() failed, but " "ignored"); return EditActionResult::HandledResult(); } InsertParagraphResult unwrappedInsertParagraphInHeadingElementResult = insertParagraphInHeadingElementResult.unwrap(); if (unwrappedInsertParagraphInHeadingElementResult.Handled()) { MOZ_ASSERT(unwrappedInsertParagraphInHeadingElementResult.GetNewNode()); blockElementToPutCaret = unwrappedInsertParagraphInHeadingElementResult.UnwrapNewNode(); } const EditorDOMPoint pointToPutCaret = unwrappedInsertParagraphInHeadingElementResult.UnwrapCaretPoint(); nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( pointToPutCaret, blockElementToPutCaret, {SuggestCaret::OnlyIfHasSuggestion, SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) { NS_WARNING( "AutoInsertParagraphHandler::" "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() failed"); return Err(rv); } NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "CollapseSelection() failed, but ignored"); return EditActionResult::HandledResult(); } // XXX Ideally, we should take same behavior with both

container and //

container. However, we are still using
as default // paragraph separator (non-standard) and we've split only

container // long time. Therefore, some web apps may depend on this behavior like // Gmail. So, let's use traditional odd behavior only when the default // paragraph separator is
. Otherwise, take consistent behavior // between

container and

container. if ((mDefaultParagraphSeparator == ParagraphSeparator::br && editableBlockElement->IsHTMLElement(nsGkAtoms::p)) || (mDefaultParagraphSeparator != ParagraphSeparator::br && editableBlockElement->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div))) { const EditorDOMPoint pointToSplit = GetBetterPointToSplitParagraph( *editableBlockElement, insertedPaddingBRElement ? EditorDOMPoint(insertedPaddingBRElement) : pointToInsert, mEditingHost); if (ShouldCreateNewParagraph(*editableBlockElement, pointToSplit)) { MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); // Paragraphs: special rules to look for
s Result splitNodeResult = SplitParagraphWithTransaction(*editableBlockElement, pointToSplit); if (MOZ_UNLIKELY(splitNodeResult.isErr())) { NS_WARNING("HTMLEditor::SplitParagraphWithTransaction() failed"); return splitNodeResult.propagateErr(); } if (splitNodeResult.inspect().Handled()) { SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); const RefPtr rightParagraphElement = unwrappedSplitNodeResult.DidSplit() ? unwrappedSplitNodeResult.GetNextContentAs() : blockElementToPutCaret.get(); const EditorDOMPoint pointToPutCaret = unwrappedSplitNodeResult.UnwrapCaretPoint(); nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( pointToPutCaret, rightParagraphElement, {SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) { NS_WARNING( "AutoInsertParagraphHandler::" "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() " "failed"); return Err(rv); } NS_WARNING_ASSERTION( rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "AutoInsertParagraphHandler::" "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() " "failed, but ignored"); return EditActionResult::HandledResult(); } MOZ_ASSERT(!splitNodeResult.inspect().HasCaretPointSuggestion()); } // Fall through, if we didn't handle it above. } // If nobody handles this edit action, let's insert new
at the selection. Result insertBRElementResultOrError = HandleInsertBRElement(pointToInsert, blockElementToPutCaret); NS_WARNING_ASSERTION( insertBRElementResultOrError.isOk(), "AutoInsertParagraphHandler::HandleInsertBRElement() failed"); return insertBRElementResultOrError; } Result HTMLEditor::AutoInsertParagraphHandler::HandleInsertBRElement( const EditorDOMPoint& aPointToInsert, const Element* aBlockElementWhichShouldHaveCaret /* = nullptr */) { Result insertBRElementResult = InsertBRElement(aPointToInsert); if (MOZ_UNLIKELY(insertBRElementResult.isErr())) { NS_WARNING("AutoInsertParagraphHandler::InsertBRElement() failed"); return insertBRElementResult.propagateErr(); } const EditorDOMPoint pointToPutCaret = insertBRElementResult.unwrap().UnwrapCaretPoint(); if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) { NS_WARNING( "AutoInsertParagraphHandler::InsertBRElement() didn't suggest a " "point to put caret"); return Err(NS_ERROR_FAILURE); } nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( pointToPutCaret, aBlockElementWhichShouldHaveCaret, {}); if (NS_FAILED(rv)) { NS_WARNING( "AutoInsertParagraphHandler::" "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() failed"); return Err(rv); } return EditActionResult::HandledResult(); } Result HTMLEditor::AutoInsertParagraphHandler::HandleInsertLinefeed( const EditorDOMPoint& aPointToInsert) { Result insertLineFeedResult = AutoInsertLineBreakHandler::InsertLinefeed(mHTMLEditor, aPointToInsert, mEditingHost); if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) { NS_WARNING("AutoInsertLineBreakHandler::InsertLinefeed() failed"); return insertLineFeedResult.propagateErr(); } nsresult rv = mHTMLEditor.CollapseSelectionTo(insertLineFeedResult.inspect()); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::CollapseSelectionTo() failed"); return Err(rv); } return EditActionResult::HandledResult(); } bool HTMLEditor::AutoInsertParagraphHandler::ShouldInsertLineBreakInstead( const Element* aEditableBlockElement, const EditorDOMPoint& aCandidatePointToSplit) { // If there is no block parent in the editing host, i.e., the editing // host itself is also a non-block element, we should insert a line // break. if (!aEditableBlockElement) { // XXX Chromium checks if the CSS box of the editing host is a block. return true; } // If the editable block element is not splittable, e.g., it's an // editing host, and the default paragraph separator is
or the // element cannot contain a

element, we should insert a
// element. if (!HTMLEditUtils::IsSplittableNode(*aEditableBlockElement)) { return mDefaultParagraphSeparator == ParagraphSeparator::br || !HTMLEditUtils::CanElementContainParagraph(*aEditableBlockElement) || (aCandidatePointToSplit.IsInContentNode() && mHTMLEditor .GetPreferredLineBreakType( *aCandidatePointToSplit.ContainerAs(), mEditingHost) .valueOr(LineBreakType::BRElement) == LineBreakType::Linefeed && HTMLEditUtils::IsDisplayOutsideInline(mEditingHost)); } // If the nearest block parent is a single-line container declared in // the execCommand spec and not the editing host, we should separate the // block even if the default paragraph separator is
element. if (HTMLEditUtils::IsSingleLineContainer(*aEditableBlockElement)) { return false; } // Otherwise, unless there is no block ancestor which can contain

// element, we shouldn't insert a line break here. for (const Element* editableBlockAncestor = aEditableBlockElement; editableBlockAncestor; editableBlockAncestor = HTMLEditUtils::GetAncestorElement( *editableBlockAncestor, HTMLEditUtils::ClosestEditableBlockElementOrButtonElement, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { if (HTMLEditUtils::CanElementContainParagraph(*editableBlockAncestor)) { return false; } } return true; } // static nsresult HTMLEditor::AutoInsertParagraphHandler:: CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( const EditorDOMPoint& aCandidatePointToPutCaret, const Element* aBlockElementShouldHaveCaret, const SuggestCaretOptions& aOptions) { if (!aCandidatePointToPutCaret.IsSet()) { if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) { return NS_OK; } return aOptions.contains(SuggestCaret::AndIgnoreTrivialError) ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR : NS_ERROR_FAILURE; } EditorDOMPoint pointToPutCaret(aCandidatePointToPutCaret); if (aBlockElementShouldHaveCaret) { Result pointToPutCaretOrError = HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( *aBlockElementShouldHaveCaret, aCandidatePointToPutCaret); if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { NS_WARNING( "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() " "failed, but ignored"); } else if (pointToPutCaretOrError.inspect().IsSet()) { pointToPutCaret = pointToPutCaretOrError.unwrap(); } } nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); if (NS_FAILED(rv) && MOZ_LIKELY(rv != NS_ERROR_EDITOR_DESTROYED) && aOptions.contains(SuggestCaret::AndIgnoreTrivialError)) { rv = NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR; } return rv; } Result HTMLEditor::AutoInsertParagraphHandler::InsertBRElement( const EditorDOMPoint& aPointToBreak) { MOZ_ASSERT(aPointToBreak.IsInContentNode()); const bool editingHostIsEmpty = HTMLEditUtils::IsEmptyNode( mEditingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible}); const WSRunScanner wsRunScanner({WSRunScanner::Option::OnlyEditableNodes}, aPointToBreak); const WSScanResult backwardScanResult = wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPointToBreak); if (MOZ_UNLIKELY(backwardScanResult.Failed())) { NS_WARNING( "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed"); return Err(NS_ERROR_FAILURE); } const bool brElementIsAfterBlock = backwardScanResult.ReachedBlockBoundary() || // FIXME: This is wrong considering because the inline editing host may // be surrounded by visible inline content. However, WSRunScanner is // not aware of block boundary around it and stopping this change causes // starting to fail some WPT. Therefore, we need to keep doing this for // now. backwardScanResult.ReachedInlineEditingHostBoundary(); const WSScanResult forwardScanResult = wsRunScanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( aPointToBreak); if (MOZ_UNLIKELY(forwardScanResult.Failed())) { NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed"); return Err(NS_ERROR_FAILURE); } const bool brElementIsBeforeBlock = forwardScanResult.ReachedBlockBoundary() || // FIXME: See above comment forwardScanResult.ReachedInlineEditingHostBoundary(); // First, insert a
element. RefPtr brElement; if (mHTMLEditor.IsPlaintextMailComposer()) { Result insertBRElementResultOrError = mHTMLEditor.InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, aPointToBreak); if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { NS_WARNING( "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " "LineBreakType::BRElement) failed"); return insertBRElementResultOrError.propagateErr(); } CreateLineBreakResult insertBRElementResult = insertBRElementResultOrError.unwrap(); // We'll return with suggesting new caret position and nobody refers // selection after here. So we don't need to update selection here. insertBRElementResult.IgnoreCaretPointSuggestion(); brElement = &insertBRElementResult->BRElementRef(); } else { EditorDOMPoint pointToBreak(aPointToBreak); // If the container of the break is a link, we need to split it and // insert new
between the split links. RefPtr linkNode = HTMLEditor::GetLinkElement(pointToBreak.GetContainer()); if (linkNode) { // FIXME: Normalize surrounding white-spaces before splitting the // insertion point here. Result splitLinkNodeResult = mHTMLEditor.SplitNodeDeepWithTransaction( *linkNode, pointToBreak, SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(splitLinkNodeResult.isErr())) { NS_WARNING( "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" "eDoNotCreateEmptyContainer) failed"); return splitLinkNodeResult.propagateErr(); } // TODO: Some methods called by // WhiteSpaceVisibilityKeeper::InsertLineBreak() use // ComputeEditingHost() which depends on selection. Therefore, // we cannot skip updating selection here. nsresult rv = splitLinkNodeResult.inspect().SuggestCaretPointTo( mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); if (NS_FAILED(rv)) { NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); return Err(rv); } pointToBreak = splitLinkNodeResult.inspect().AtSplitPoint(); } Result insertBRElementResultOrError = WhiteSpaceVisibilityKeeper::InsertLineBreak(LineBreakType::BRElement, mHTMLEditor, pointToBreak); if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { NS_WARNING( "WhiteSpaceVisibilityKeeper::InsertLineBreak(LineBreakType::" "BRElement) failed"); return insertBRElementResultOrError.propagateErr(); } CreateLineBreakResult insertBRElementResult = insertBRElementResultOrError.unwrap(); // We'll return with suggesting new caret position and nobody refers // selection after here. So we don't need to update selection here. insertBRElementResult.IgnoreCaretPointSuggestion(); brElement = &insertBRElementResult->BRElementRef(); } if (MOZ_UNLIKELY(!brElement->GetParentNode())) { NS_WARNING("Inserted
element was removed by the web app"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } auto afterBRElement = EditorDOMPoint::After(brElement); const auto InsertAdditionalInvisibleLineBreak = [this, &afterBRElement]() MOZ_CAN_RUN_SCRIPT -> Result { // Empty last line is invisible if it's immediately before either parent or // another block's boundary so that we need to put invisible
element // here for making it visible. Result insertPaddingBRElementResultOrError = WhiteSpaceVisibilityKeeper::InsertLineBreak( LineBreakType::BRElement, mHTMLEditor, afterBRElement); NS_WARNING_ASSERTION(insertPaddingBRElementResultOrError.isOk(), "WhiteSpaceVisibilityKeeper::InsertLineBreak(" "LineBreakType::BRElement) failed"); // afterBRElement points after the first
with referring an old child. // Therefore, we need to update it with new child which is the new invisible //
. afterBRElement = insertPaddingBRElementResultOrError.inspect() .AtLineBreak(); return insertPaddingBRElementResultOrError; }; if (brElementIsAfterBlock && brElementIsBeforeBlock) { // We just placed a
between block boundaries. This is the one case // where we want the selection to be before the br we just placed, as the // br will be on a new line, rather than at end of prior line. // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before // modifying the DOM tree. So, now, the
element may not be // between blocks. EditorDOMPoint pointToPutCaret; if (editingHostIsEmpty) { Result insertPaddingBRElementResultOrError = InsertAdditionalInvisibleLineBreak(); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { return insertPaddingBRElementResultOrError.propagateErr(); } insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); pointToPutCaret = std::move(afterBRElement); } else { pointToPutCaret = EditorDOMPoint(brElement, InterlinePosition::StartOfNextLine); } return CreateElementResult(std::move(brElement), std::move(pointToPutCaret)); } const WSScanResult forwardScanFromAfterBRElementResult = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {WSRunScanner::Option::OnlyEditableNodes}, afterBRElement); if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) { NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed"); return Err(NS_ERROR_FAILURE); } if (forwardScanFromAfterBRElementResult.ReachedBRElement()) { // The next thing after the break we inserted is another break. Move the // second break to be the first break's sibling. This will prevent them // from being in different inline nodes, which would break // SetInterlinePosition(). It will also assure that if the user clicks // away and then clicks back on their new blank line, they will still get // the style from the line above. if (brElement->GetNextSibling() != forwardScanFromAfterBRElementResult.BRElementPtr()) { MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr()); Result moveBRElementResult = mHTMLEditor.MoveNodeWithTransaction( MOZ_KnownLive( *forwardScanFromAfterBRElementResult.BRElementPtr()), afterBRElement); if (MOZ_UNLIKELY(moveBRElementResult.isErr())) { NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); return moveBRElementResult.propagateErr(); } nsresult rv = moveBRElementResult.inspect().SuggestCaretPointTo( mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, SuggestCaret::OnlyIfTransactionsAllowedToDoIt, SuggestCaret::AndIgnoreTrivialError}); if (NS_FAILED(rv)) { NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed"); return Err(rv); } NS_WARNING_ASSERTION( rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, "MoveNodeResult::SuggestCaretPointTo() failed, but ignored"); // afterBRElement points after the first
with referring an old child. // Therefore, we need to update it with new child which is the new // invisible
. afterBRElement.Set(forwardScanFromAfterBRElementResult.BRElementPtr()); } } else if ((forwardScanFromAfterBRElementResult.ReachedBlockBoundary() || // FIXME: This is wrong considering because the inline editing // host may be surrounded by visible inline content. However, // WSRunScanner is not aware of block boundary around it and // stopping this change causes starting to fail some WPT. // Therefore, we need to keep doing this for now. forwardScanFromAfterBRElementResult .ReachedInlineEditingHostBoundary()) && !brElementIsAfterBlock) { Result insertPaddingBRElementResultOrError = InsertAdditionalInvisibleLineBreak(); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { return insertPaddingBRElementResultOrError.propagateErr(); } insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); } // We want the caret to stick to whatever is past the break. This is because // the break is on the same line we were on, but the next content will be on // the following line. // An exception to this is if the break has a next sibling that is a block // node. Then we stick to the left to avoid an uber caret. nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling(); afterBRElement.SetInterlinePosition( nextSiblingOfBRElement && HTMLEditUtils::IsBlockElement( *nextSiblingOfBRElement, BlockInlineCheck::UseComputedDisplayStyle) ? InterlinePosition::EndOfLine : InterlinePosition::StartOfNextLine); return CreateElementResult(std::move(brElement), afterBRElement); } Result HTMLEditor::AutoInsertParagraphHandler::HandleInMailCiteElement( Element& aMailCiteElement, const EditorDOMPoint& aPointToSplit) { MOZ_ASSERT(aPointToSplit.IsSet()); NS_ASSERTION(!HTMLEditUtils::IsEmptyNode( aMailCiteElement, {EmptyCheckOption::TreatNonEditableContentAsInvisible}), "The mail-cite element will be deleted, does it expected result " "for you?"); auto splitCiteElementResult = SplitMailCiteElement(aPointToSplit, aMailCiteElement); if (MOZ_UNLIKELY(splitCiteElementResult.isErr())) { NS_WARNING("Failed to split a mail-cite element"); return splitCiteElementResult.propagateErr(); } SplitNodeResult unwrappedSplitCiteElementResult = splitCiteElementResult.unwrap(); // When adding caret suggestion to SplitNodeResult, here didn't change // selection so that just ignore it. unwrappedSplitCiteElementResult.IgnoreCaretPointSuggestion(); // Add an invisible
to the end of left cite node if it was a of // style="display: block". This is important, since when serializing the cite // to plain text, the span which caused the visual break is discarded. So the // added
will guarantee that the serializer will insert a break where the // user saw one. // FYI: unwrappedSplitCiteElementResult grabs the previous node and the next // node with nsCOMPtr or EditorDOMPoint. So, it's safe to access // leftCiteElement and rightCiteElement even after changing the DOM tree // and/or selection even though it's raw pointer. auto* const leftCiteElement = unwrappedSplitCiteElementResult.GetPreviousContentAs(); auto* const rightCiteElement = unwrappedSplitCiteElementResult.GetNextContentAs(); if (leftCiteElement && leftCiteElement->IsHTMLElement(nsGkAtoms::span) && // XXX Oh, this depends on layout information of new element, and it's // created by the hacky flush in DoSplitNode(). So we need to // redesign around this for bug 1710784. leftCiteElement->GetPrimaryFrame() && leftCiteElement->GetPrimaryFrame()->IsBlockFrameOrSubclass()) { nsIContent* lastChild = leftCiteElement->GetLastChild(); if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) { Result insertPaddingBRElementResultOrError = mHTMLEditor.InsertLineBreak( WithTransaction::Yes, LineBreakType::BRElement, EditorDOMPoint::AtEndOf(*leftCiteElement)); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { NS_WARNING( "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " "LineBreakType::BRElement) failed"); return insertPaddingBRElementResultOrError.propagateErr(); } CreateLineBreakResult insertPaddingBRElementResult = insertPaddingBRElementResultOrError.unwrap(); MOZ_ASSERT(insertPaddingBRElementResult.Handled()); // We don't need to update selection here because we'll do another // InsertLineBreak call soon. insertPaddingBRElementResult.IgnoreCaretPointSuggestion(); } } // In most cases,
should be inserted after current cite. However, if // left cite hasn't been created because the split point was start of the // cite node,
should be inserted before the current cite. Result insertBRElementResultOrError = mHTMLEditor.InsertLineBreak( WithTransaction::Yes, LineBreakType::BRElement, unwrappedSplitCiteElementResult.AtSplitPoint()); if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { NS_WARNING( "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " "LineBreakType::BRElement) failed"); return Err(insertBRElementResultOrError.unwrapErr()); } CreateLineBreakResult insertBRElementResult = insertBRElementResultOrError.unwrap(); MOZ_ASSERT(insertBRElementResult.Handled()); // We'll return with suggesting caret position. Therefore, we don't need // to update selection here. insertBRElementResult.IgnoreCaretPointSuggestion(); // if aMailCiteElement wasn't a block, we might also want another break before // it. We need to examine the content both before the br we just added and // also just after it. If we don't have another br or block boundary // adjacent, then we will need a 2nd br added to achieve blank line that user // expects. { nsresult rvOfInsertPaddingBRElement = MaybeInsertPaddingBRElementToInlineMailCiteElement( insertBRElementResult.AtLineBreak(), aMailCiteElement); if (NS_FAILED(rvOfInsertPaddingBRElement)) { NS_WARNING( "Failed to insert additional
element before the inline right " "mail-cite element"); return Err(rvOfInsertPaddingBRElement); } } if (leftCiteElement && HTMLEditUtils::IsEmptyNode( *leftCiteElement, {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { // MOZ_KnownLive(leftCiteElement) because it's grabbed by // unwrappedSplitCiteElementResult. nsresult rv = mHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*leftCiteElement)); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); return Err(rv); } } if (rightCiteElement && HTMLEditUtils::IsEmptyNode( *rightCiteElement, {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { // MOZ_KnownLive(rightCiteElement) because it's grabbed by // unwrappedSplitCiteElementResult. nsresult rv = mHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*rightCiteElement)); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); return Err(rv); } } if (NS_WARN_IF(!insertBRElementResult.LineBreakIsInComposedDoc())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } auto pointToPutCaret = insertBRElementResult.AtLineBreak(); pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine); return CaretPoint(std::move(pointToPutCaret)); } Result HTMLEditor::AutoInsertParagraphHandler::SplitMailCiteElement( const EditorDOMPoint& aPointToSplit, Element& aMailCiteElement) { EditorDOMPoint pointToSplit(aPointToSplit); // If our selection is just before a break, nudge it to be just after // it. This does two things for us. It saves us the trouble of having // to add a break here ourselves to preserve the "blockness" of the // inline span mailquote (in the inline case), and : it means the break // won't end up making an empty line that happens to be inside a // mailquote (in either inline or block case). The latter can confuse a // user if they click there and start typing, because being in the // mailquote may affect wrapping behavior, or font color, etc. const WSScanResult forwardScanFromPointToSplitResult = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {WSRunScanner::Option::OnlyEditableNodes, WSRunScanner::Option::ReferHTMLDefaultStyle}, pointToSplit); if (forwardScanFromPointToSplitResult.Failed()) { return Err(NS_ERROR_FAILURE); } // If selection start point is before a break and it's inside the // mailquote, let's split it after the visible node. if (forwardScanFromPointToSplitResult.ReachedBRElement() && forwardScanFromPointToSplitResult.BRElementPtr() != &aMailCiteElement && aMailCiteElement.Contains( forwardScanFromPointToSplitResult.BRElementPtr())) { pointToSplit = forwardScanFromPointToSplitResult .PointAfterReachedContent(); } if (NS_WARN_IF(!pointToSplit.IsInContentNode())) { return Err(NS_ERROR_FAILURE); } Result pointToSplitOrError = WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( mHTMLEditor, pointToSplit, {WhiteSpaceVisibilityKeeper::NormalizeOption:: StopIfPrecedingWhiteSpacesEndsWithNBP, WhiteSpaceVisibilityKeeper::NormalizeOption:: StopIfFollowingWhiteSpacesStartsWithNBSP}); if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) { NS_WARNING( "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() " "failed"); return pointToSplitOrError.propagateErr(); } pointToSplit = pointToSplitOrError.unwrap(); if (NS_WARN_IF(!pointToSplit.IsInContentNode())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } Result splitResult = mHTMLEditor.SplitNodeDeepWithTransaction( aMailCiteElement, pointToSplit, SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(splitResult.isErr())) { NS_WARNING( "HTMLEditor::SplitNodeDeepWithTransaction(aMailCiteElement, " "SplitAtEdges::eDoNotCreateEmptyContainer) failed"); return splitResult; } // FIXME: We should make the caller handle `Selection`. nsresult rv = splitResult.inspect().SuggestCaretPointTo( mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); if (NS_FAILED(rv)) { NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); return Err(rv); } return splitResult; } nsresult HTMLEditor::AutoInsertParagraphHandler:: MaybeInsertPaddingBRElementToInlineMailCiteElement( const EditorDOMPoint& aPointToInsertBRElement, Element& aMailCiteElement) { if (!HTMLEditUtils::IsInlineContent(aMailCiteElement, BlockInlineCheck::UseHTMLDefaultStyle)) { return NS_SUCCESS_DOM_NO_OPERATION; } // XXX Cannot we replace this complicated check with just a call of // HTMLEditUtils::IsVisibleBRElement with // resultOfInsertingBRElement.inspect()? const WSScanResult backwardScanFromPointToCreateNewBRElementResult = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( {WSRunScanner::Option::OnlyEditableNodes, WSRunScanner::Option::ReferHTMLDefaultStyle}, aPointToInsertBRElement); if (MOZ_UNLIKELY(backwardScanFromPointToCreateNewBRElementResult.Failed())) { NS_WARNING( "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() " "failed"); return NS_ERROR_FAILURE; } if (!backwardScanFromPointToCreateNewBRElementResult .InVisibleOrCollapsibleCharacters() && !backwardScanFromPointToCreateNewBRElementResult .ReachedSpecialContent() && !backwardScanFromPointToCreateNewBRElementResult .ReachedEmptyInlineContainerElement()) { return NS_SUCCESS_DOM_NO_OPERATION; } const WSScanResult forwardScanFromPointAfterNewBRElementResult = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {WSRunScanner::Option::OnlyEditableNodes, WSRunScanner::Option::ReferHTMLDefaultStyle}, EditorRawDOMPoint::After(aPointToInsertBRElement)); if (MOZ_UNLIKELY(forwardScanFromPointAfterNewBRElementResult.Failed())) { NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed"); return NS_ERROR_FAILURE; } if (!forwardScanFromPointAfterNewBRElementResult .InVisibleOrCollapsibleCharacters() && !forwardScanFromPointAfterNewBRElementResult.ReachedSpecialContent() && !forwardScanFromPointAfterNewBRElementResult .ReachedEmptyInlineContainerElement() && // In case we're at the very end. !forwardScanFromPointAfterNewBRElementResult .ReachedCurrentBlockBoundary()) { return NS_SUCCESS_DOM_NO_OPERATION; } Result insertAnotherBRElementResultOrError = mHTMLEditor.InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, aPointToInsertBRElement); if (MOZ_UNLIKELY(insertAnotherBRElementResultOrError.isErr())) { NS_WARNING( "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " "LineBreakType::BRElement) failed"); return insertAnotherBRElementResultOrError.unwrapErr(); } CreateLineBreakResult insertAnotherBRElementResult = insertAnotherBRElementResultOrError.unwrap(); MOZ_ASSERT(insertAnotherBRElementResult.Handled()); insertAnotherBRElementResult.IgnoreCaretPointSuggestion(); return NS_OK; } Result HTMLEditor::AutoInsertParagraphHandler::HandleInHeadingElement( Element& aHeadingElement, const EditorDOMPoint& aPointToSplit) { // Don't preserve empty link at the end of the left heading element nor the // start of the right one. const EditorDOMPoint pointToSplit = GetBetterPointToSplitParagraph( aHeadingElement, aPointToSplit, mEditingHost); MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); // If the split point is end of the heading element, we should not touch the // heading element and insert a default paragraph next to the heading element. if (SplitPointIsEndOfSplittingBlock(aHeadingElement, pointToSplit, IgnoreBlockBoundaries::Yes)) { Result handleAtEndOfHeadingElementResultOrError = HandleAtEndOfHeadingElement(aHeadingElement); NS_WARNING_ASSERTION( handleAtEndOfHeadingElementResultOrError.isOk(), "AutoInsertParagraphHandler::HandleAtEndOfHeadingElement() failed"); return handleAtEndOfHeadingElementResultOrError; } Result splitHeadingResultOrError = SplitParagraphWithTransaction(aHeadingElement, pointToSplit); if (MOZ_UNLIKELY(splitHeadingResultOrError.isErr())) { NS_WARNING( "AutoInsertParagraphHandler::SplitParagraphWithTransaction() failed"); return splitHeadingResultOrError.propagateErr(); } SplitNodeResult splitHeadingResult = splitHeadingResultOrError.unwrap(); splitHeadingResult.IgnoreCaretPointSuggestion(); if (MOZ_UNLIKELY(!splitHeadingResult.DidSplit())) { NS_WARNING( "AutoInsertParagraphHandler::SplitParagraphWithTransaction() didn't " "split aHeadingElement"); return Err(NS_ERROR_FAILURE); } // Put caret at start of the right head element if it's not empty. auto* const rightHeadingElement = splitHeadingResult.GetNextContentAs(); MOZ_ASSERT(rightHeadingElement, "SplitNodeResult::GetNextContent() should return something if " "DidSplit() returns true"); return InsertParagraphResult(*rightHeadingElement, splitHeadingResult.UnwrapCaretPoint()); } Result HTMLEditor::AutoInsertParagraphHandler::HandleAtEndOfHeadingElement( Element& aHeadingElement) { // XXX This makes HTMLEditor instance stateful. So, we should move this out // from AutoInsertParagraphHandler with adding a method which HTMLEditor can // consider to do this. mHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear(); mHTMLEditor.mPendingStylesToApplyToNewContent->ClearAllStyles(); // Create a paragraph if the right heading element is not followed by an // editable
element. nsStaticAtom& newParagraphTagName = &mDefaultParagraphSeparatorTagName == nsGkAtoms::br ? *nsGkAtoms::p : mDefaultParagraphSeparatorTagName; // We want a wrapper element even if we separate with a
. // FIXME: Chrome does not preserve the style coming from the heading element. // However, Chrome preserves the inline ancestors at the split point. // Perhaps, we should follow them. // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown. Result createNewParagraphElementResult = mHTMLEditor.CreateAndInsertElement(WithTransaction::Yes, MOZ_KnownLive(newParagraphTagName), EditorDOMPoint::After(aHeadingElement), HTMLEditor::InsertNewBRElement); if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) { NS_WARNING( "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); return createNewParagraphElementResult.propagateErr(); } CreateElementResult unwrappedCreateNewParagraphElementResult = createNewParagraphElementResult.unwrap(); // Put caret at the
element in the following paragraph. unwrappedCreateNewParagraphElementResult.IgnoreCaretPointSuggestion(); MOZ_ASSERT(unwrappedCreateNewParagraphElementResult.GetNewNode()); EditorDOMPoint pointToPutCaret( unwrappedCreateNewParagraphElementResult.GetNewNode(), 0u); return InsertParagraphResult( unwrappedCreateNewParagraphElementResult.UnwrapNewNode(), std::move(pointToPutCaret)); } // static bool HTMLEditor::AutoInsertParagraphHandler:: IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( const dom::HTMLBRElement* aBRElement) { return !aBRElement || HTMLEditUtils::IsBRElementFollowedByBlockBoundary(*aBRElement) || // XXX It's odd to check the
element's flag. It may be wrong if // the web app moved the
or changed the preceding conent. EditorUtils::IsPaddingBRElementForEmptyLastLine(*aBRElement); } bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph( Element& aParentDivOrP, const EditorDOMPoint& aPointToSplit) const { MOZ_ASSERT(aPointToSplit.IsInContentNodeAndValidInComposedDoc()); if (MOZ_LIKELY(mHTMLEditor.GetReturnInParagraphCreatesNewParagraph())) { // We should always create a new paragraph by default. return true; } if (aPointToSplit.GetContainer() == &aParentDivOrP) { // We are trying to split only the current paragraph, let's do it. return true; } if (aPointToSplit.IsInTextNode()) { if (aPointToSplit.IsStartOfContainer()) { // If we're splitting the paragraph at start of a `Text` and it does // not follow a
or follows an invisible
, we should not create a // new paragraph. // XXX It seems that here assumes that the paragraph has only this `Text`. const auto* const precedingBRElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousSibling( *aPointToSplit.ContainerAs(), {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle)); return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( precedingBRElement); } if (aPointToSplit.IsEndOfContainer()) { // If we're splitting the paragraph at end of a `Text` and it's not // followed by a
or is followed by an invisible
, we should not // create a new paragraph. // XXX It seems that here assumes that the paragraph has only this `Text`. const auto* const followingBRElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextSibling( *aPointToSplit.ContainerAs(), {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle)); return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( followingBRElement); } // If we're splitting the paragraph at middle of a `Text`, we should create // a new paragraph. return true; } // If we're splitting in a child element of the paragraph and it does not // follow a
or follows an invisible
, maybe we should not create a // new paragraph. // XXX Why? We probably need to do this if we're splitting in an inline // element which and whose parents provide some styles, we should put // the
element for making a placeholder in the left paragraph for // moving to the caret, but I think that this could be handled in fewer // cases than this. const auto* const precedingBRElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousLeafContent( aPointToSplit, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &mEditingHost)); if (!IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( precedingBRElement)) { return true; } // If we're splitting in a child element of the paragraph and it's not // followed by a
or followed by an invisible
, we should not create a // new paragraph. const auto* followingBRElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextLeafContent( aPointToSplit, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::Auto, &mEditingHost)); return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( followingBRElement); } // static EditorDOMPoint HTMLEditor::AutoInsertParagraphHandler::GetBetterPointToSplitParagraph( const Element& aBlockElementToSplit, const EditorDOMPoint& aCandidatePointToSplit, const Element& aEditingHost) { EditorDOMPoint pointToSplit = [&]() MOZ_NEVER_INLINE_DEBUG { // We shouldn't create new anchor element which has non-empty href unless // splitting middle of it because we assume that users don't want to create // *same* anchor element across two or more paragraphs in most cases. // So, adjust selection start if it's edge of anchor element(s). { const WSScanResult prevVisibleThing = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( {}, aCandidatePointToSplit, &aBlockElementToSplit); if (prevVisibleThing.GetContent() && // Only if the previous thing is not in the same container. prevVisibleThing.GetContent() != aCandidatePointToSplit.GetContainer() && // Only if the previous thing is a preceding node of closest inclusive // ancestor element at the split point. !prevVisibleThing.GetContent()->IsInclusiveDescendantOf( aCandidatePointToSplit.GetContainerOrContainerParentElement())) { EditorRawDOMPoint candidatePointToSplit = aCandidatePointToSplit.To(); const Element* const commonAncestor = Element::FromNode(nsContentUtils::GetClosestCommonInclusiveAncestor( candidatePointToSplit.GetContainerOrContainerParentElement(), prevVisibleThing.GetContent())); MOZ_ASSERT(commonAncestor); for (const Element* container = candidatePointToSplit.GetContainerOrContainerParentElement(); container && container != commonAncestor; container = container->GetParentElement()) { if (!HTMLEditUtils::IsHyperlinkElement(*container)) { continue; } // Found link should be only in right node. So, we shouldn't split // it. candidatePointToSplit.Set(container); // Even if we found an anchor element, don't break because DOM API // allows to nest anchor elements. } return candidatePointToSplit.To(); } } const WSScanResult nextVisibleThing = HTMLEditUtils::ScanInclusiveNextThingWithIgnoringUnnecessaryLineBreak( aCandidatePointToSplit, PaddingForEmptyBlock::Unnecessary, aEditingHost, &aBlockElementToSplit); if (nextVisibleThing.GetContent() && !nextVisibleThing.ReachedOutsideEditingHost() && // Only if the next thing is not in the same container. nextVisibleThing.GetContent() != aCandidatePointToSplit.GetContainer() && // Only if the next thing is a preceding node of closest inclusive // ancestor element at the split point. !nextVisibleThing.GetContent()->IsInclusiveDescendantOf( aCandidatePointToSplit.GetContainerOrContainerParentElement())) { EditorRawDOMPoint candidatePointToSplit = aCandidatePointToSplit.To(); const Element* const commonAncestor = Element::FromNode(nsContentUtils::GetClosestCommonInclusiveAncestor( candidatePointToSplit.GetContainerOrContainerParentElement(), nextVisibleThing.GetContent())); MOZ_ASSERT(commonAncestor); for (const Element* container = candidatePointToSplit.GetContainerOrContainerParentElement(); container && container != commonAncestor; container = container->GetParentElement()) { if (!HTMLEditUtils::IsHyperlinkElement(*container)) { continue; } // Found link should be only in left node. So, we shouldn't split it. candidatePointToSplit.SetAfter(container); // Even if we found an anchor element, don't break because DOM API // allows to nest anchor elements. } return candidatePointToSplit.To(); } // Okay, split the ancestors as-is. return aCandidatePointToSplit; }(); // If the candidate split point is not in a splittable node, let's move the // point after the parent. for (const nsIContent* container = pointToSplit.ContainerAs(); container && container != &aBlockElementToSplit && !HTMLEditUtils::IsSplittableNode(*container); container = container->GetParent()) { pointToSplit = pointToSplit.ParentPoint(); } return pointToSplit; } Result HTMLEditor::AutoInsertParagraphHandler:: EnsureNoInvisibleLineBreakBeforePointToSplit( const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit) { const WSScanResult nextVisibleThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {}, aPointToSplit, &aBlockElementToSplit); if (!nextVisibleThing.ReachedBlockBoundary()) { return aPointToSplit; } const WSScanResult prevVisibleThing = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( {WSRunScanner::Option::StopAtVisibleEmptyInlineContainers}, aPointToSplit, &aBlockElementToSplit); if (!prevVisibleThing.ReachedLineBreak()) { return aPointToSplit; } EditorDOMPoint pointToSplit = aPointToSplit; EditorLineBreak precedingLineBreak = prevVisibleThing.CreateEditorLineBreak(); { // FIXME: Once bug 1951041 is fixed in the layout level, we don't need to // treat collapsible white-spaces before invisible
elements here. AutoTrackDOMPoint trackPointToSplit(mHTMLEditor.RangeUpdaterRef(), &pointToSplit); Maybe trackPrecedingLineBreak; if (precedingLineBreak.IsPreformattedLineBreak()) { trackPrecedingLineBreak.emplace(mHTMLEditor.RangeUpdaterRef(), &precedingLineBreak); } Result normalizePrecedingWhiteSpacesResultOrError = [&]() MOZ_CAN_RUN_SCRIPT -> Result { if (precedingLineBreak.IsHTMLBRElement() || precedingLineBreak.IsPreformattedLineBreakAtStartOfText()) { Result ret = WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( mHTMLEditor, precedingLineBreak.To(), {}); NS_WARNING_ASSERTION( ret.isOk(), "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed"); return ret; } Result ret = WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( mHTMLEditor, precedingLineBreak.To(), {}); NS_WARNING_ASSERTION( ret.isOk(), "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed"); return ret; }(); if (NS_WARN_IF(normalizePrecedingWhiteSpacesResultOrError.isErr())) { return normalizePrecedingWhiteSpacesResultOrError.propagateErr(); } } if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( &aBlockElementToSplit)) || NS_WARN_IF(!precedingLineBreak.IsDeletableFromComposedDoc())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } { AutoTrackDOMPoint trackPointToSplit(mHTMLEditor.RangeUpdaterRef(), &pointToSplit); Result deleteInvisibleLineBreakResult = mHTMLEditor.DeleteLineBreakWithTransaction( precedingLineBreak, nsIEditor::eNoStrip, aBlockElementToSplit); if (MOZ_UNLIKELY(deleteInvisibleLineBreakResult.isErr())) { NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed"); return deleteInvisibleLineBreakResult.propagateErr(); } } if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( &aBlockElementToSplit))) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } return pointToSplit; } Result HTMLEditor::AutoInsertParagraphHandler:: MaybeInsertFollowingBRElementToPreserveRightBlock( const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit) { MOZ_ASSERT(HTMLEditUtils::IsSplittableNode(aBlockElementToSplit)); MOZ_ASSERT(aPointToSplit.ContainerAs()->IsInclusiveDescendantOf( &aBlockElementToSplit)); const Element* const closestContainerElement = HTMLEditUtils::GetInclusiveAncestorElement( *aPointToSplit.ContainerAs(), HTMLEditUtils::ClosestContainerElementOrVoidAncestorLimiter, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aBlockElementToSplit); MOZ_ASSERT(closestContainerElement); MOZ_ASSERT(HTMLEditUtils::IsSplittableNode(*closestContainerElement)); // If we're at end of the paragraph and there are some inline container // elements, we want to preserve the inline containers to preserve their // styles. Maybe unnecessaryLineBreak; const EditorDOMPoint pointToInsertFollowingBRElement = [&]() MOZ_NEVER_INLINE_DEBUG { const WSScanResult nextVisibleThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {}, aPointToSplit, &aBlockElementToSplit); if (nextVisibleThing.ReachedLineBreak()) { EditorLineBreak lineBreak = nextVisibleThing.CreateEditorLineBreak(); // If there is a
in the closest ancestor container element, we // can use it. Note that we don't want to make an empty paragraph // which contains only a preformatted linefeed. if (lineBreak.IsHTMLBRElement() && lineBreak.BRElementRef().GetParentNode() == closestContainerElement && // The serializer requires a normal
in the empty right // paragraph so that we cannot reuse it if it's a padding
. // See bug 1385905. !lineBreak.BRElementRef().IsPaddingForEmptyLastLine() && !lineBreak.BRElementRef().IsPaddingForEmptyEditor()) { return EditorDOMPoint(); } // If the line break is not followed by current block boundary, we // don't need to insert a padding
element for the right paragraph // because it'll have some visible content. if (!lineBreak.IsFollowedByCurrentBlockBoundary()) { return EditorDOMPoint(); } unnecessaryLineBreak.emplace(std::move(lineBreak)); } // If it's not directly followed by current block boundary, we don't // need to insert a padding
element for the right paragraph because // it'll have some visible content. else if (!nextVisibleThing.ReachedCurrentBlockBoundary()) { return EditorDOMPoint(); } // We want to insert a padding
into the closest ancestor container // element to preserve the style provided by it. EditorDOMPoint candidatePoint = aPointToSplit; for (; candidatePoint.GetContainer() != closestContainerElement; candidatePoint = candidatePoint.AfterContainer()) { MOZ_ASSERT(candidatePoint.GetContainer() != &aBlockElementToSplit); } return candidatePoint; }(); if (unnecessaryLineBreak) { Result deleteLineBreakResultOrError = mHTMLEditor.DeleteLineBreakWithTransaction( *unnecessaryLineBreak, nsIEditor::eNoStrip, aBlockElementToSplit); if (MOZ_UNLIKELY(deleteLineBreakResultOrError.isErr())) { NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed"); return deleteLineBreakResultOrError.propagateErr(); } if (NS_WARN_IF(!aPointToSplit.IsSetAndValidInComposedDoc())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } if (pointToInsertFollowingBRElement.IsSet() && (NS_WARN_IF(!pointToInsertFollowingBRElement .IsInContentNodeAndValidInComposedDoc()) || NS_WARN_IF(!pointToInsertFollowingBRElement.GetContainer() ->IsInclusiveDescendantOf(&aBlockElementToSplit)))) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } } EditorDOMPoint pointToSplit(aPointToSplit); if (pointToInsertFollowingBRElement.IsSet()) { Maybe trackPointToSplit; if (pointToSplit.GetContainer() == pointToInsertFollowingBRElement.GetContainer()) { trackPointToSplit.emplace(mHTMLEditor.RangeUpdaterRef(), &pointToSplit); } Result insertPaddingBRElementResultOrError = mHTMLEditor.InsertBRElement( WithTransaction::Yes, // We don't want to expose the
for IME, but the plaintext // serializer requires this. See bug 1385905. BRElementType::Normal, pointToInsertFollowingBRElement); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { return insertPaddingBRElementResultOrError.propagateErr(); } insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); } if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( &aBlockElementToSplit))) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } if (mHTMLEditor.GetDefaultParagraphSeparator() != ParagraphSeparator::br) { return pointToSplit; } // If we're in the legacy mode, we don't want the right paragraph start with // an empty line. So, if the right paragraph now starts with 2
elements, // remove the second one. (The first one is in the closest container element, // so, we want to keep it.) const WSScanResult nextVisibleThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {}, pointToSplit, &aBlockElementToSplit); if (!nextVisibleThing.ReachedBRElement()) { return pointToSplit; } const WSScanResult nextVisibleThingAfterFirstBRElement = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {}, nextVisibleThing.PointAfterReachedContent(), &aBlockElementToSplit); if (!nextVisibleThingAfterFirstBRElement.ReachedBRElement()) { return pointToSplit; } nsresult rv = mHTMLEditor.DeleteNodeWithTransaction( MOZ_KnownLive(*nextVisibleThingAfterFirstBRElement.BRElementPtr())); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); return Err(rv); } if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( &aBlockElementToSplit))) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } return pointToSplit; } Result HTMLEditor::AutoInsertParagraphHandler::SplitParagraphWithTransaction( Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit) { // First, maybe the split point follows an invisible
. E.g., when // `

foo[]

`, // GetBetterSplitPointToAvoidToContinueLink() adjusted the split point as // `

foo
{}

`. Then, we shouldn't insert another //
to end of the left

to make the last line visible. Even though we // need to insert an invisible
element later, let's delete the invisible // line break first to make this method simpler. Result deleteInvisibleLineBreakResultOrError = EnsureNoInvisibleLineBreakBeforePointToSplit(aBlockElementToSplit, aPointToSplit); if (MOZ_UNLIKELY(deleteInvisibleLineBreakResultOrError.isErr())) { NS_WARNING( "AutoInsertParagraphHandler::SplitParagraphWithTransaction() failed"); return deleteInvisibleLineBreakResultOrError.propagateErr(); } EditorDOMPoint pointToSplit = deleteInvisibleLineBreakResultOrError.unwrap(); MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); MOZ_ASSERT(pointToSplit.GetContainer()->IsInclusiveDescendantOf( &aBlockElementToSplit)); // Then, we need to keep the visibility of the surrounding collapsible // white-spaces at the split point. Result preparationResult = WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement( mHTMLEditor, pointToSplit, aBlockElementToSplit); if (MOZ_UNLIKELY(preparationResult.isErr())) { NS_WARNING( "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed"); return preparationResult.propagateErr(); } pointToSplit = preparationResult.unwrap(); MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); MOZ_ASSERT(pointToSplit.GetContainer()->IsInclusiveDescendantOf( &aBlockElementToSplit)); // Next, if there are some inline elements which we will split and we're // splitting the deepest one at end of it, we need to put invisible
// before splitting to preserve the cloned inline elements in the new // paragraph. { Result insertPaddingBRElementResultOrError = MaybeInsertFollowingBRElementToPreserveRightBlock(aBlockElementToSplit, pointToSplit); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { NS_WARNING( "AutoInsertParagraphHandler::" "MaybeInsertFollowingBRElementToPreserveRightBlock() failed"); return insertPaddingBRElementResultOrError.propagateErr(); } pointToSplit = insertPaddingBRElementResultOrError.unwrap(); if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( &aBlockElementToSplit))) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } } // Then, split current paragraph. const RefPtr deepestContainerElementToSplit = HTMLEditUtils::GetInclusiveAncestorElement( *pointToSplit.ContainerAs(), HTMLEditUtils::ClosestContainerElementOrVoidAncestorLimiter, BlockInlineCheck::UseComputedDisplayOutsideStyle, &aBlockElementToSplit); if (NS_WARN_IF(!deepestContainerElementToSplit)) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } Result splitDivOrPResultOrError = mHTMLEditor.SplitNodeDeepWithTransaction( aBlockElementToSplit, pointToSplit, SplitAtEdges::eAllowToCreateEmptyContainer); if (MOZ_UNLIKELY(splitDivOrPResultOrError.isErr())) { NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); return splitDivOrPResultOrError; } SplitNodeResult splitDivOrPResult = splitDivOrPResultOrError.unwrap(); if (MOZ_UNLIKELY(!splitDivOrPResult.DidSplit())) { NS_WARNING( "HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes"); return splitDivOrPResult; } // We'll compute caret suggestion later. So the simple result is not needed. splitDivOrPResult.IgnoreCaretPointSuggestion(); auto* const leftDivOrParagraphElement = splitDivOrPResult.GetPreviousContentAs(); MOZ_ASSERT(leftDivOrParagraphElement, "SplitNodeResult::GetPreviousContent() should return something if " "DidSplit() returns true"); auto* const rightDivOrParagraphElement = splitDivOrPResult.GetNextContentAs(); MOZ_ASSERT(rightDivOrParagraphElement, "SplitNodeResult::GetNextContent() should return something if " "DidSplit() returns true"); // Remove ID attribute on the paragraph from the right node. // MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by // splitDivOrPResult. nsresult rv = mHTMLEditor.RemoveAttributeWithTransaction( MOZ_KnownLive(*rightDivOrParagraphElement), *nsGkAtoms::id); if (NS_FAILED(rv)) { NS_WARNING( "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed"); return Err(rv); } // Finally, we need to ensure that both paragraphs are visible even if they // are empty. Note that we need to use padding
element for the empty // last line as usual because it won't appear as a line break when serialized // by ContentEventHandler. Thus, if we were using normal
elements, // disappearing following line break of composition string would make IME // confused. if (NS_WARN_IF(!deepestContainerElementToSplit->IsInclusiveDescendantOf( leftDivOrParagraphElement))) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } const EditorDOMPoint pointToInsertBRElement = [&]() MOZ_NEVER_INLINE_DEBUG { // If we split the paragraph immediately after a block boundary or a line // break, we need to put a padding
to make an empty line. const WSScanResult prevVisibleThing = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( {}, EditorRawDOMPoint::AtEndOf(*deepestContainerElementToSplit), leftDivOrParagraphElement); if (prevVisibleThing.ReachedLineBoundary()) { return EditorDOMPoint::AtEndOf(*deepestContainerElementToSplit); } // If we split a descendant element and it's empty, we need to put a padding //
element into it to preserve the style of the element. if (deepestContainerElementToSplit == leftDivOrParagraphElement) { return EditorDOMPoint(); } const WSScanResult nextVisibleThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {}, EditorRawDOMPoint(deepestContainerElementToSplit, 0), leftDivOrParagraphElement); return nextVisibleThing.ReachedCurrentBlockBoundary() ? EditorDOMPoint::AtEndOf(*deepestContainerElementToSplit) : EditorDOMPoint(); }(); if (pointToInsertBRElement.IsSet()) { Result insertPaddingBRElementResultOrError = mHTMLEditor.InsertBRElement( WithTransaction::Yes, // XXX We don't want to expose the
for IME, but the plaintext // serializer requires this. See bug 1385905. BRElementType::Normal, pointToInsertBRElement); if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { return insertPaddingBRElementResultOrError.propagateErr(); } insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); } // The right paragraph should not be empty because // MaybeInsertFollowingBRElementToPreserveRightBlock() should've already put a // padding
before splitting the paragraph. if (NS_WARN_IF(HTMLEditUtils::IsEmptyNode( *rightDivOrParagraphElement, {EmptyCheckOption::TreatSingleBRElementAsVisible, EmptyCheckOption::TreatListItemAsVisible, EmptyCheckOption::TreatTableCellAsVisible}))) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } // Let's put caret at start of the first leaf container. nsIContent* child = HTMLEditUtils::GetFirstLeafContent( *rightDivOrParagraphElement, {LeafNodeOption::TreatChildBlockAsLeafNode}, BlockInlineCheck::UseComputedDisplayStyle); if (MOZ_UNLIKELY(!child)) { return SplitNodeResult(std::move(splitDivOrPResult), EditorDOMPoint(rightDivOrParagraphElement, 0u)); } return child->IsText() || HTMLEditUtils::IsContainerNode(*child) ? SplitNodeResult(std::move(splitDivOrPResult), EditorDOMPoint(child, 0u)) : SplitNodeResult(std::move(splitDivOrPResult), EditorDOMPoint(child)); } Result HTMLEditor::AutoInsertParagraphHandler::InsertBRElementIfEmptyBlockElement( Element& aMaybeBlockElement, InsertBRElementIntoEmptyBlock aInsertBRElementIntoEmptyBlock, BlockInlineCheck aBlockInlineCheck) { if (!HTMLEditUtils::IsBlockElement(aMaybeBlockElement, aBlockInlineCheck)) { return CreateLineBreakResult::NotHandled(); } if (!HTMLEditUtils::IsEmptyNode( aMaybeBlockElement, {EmptyCheckOption::TreatSingleBRElementAsVisible})) { return CreateLineBreakResult::NotHandled(); } // XXX: Probably, we should use // InsertPaddingBRElementForEmptyLastLineWithTransaction here, and // if there are some empty inline container, we should put the
// into the last one. Result insertBRElementResultOrError = mHTMLEditor.InsertLineBreak( WithTransaction::Yes, LineBreakType::BRElement, aInsertBRElementIntoEmptyBlock == InsertBRElementIntoEmptyBlock::Start ? EditorDOMPoint(&aMaybeBlockElement, 0u) : EditorDOMPoint::AtEndOf(aMaybeBlockElement)); NS_WARNING_ASSERTION(insertBRElementResultOrError.isOk(), "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " "LineBreakType::BRElement) failed"); return insertBRElementResultOrError; } // static Element* HTMLEditor::AutoInsertParagraphHandler:: GetDeepestFirstChildInlineContainerElement(Element& aBlockElement) { // XXX Should we ignore invisible children like empty Text, Comment, etc? Element* result = nullptr; for (Element* maybeDeepestInlineContainer = Element::FromNodeOrNull(aBlockElement.GetFirstChild()); maybeDeepestInlineContainer && HTMLEditUtils::IsInlineContent( *maybeDeepestInlineContainer, BlockInlineCheck::UseComputedDisplayStyle) && HTMLEditUtils::IsContainerNode(*maybeDeepestInlineContainer); // FIXME: There may be visible node before first element child, so, here // is obviously wrong. maybeDeepestInlineContainer = maybeDeepestInlineContainer->GetFirstElementChild()) { result = maybeDeepestInlineContainer; } return result; } Result HTMLEditor::AutoInsertParagraphHandler::HandleInListItemElement( Element& aListItemElement, const EditorDOMPoint& aPointToSplit) { MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aListItemElement)); // If aListItemElement is empty, then we want to outdent its content. if (&mEditingHost != aListItemElement.GetParentElement() && HTMLEditUtils::IsEmptyBlockElement( aListItemElement, {EmptyCheckOption::TreatNonEditableContentAsInvisible}, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { RefPtr leftListElement = aListItemElement.GetParentElement(); // If the given list item element is not the last list item element of // its parent nor not followed by sub list elements, split the parent // before it. if (!HTMLEditUtils::IsLastChild( aListItemElement, {LeafNodeOption::IgnoreNonEditableNode}, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { Result splitListItemParentResult = mHTMLEditor.SplitNodeWithTransaction( EditorDOMPoint(&aListItemElement)); if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) { NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); return splitListItemParentResult.propagateErr(); } SplitNodeResult unwrappedSplitListItemParentResult = splitListItemParentResult.unwrap(); if (MOZ_UNLIKELY(!unwrappedSplitListItemParentResult.DidSplit())) { NS_WARNING( "HTMLEditor::SplitNodeWithTransaction() didn't split the parent of " "aListItemElement"); MOZ_ASSERT( !unwrappedSplitListItemParentResult.HasCaretPointSuggestion()); return Err(NS_ERROR_FAILURE); } unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion(); leftListElement = unwrappedSplitListItemParentResult.GetPreviousContentAs(); MOZ_DIAGNOSTIC_ASSERT(leftListElement); } auto afterLeftListElement = EditorDOMPoint::After(leftListElement); if (MOZ_UNLIKELY(!afterLeftListElement.IsInContentNode())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } // If aListItemElement is in an invalid sub-list element, move it into // the grand parent list element in order to outdent. if (HTMLEditUtils::IsListElement( *afterLeftListElement.ContainerAs())) { Result moveListItemElementResult = mHTMLEditor.MoveNodeWithTransaction(aListItemElement, afterLeftListElement); if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) { NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); return moveListItemElementResult.propagateErr(); } moveListItemElementResult.inspect().IgnoreCaretPointSuggestion(); return InsertParagraphResult(aListItemElement, EditorDOMPoint(&aListItemElement, 0u)); } // Otherwise, replace the empty aListItemElement with a new paragraph. nsresult rv = mHTMLEditor.DeleteNodeWithTransaction(aListItemElement); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); return Err(rv); } nsStaticAtom& newParagraphTagName = &mDefaultParagraphSeparatorTagName == nsGkAtoms::br ? *nsGkAtoms::p : mDefaultParagraphSeparatorTagName; // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown. Result createNewParagraphElementResult = mHTMLEditor.CreateAndInsertElement( WithTransaction::Yes, MOZ_KnownLive(newParagraphTagName), afterLeftListElement, HTMLEditor::InsertNewBRElement); if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) { NS_WARNING( "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); return createNewParagraphElementResult.propagateErr(); } createNewParagraphElementResult.inspect().IgnoreCaretPointSuggestion(); MOZ_ASSERT(createNewParagraphElementResult.inspect().GetNewNode()); EditorDOMPoint pointToPutCaret( createNewParagraphElementResult.inspect().GetNewNode(), 0u); return InsertParagraphResult( *createNewParagraphElementResult.inspect().GetNewNode(), std::move(pointToPutCaret)); } const EditorDOMPoint pointToSplit = GetBetterPointToSplitParagraph( aListItemElement, aPointToSplit, mEditingHost); MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); // If insertParagraph at end of

or
, we should put opposite type list // item without copying the style of end of aListItemElement. // FIXME: Chrome does not do this. So, we should stop doing this at least on // Firefox later. if (aListItemElement.IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd) && SplitPointIsEndOfSplittingBlock(aListItemElement, pointToSplit, IgnoreBlockBoundaries::Yes) && // However, don't do that if we're handling it in empty list item. !SplitPointIsStartOfSplittingBlock(aListItemElement, pointToSplit, IgnoreBlockBoundaries::Yes)) { nsStaticAtom& oppositeTypeListItemTag = aListItemElement.IsHTMLElement(nsGkAtoms::dt) ? *nsGkAtoms::dd : *nsGkAtoms::dt; // MOZ_KnownLive(oppositeTypeListItemTag) because it's available // until shutdown. Result insertOppositeTypeListItemResultOrError = mHTMLEditor.CreateAndInsertElement( WithTransaction::Yes, MOZ_KnownLive(oppositeTypeListItemTag), EditorDOMPoint::After(aListItemElement), HTMLEditor::InsertNewBRElement); if (MOZ_UNLIKELY(insertOppositeTypeListItemResultOrError.isErr())) { NS_WARNING( "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); return insertOppositeTypeListItemResultOrError.propagateErr(); } CreateElementResult insertOppositeTypeListItemResult = insertOppositeTypeListItemResultOrError.unwrap(); insertOppositeTypeListItemResult.IgnoreCaretPointSuggestion(); RefPtr oppositeTypeListItemElement = insertOppositeTypeListItemResult.UnwrapNewNode(); EditorDOMPoint startOfOppositeTypeListItem(oppositeTypeListItemElement, 0u); MOZ_ASSERT(oppositeTypeListItemElement); return InsertParagraphResult(std::move(oppositeTypeListItemElement), std::move(startOfOppositeTypeListItem)); } // If aListItemElement has some content or aListItemElement is empty but it's // a child of editing host, we want a new list item at the same list level. // First, sort out white-spaces. Result splitListItemResultOrError = SplitParagraphWithTransaction(aListItemElement, pointToSplit); if (MOZ_UNLIKELY(splitListItemResultOrError.isErr())) { NS_WARNING( "AutoInsertParagraphHandler::SplitParagraphWithTransaction() failed"); return splitListItemResultOrError.propagateErr(); } SplitNodeResult splitListItemElement = splitListItemResultOrError.unwrap(); EditorDOMPoint pointToPutCaret = splitListItemElement.UnwrapCaretPoint(); if (MOZ_UNLIKELY(!aListItemElement.GetParent())) { NS_WARNING("Somebody disconnected the target listitem from the parent"); return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } // If aListItemElement is not replaced, we should not do anything anymore. if (MOZ_UNLIKELY(!splitListItemElement.DidSplit()) || NS_WARN_IF(!splitListItemElement.GetNewContentAs()) || NS_WARN_IF(!splitListItemElement.GetOriginalContentAs())) { NS_WARNING( "AutoInsertParagraphHandler::SplitParagraphWithTransaction() didn't " "split the listitem"); return Err(NS_ERROR_FAILURE); } auto* const rightListItemElement = splitListItemElement.GetNextContentAs(); return InsertParagraphResult(*rightListItemElement, std::move(pointToPutCaret)); } // static bool HTMLEditor::AutoInsertParagraphHandler::SplitPointIsStartOfSplittingBlock( const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit, IgnoreBlockBoundaries aIgnoreBlockBoundaries) { EditorRawDOMPoint pointToSplit = aPointToSplit.To(); while (true) { const WSScanResult prevVisibleThing = WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary({}, pointToSplit); if (!prevVisibleThing.ReachedCurrentBlockBoundary()) { return false; } if (prevVisibleThing.ElementPtr() == &aBlockElementToSplit) { return true; } if (!static_cast(aIgnoreBlockBoundaries)) { return false; } pointToSplit = pointToSplit.ParentPoint(); } } // static bool HTMLEditor::AutoInsertParagraphHandler::SplitPointIsEndOfSplittingBlock( const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit, IgnoreBlockBoundaries aIgnoreBlockBoundaries) { bool maybeFollowedByInvisibleBRElement = true; EditorRawDOMPoint pointToSplit = aPointToSplit.To(); while (true) { WSScanResult nextVisibleThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {}, pointToSplit, &aBlockElementToSplit); if (maybeFollowedByInvisibleBRElement && (nextVisibleThing.ReachedBRElement() || nextVisibleThing.ReachedPreformattedLineBreak())) { nextVisibleThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( {}, nextVisibleThing.PointAfterReachedContent(), &aBlockElementToSplit); } if (!nextVisibleThing.ReachedCurrentBlockBoundary()) { return false; } if (nextVisibleThing.ElementPtr() == &aBlockElementToSplit) { return true; } if (!static_cast(aIgnoreBlockBoundaries)) { return false; } pointToSplit = pointToSplit.AfterContainer(); //
element after other block boundary creates an empty line so that // it's always visible. maybeFollowedByInvisibleBRElement = false; } } } // namespace mozilla