2053 lines
93 KiB
C++
2053 lines
93 KiB
C++
/* 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 <utility>
|
|
|
|
#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<EditActionResult, nsresult>
|
|
HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) {
|
|
if (NS_WARN_IF(!mInitSucceeded)) {
|
|
return Err(NS_ERROR_NOT_INITIALIZED);
|
|
}
|
|
|
|
{
|
|
Result<EditActionResult, nsresult> result =
|
|
CanHandleHTMLEditSubAction(CheckSelectionInReplacedElement::No);
|
|
if (MOZ_UNLIKELY(result.isErr())) {
|
|
NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
|
|
return result;
|
|
}
|
|
if (result.inspect().Canceled()) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (GetEditActionEditContext()) {
|
|
// Don't insert paragraph if there is an EditContext
|
|
return EditActionResult::HandledResult();
|
|
}
|
|
|
|
// 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<EditActionResult, nsresult> insertParagraphResult =
|
|
insertParagraphHandler.Run();
|
|
NS_WARNING_ASSERTION(insertParagraphResult.isOk(),
|
|
"AutoInsertParagraphHandler::Run() failed");
|
|
return insertParagraphResult;
|
|
}
|
|
|
|
Result<EditActionResult, nsresult>
|
|
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<EditorDOMPoint>();
|
|
if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
// If the element can have a <br> element (it means that the element or its
|
|
// container must be able to have <div> or <p> 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<Element> mailCiteElement =
|
|
mHTMLEditor.GetMostDistantAncestorMailCiteElement(
|
|
*pointToInsert.ContainerAs<nsIContent>())) {
|
|
// Split any mailcites in the way. Should we abort this if we encounter
|
|
// table cell boundaries?
|
|
Result<CaretPoint, nsresult> 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 <br> as a
|
|
// paragraph separator, just append a <br>.
|
|
// If the editing host parent element is editable, it means that the editing
|
|
// host must be a <body> 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 <br> element.
|
|
// XXX Currently, we don't support editing outside <body> element, but Blink
|
|
// does it.
|
|
if (mEditingHost.GetParentElement() &&
|
|
HTMLEditUtils::IsSimplyEditableNode(*mEditingHost.GetParentElement()) &&
|
|
!nsContentUtils::ContentIsFlattenedTreeDescendantOf(
|
|
pointToInsert.ContainerAs<nsIContent>(), &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 <br> simply.
|
|
RefPtr<Element> editableBlockElement =
|
|
HTMLEditUtils::GetInclusiveAncestorElement(
|
|
*pointToInsert.ContainerAs<nsIContent>(),
|
|
HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
|
|
BlockInlineCheck::UseComputedDisplayOutsideStyle);
|
|
|
|
// If we cannot insert a <p>/<div> element at the selection, we should insert
|
|
// a <br> element or a linefeed instead.
|
|
if (ShouldInsertLineBreakInstead(editableBlockElement, pointToInsert)) {
|
|
const Maybe<LineBreakType> lineBreakType =
|
|
mHTMLEditor.GetPreferredLineBreakType(
|
|
*pointToInsert.ContainerAs<nsIContent>(), mEditingHost);
|
|
if (MOZ_UNLIKELY(!lineBreakType)) {
|
|
// Cannot insert a line break there.
|
|
return EditActionResult::IgnoredResult();
|
|
}
|
|
if (lineBreakType.value() == LineBreakType::Linefeed) {
|
|
Result<EditActionResult, nsresult> insertLinefeedResultOrError =
|
|
HandleInsertLinefeed(pointToInsert);
|
|
NS_WARNING_ASSERTION(
|
|
insertLinefeedResultOrError.isOk(),
|
|
"AutoInsertParagraphHandler::HandleInsertLinefeed() failed");
|
|
return insertLinefeedResultOrError;
|
|
}
|
|
Result<EditActionResult, nsresult> insertBRElementResultOrError =
|
|
HandleInsertBRElement(pointToInsert);
|
|
NS_WARNING_ASSERTION(
|
|
insertBRElementResultOrError.isOk(),
|
|
"AutoInsertParagraphHandler::HandleInsertBRElement() failed");
|
|
return insertBRElementResultOrError;
|
|
}
|
|
|
|
RefPtr<Element> blockElementToPutCaret;
|
|
// If the default paragraph separator is not <br> 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<RefPtr<Element>, 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<EditorDOMPoint>();
|
|
if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
|
|
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
|
}
|
|
MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
|
|
|
|
editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement(
|
|
*pointToInsert.ContainerAs<nsIContent>(),
|
|
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 <br>.
|
|
Result<EditActionResult, nsresult> 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<Element> insertedPaddingBRElement;
|
|
{
|
|
Result<CreateLineBreakResult, nsresult> 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<EditorDOMPoint>();
|
|
if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
|
|
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
|
}
|
|
}
|
|
|
|
RefPtr<Element> maybeNonEditableListItem =
|
|
HTMLEditUtils::GetClosestInclusiveAncestorListItemElement(
|
|
*editableBlockElement, &mEditingHost);
|
|
if (maybeNonEditableListItem &&
|
|
HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem)) {
|
|
Result<InsertParagraphResult, nsresult> 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<Element> 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<InsertParagraphResult, nsresult>
|
|
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 <p> container and
|
|
// <div> container. However, we are still using <br> as default
|
|
// paragraph separator (non-standard) and we've split only <p> 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 <br>. Otherwise, take consistent behavior
|
|
// between <p> container and <div> 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 <br>s
|
|
Result<SplitNodeResult, nsresult> 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<Element> rightParagraphElement =
|
|
unwrappedSplitNodeResult.DidSplit()
|
|
? unwrappedSplitNodeResult.GetNextContentAs<Element>()
|
|
: 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 <br> at the selection.
|
|
Result<EditActionResult, nsresult> insertBRElementResultOrError =
|
|
HandleInsertBRElement(pointToInsert, blockElementToPutCaret);
|
|
NS_WARNING_ASSERTION(
|
|
insertBRElementResultOrError.isOk(),
|
|
"AutoInsertParagraphHandler::HandleInsertBRElement() failed");
|
|
return insertBRElementResultOrError;
|
|
}
|
|
|
|
Result<EditActionResult, nsresult>
|
|
HTMLEditor::AutoInsertParagraphHandler::HandleInsertBRElement(
|
|
const EditorDOMPoint& aPointToInsert,
|
|
const Element* aBlockElementWhichShouldHaveCaret /* = nullptr */) {
|
|
Result<CreateElementResult, nsresult> 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<EditActionResult, nsresult>
|
|
HTMLEditor::AutoInsertParagraphHandler::HandleInsertLinefeed(
|
|
const EditorDOMPoint& aPointToInsert) {
|
|
Result<EditorDOMPoint, nsresult> 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 <br> or the
|
|
// element cannot contain a <p> element, we should insert a <br>
|
|
// element.
|
|
if (!HTMLEditUtils::IsSplittableNode(*aEditableBlockElement)) {
|
|
return mDefaultParagraphSeparator == ParagraphSeparator::br ||
|
|
!HTMLEditUtils::CanElementContainParagraph(*aEditableBlockElement) ||
|
|
(aCandidatePointToSplit.IsInContentNode() &&
|
|
mHTMLEditor
|
|
.GetPreferredLineBreakType(
|
|
*aCandidatePointToSplit.ContainerAs<nsIContent>(),
|
|
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 <br> element.
|
|
if (HTMLEditUtils::IsSingleLineContainer(*aEditableBlockElement)) {
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, unless there is no block ancestor which can contain <p>
|
|
// 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<EditorDOMPoint, nsresult> pointToPutCaretOrError =
|
|
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<EditorDOMPoint>(
|
|
*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<CreateElementResult, nsresult>
|
|
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 <br> element.
|
|
RefPtr<Element> brElement;
|
|
if (mHTMLEditor.IsPlaintextMailComposer()) {
|
|
Result<CreateLineBreakResult, nsresult> 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 <br> between the split links.
|
|
RefPtr<Element> linkNode =
|
|
HTMLEditor::GetLinkElement(pointToBreak.GetContainer());
|
|
if (linkNode) {
|
|
// FIXME: Normalize surrounding white-spaces before splitting the
|
|
// insertion point here.
|
|
Result<SplitNodeResult, nsresult> 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<EditorDOMPoint>();
|
|
}
|
|
Result<CreateLineBreakResult, nsresult> 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 <br> 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<CreateLineBreakResult, nsresult> {
|
|
// Empty last line is invisible if it's immediately before either parent or
|
|
// another block's boundary so that we need to put invisible <br> element
|
|
// here for making it visible.
|
|
Result<CreateLineBreakResult, nsresult>
|
|
insertPaddingBRElementResultOrError =
|
|
WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
|
LineBreakType::BRElement, mHTMLEditor, afterBRElement);
|
|
NS_WARNING_ASSERTION(insertPaddingBRElementResultOrError.isOk(),
|
|
"WhiteSpaceVisibilityKeeper::InsertLineBreak("
|
|
"LineBreakType::BRElement) failed");
|
|
// afterBRElement points after the first <br> with referring an old child.
|
|
// Therefore, we need to update it with new child which is the new invisible
|
|
// <br>.
|
|
afterBRElement = insertPaddingBRElementResultOrError.inspect()
|
|
.AtLineBreak<EditorDOMPoint>();
|
|
return insertPaddingBRElementResultOrError;
|
|
};
|
|
|
|
if (brElementIsAfterBlock && brElementIsBeforeBlock) {
|
|
// We just placed a <br> 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 <br> element may not be
|
|
// between blocks.
|
|
EditorDOMPoint pointToPutCaret;
|
|
if (editingHostIsEmpty) {
|
|
Result<CreateLineBreakResult, nsresult>
|
|
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<MoveNodeResult, nsresult> 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 <br> with referring an old child.
|
|
// Therefore, we need to update it with new child which is the new
|
|
// invisible <br>.
|
|
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<CreateLineBreakResult, nsresult>
|
|
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<CaretPoint, nsresult>
|
|
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 <br> to the end of left cite node if it was a <span> 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 <br> 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<Element>();
|
|
auto* const rightCiteElement =
|
|
unwrappedSplitCiteElementResult.GetNextContentAs<Element>();
|
|
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<CreateLineBreakResult, nsresult>
|
|
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, <br> should be inserted after current cite. However, if
|
|
// left cite hasn't been created because the split point was start of the
|
|
// cite node, <br> should be inserted before the current cite.
|
|
Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
|
|
mHTMLEditor.InsertLineBreak(
|
|
WithTransaction::Yes, LineBreakType::BRElement,
|
|
unwrappedSplitCiteElementResult.AtSplitPoint<EditorDOMPoint>());
|
|
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<EditorDOMPoint>(),
|
|
aMailCiteElement);
|
|
if (NS_FAILED(rvOfInsertPaddingBRElement)) {
|
|
NS_WARNING(
|
|
"Failed to insert additional <br> 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<EditorDOMPoint>();
|
|
pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
|
|
return CaretPoint(std::move(pointToPutCaret));
|
|
}
|
|
|
|
Result<SplitNodeResult, nsresult>
|
|
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<EditorDOMPoint>();
|
|
}
|
|
|
|
if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
Result<EditorDOMPoint, nsresult> 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<SplitNodeResult, nsresult> 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<CreateLineBreakResult, nsresult> 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<InsertParagraphResult, nsresult>
|
|
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<InsertParagraphResult, nsresult>
|
|
handleAtEndOfHeadingElementResultOrError =
|
|
HandleAtEndOfHeadingElement(aHeadingElement);
|
|
NS_WARNING_ASSERTION(
|
|
handleAtEndOfHeadingElementResultOrError.isOk(),
|
|
"AutoInsertParagraphHandler::HandleAtEndOfHeadingElement() failed");
|
|
return handleAtEndOfHeadingElementResultOrError;
|
|
}
|
|
|
|
Result<SplitNodeResult, nsresult> 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<Element>();
|
|
MOZ_ASSERT(rightHeadingElement,
|
|
"SplitNodeResult::GetNextContent() should return something if "
|
|
"DidSplit() returns true");
|
|
return InsertParagraphResult(*rightHeadingElement,
|
|
splitHeadingResult.UnwrapCaretPoint());
|
|
}
|
|
|
|
Result<InsertParagraphResult, nsresult>
|
|
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 <br> element.
|
|
nsStaticAtom& newParagraphTagName =
|
|
&mDefaultParagraphSeparatorTagName == nsGkAtoms::br
|
|
? *nsGkAtoms::p
|
|
: mDefaultParagraphSeparatorTagName;
|
|
// We want a wrapper element even if we separate with a <br>.
|
|
// 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<CreateElementResult, nsresult> 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 <br> 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 <br> element's flag. It may be wrong if
|
|
// the web app moved the <br> 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 <br> or follows an invisible <br>, 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<Text>(),
|
|
{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 <br> or is followed by an invisible <br>, 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<Text>(),
|
|
{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 <br> or follows an invisible <br>, 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 <br> 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 <br> or followed by an invisible <br>, 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<EditorRawDOMPoint>();
|
|
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<EditorDOMPoint>();
|
|
}
|
|
}
|
|
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<EditorRawDOMPoint>();
|
|
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<EditorDOMPoint>();
|
|
}
|
|
|
|
// 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<nsIContent>();
|
|
container && container != &aBlockElementToSplit &&
|
|
!HTMLEditUtils::IsSplittableNode(*container);
|
|
container = container->GetParent()) {
|
|
pointToSplit = pointToSplit.ParentPoint();
|
|
}
|
|
return pointToSplit;
|
|
}
|
|
|
|
Result<EditorDOMPoint, nsresult> 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<EditorLineBreak>();
|
|
{
|
|
// FIXME: Once bug 1951041 is fixed in the layout level, we don't need to
|
|
// treat collapsible white-spaces before invisible <br> elements here.
|
|
AutoTrackDOMPoint trackPointToSplit(mHTMLEditor.RangeUpdaterRef(),
|
|
&pointToSplit);
|
|
Maybe<AutoTrackLineBreak> trackPrecedingLineBreak;
|
|
if (precedingLineBreak.IsPreformattedLineBreak()) {
|
|
trackPrecedingLineBreak.emplace(mHTMLEditor.RangeUpdaterRef(),
|
|
&precedingLineBreak);
|
|
}
|
|
Result<EditorDOMPoint, nsresult>
|
|
normalizePrecedingWhiteSpacesResultOrError =
|
|
[&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMPoint, nsresult> {
|
|
if (precedingLineBreak.IsHTMLBRElement() ||
|
|
precedingLineBreak.IsPreformattedLineBreakAtStartOfText()) {
|
|
Result<EditorDOMPoint, nsresult> ret =
|
|
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
|
|
mHTMLEditor, precedingLineBreak.To<EditorDOMPoint>(), {});
|
|
NS_WARNING_ASSERTION(
|
|
ret.isOk(),
|
|
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed");
|
|
return ret;
|
|
}
|
|
Result<EditorDOMPoint, nsresult> ret =
|
|
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
|
|
mHTMLEditor, precedingLineBreak.To<EditorDOMPoint>(), {});
|
|
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<EditorDOMPoint, nsresult> 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<EditorDOMPoint, nsresult> HTMLEditor::AutoInsertParagraphHandler::
|
|
MaybeInsertFollowingBRElementToPreserveRightBlock(
|
|
const Element& aBlockElementToSplit,
|
|
const EditorDOMPoint& aPointToSplit) {
|
|
MOZ_ASSERT(HTMLEditUtils::IsSplittableNode(aBlockElementToSplit));
|
|
MOZ_ASSERT(aPointToSplit.ContainerAs<nsIContent>()->IsInclusiveDescendantOf(
|
|
&aBlockElementToSplit));
|
|
|
|
const Element* const closestContainerElement =
|
|
HTMLEditUtils::GetInclusiveAncestorElement(
|
|
*aPointToSplit.ContainerAs<nsIContent>(),
|
|
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<EditorLineBreak> unnecessaryLineBreak;
|
|
const EditorDOMPoint pointToInsertFollowingBRElement =
|
|
[&]() MOZ_NEVER_INLINE_DEBUG {
|
|
const WSScanResult nextVisibleThing =
|
|
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
|
|
{}, aPointToSplit, &aBlockElementToSplit);
|
|
if (nextVisibleThing.ReachedLineBreak()) {
|
|
EditorLineBreak lineBreak =
|
|
nextVisibleThing.CreateEditorLineBreak<EditorLineBreak>();
|
|
// If there is a <br> 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 <br> in the empty right
|
|
// paragraph so that we cannot reuse it if it's a padding <br>.
|
|
// 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 <br> 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 <br> element for the right paragraph because
|
|
// it'll have some visible content.
|
|
else if (!nextVisibleThing.ReachedCurrentBlockBoundary()) {
|
|
return EditorDOMPoint();
|
|
}
|
|
// We want to insert a padding <br> 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<EditorDOMPoint, nsresult> 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<AutoTrackDOMPoint> trackPointToSplit;
|
|
if (pointToSplit.GetContainer() ==
|
|
pointToInsertFollowingBRElement.GetContainer()) {
|
|
trackPointToSplit.emplace(mHTMLEditor.RangeUpdaterRef(), &pointToSplit);
|
|
}
|
|
Result<CreateElementResult, nsresult> insertPaddingBRElementResultOrError =
|
|
mHTMLEditor.InsertBRElement(
|
|
WithTransaction::Yes,
|
|
// We don't want to expose the <br> 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 <br> 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<EditorRawDOMPoint>(),
|
|
&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<SplitNodeResult, nsresult>
|
|
HTMLEditor::AutoInsertParagraphHandler::SplitParagraphWithTransaction(
|
|
Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit) {
|
|
// First, maybe the split point follows an invisible <br>. E.g., when
|
|
// `<p><a href=foo>foo[]<br></a></p>`,
|
|
// GetBetterSplitPointToAvoidToContinueLink() adjusted the split point as
|
|
// `<p><a href=foo>foo<br></a>{}</p>`. Then, we shouldn't insert another
|
|
// <br> to end of the left <p> to make the last line visible. Even though we
|
|
// need to insert an invisible <br> element later, let's delete the invisible
|
|
// line break first to make this method simpler.
|
|
Result<EditorDOMPoint, nsresult> 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<EditorDOMPoint, nsresult> 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 <br>
|
|
// before splitting to preserve the cloned inline elements in the new
|
|
// paragraph.
|
|
{
|
|
Result<EditorDOMPoint, nsresult> 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<Element> deepestContainerElementToSplit =
|
|
HTMLEditUtils::GetInclusiveAncestorElement(
|
|
*pointToSplit.ContainerAs<nsIContent>(),
|
|
HTMLEditUtils::ClosestContainerElementOrVoidAncestorLimiter,
|
|
BlockInlineCheck::UseComputedDisplayOutsideStyle,
|
|
&aBlockElementToSplit);
|
|
if (NS_WARN_IF(!deepestContainerElementToSplit)) {
|
|
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
|
}
|
|
Result<SplitNodeResult, nsresult> 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<Element>();
|
|
MOZ_ASSERT(leftDivOrParagraphElement,
|
|
"SplitNodeResult::GetPreviousContent() should return something if "
|
|
"DidSplit() returns true");
|
|
auto* const rightDivOrParagraphElement =
|
|
splitDivOrPResult.GetNextContentAs<Element>();
|
|
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 <br> 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 <br> 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 <br> 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
|
|
// <br> 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<CreateElementResult, nsresult> insertPaddingBRElementResultOrError =
|
|
mHTMLEditor.InsertBRElement(
|
|
WithTransaction::Yes,
|
|
// XXX We don't want to expose the <br> 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 <br> 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<CreateLineBreakResult, nsresult>
|
|
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 <br>
|
|
// into the last one.
|
|
Result<CreateLineBreakResult, nsresult> 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<InsertParagraphResult, nsresult>
|
|
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<Element> 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<SplitNodeResult, nsresult> 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<Element>();
|
|
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<nsIContent>())) {
|
|
Result<MoveNodeResult, nsresult> 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<CreateElementResult, nsresult> 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 <dt> or <dd>, 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<CreateElementResult, nsresult>
|
|
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<Element> 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<SplitNodeResult, nsresult> 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<Element>()) ||
|
|
NS_WARN_IF(!splitListItemElement.GetOriginalContentAs<Element>())) {
|
|
NS_WARNING(
|
|
"AutoInsertParagraphHandler::SplitParagraphWithTransaction() didn't "
|
|
"split the listitem");
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
auto* const rightListItemElement =
|
|
splitListItemElement.GetNextContentAs<Element>();
|
|
return InsertParagraphResult(*rightListItemElement,
|
|
std::move(pointToPutCaret));
|
|
}
|
|
|
|
// static
|
|
bool HTMLEditor::AutoInsertParagraphHandler::SplitPointIsStartOfSplittingBlock(
|
|
const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit,
|
|
IgnoreBlockBoundaries aIgnoreBlockBoundaries) {
|
|
EditorRawDOMPoint pointToSplit = aPointToSplit.To<EditorRawDOMPoint>();
|
|
while (true) {
|
|
const WSScanResult prevVisibleThing =
|
|
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary({}, pointToSplit);
|
|
if (!prevVisibleThing.ReachedCurrentBlockBoundary()) {
|
|
return false;
|
|
}
|
|
if (prevVisibleThing.ElementPtr() == &aBlockElementToSplit) {
|
|
return true;
|
|
}
|
|
if (!static_cast<bool>(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<EditorRawDOMPoint>();
|
|
while (true) {
|
|
WSScanResult nextVisibleThing =
|
|
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
|
|
{}, pointToSplit, &aBlockElementToSplit);
|
|
if (maybeFollowedByInvisibleBRElement &&
|
|
(nextVisibleThing.ReachedBRElement() ||
|
|
nextVisibleThing.ReachedPreformattedLineBreak())) {
|
|
nextVisibleThing =
|
|
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
|
|
{},
|
|
nextVisibleThing.PointAfterReachedContent<EditorRawDOMPoint>(),
|
|
&aBlockElementToSplit);
|
|
}
|
|
if (!nextVisibleThing.ReachedCurrentBlockBoundary()) {
|
|
return false;
|
|
}
|
|
if (nextVisibleThing.ElementPtr() == &aBlockElementToSplit) {
|
|
return true;
|
|
}
|
|
if (!static_cast<bool>(aIgnoreBlockBoundaries)) {
|
|
return false;
|
|
}
|
|
pointToSplit = pointToSplit.AfterContainer();
|
|
// <br> element after other block boundary creates an empty line so that
|
|
// it's always visible.
|
|
maybeFollowedByInvisibleBRElement = false;
|
|
}
|
|
}
|
|
|
|
} // namespace mozilla
|