- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 78 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 60 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 78 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
- : 80 %
Source code
Revision control
Copy as Markdown
Other Tools
/* 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 "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/AutoRestore.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/EditorForwards.h"
#include "mozilla/Maybe.h"
#include "mozilla/PresShell.h"
#include "mozilla/TextComposition.h"
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/dom/Selection.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsINode.h"
#include "nsRange.h"
#include "nsTArray.h"
#include "nsTextNode.h"
class nsISupports;
namespace mozilla {
using namespace dom;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
nsresult HTMLEditor::InsertLineBreakAsSubAction() {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
if (NS_WARN_IF(!mInitSucceeded)) {
return NS_ERROR_NOT_INITIALIZED;
}
{
Result<EditActionResult, nsresult> result =
CanHandleHTMLEditSubAction(CheckSelectionInReplacedElement::No);
if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
return result.unwrapErr();
}
if (result.inspect().Canceled()) {
return NS_OK;
}
}
// XXX This may be called by execCommand() with "insertLineBreak".
// In such case, naming the transaction "TypingTxnName" is odd.
AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
ScrollSelectionIntoView::Yes,
__FUNCTION__);
// calling it text insertion to trigger moz br treatment by rules
// XXX Why do we use EditSubAction::eInsertText here? Looks like
// EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
// is better.
IgnoredErrorResult ignoredError;
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
return ignoredError.StealNSResult();
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
"HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
UndefineCaretBidiLevel();
// If the selection isn't collapsed, delete it.
if (!SelectionRef().IsCollapsed()) {
nsresult rv =
DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
if (NS_FAILED(rv)) {
NS_WARNING(
"EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
return rv;
}
}
const RefPtr<Element> editingHost =
ComputeEditingHost(LimitInBodyElement::No);
if (NS_WARN_IF(!editingHost)) {
return NS_ERROR_FAILURE;
}
AutoInsertLineBreakHandler handler(*this, *editingHost);
nsresult rv = handler.Run();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"AutoInsertLineBreakHandler::Run() failed");
return rv;
}
nsresult HTMLEditor::AutoInsertLineBreakHandler::Run() {
MOZ_ASSERT(mHTMLEditor.IsEditActionDataAvailable());
const auto atStartOfSelection =
mHTMLEditor.GetFirstSelectionStartPoint<EditorDOMPoint>();
if (NS_WARN_IF(!atStartOfSelection.IsInContentNode())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(atStartOfSelection.IsSetAndValidInComposedDoc());
const Maybe<LineBreakType> lineBreakType =
mHTMLEditor.GetPreferredLineBreakType(
*atStartOfSelection.ContainerAs<nsIContent>(), mEditingHost);
if (MOZ_UNLIKELY(!lineBreakType)) {
return NS_SUCCESS_DOM_NO_OPERATION; // Cannot insert a line break there.
}
if (lineBreakType.value() == LineBreakType::BRElement) {
nsresult rv = HandleInsertBRElement();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"AutoInsertLineBreakHandler::HandleInsertBRElement()");
return rv;
}
nsresult rv = HandleInsertLinefeed();
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"AutoInsertLineBreakHandler::HandleInsertLinefeed() failed");
return rv;
}
nsresult HTMLEditor::AutoInsertLineBreakHandler::HandleInsertBRElement() {
const EditorDOMPoint pointToInsert = [&]() {
const auto atStartOfSelection =
mHTMLEditor.GetFirstSelectionStartPoint<EditorDOMPoint>();
MOZ_ASSERT(atStartOfSelection.IsInContentNode());
return HTMLEditUtils::GetPossiblePointToInsert(
atStartOfSelection, *nsGkAtoms::br, mEditingHost);
}();
if (NS_WARN_IF(!pointToInsert.IsSet())) {
return Err(NS_ERROR_FAILURE);
}
MOZ_ASSERT(pointToInsert.IsInContentNode());
// XXX Should we check the preferred line break again?
Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
mHTMLEditor.InsertLineBreak(WithTransaction::Yes,
LineBreakType::BRElement, pointToInsert,
nsIEditor::eNext);
if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
NS_WARNING(
"HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
"LineBreakType::BRElement, eNext) failed");
return insertLineBreakResultOrError.unwrapErr();
}
CreateLineBreakResult insertLineBreakResult =
insertLineBreakResultOrError.unwrap();
MOZ_ASSERT(insertLineBreakResult.Handled());
insertLineBreakResult.IgnoreCaretPointSuggestion();
auto pointToPutCaret = insertLineBreakResult.UnwrapCaretPoint();
if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) {
NS_WARNING("Inserted <br> was unexpectedly removed");
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
{
AutoTrackDOMPoint trackPointToPutCaret(mHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
Result<CreateLineBreakResult, nsresult> insertPaddingBRResultOrError =
mHTMLEditor.InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded(
insertLineBreakResult.AfterLineBreak<EditorDOMPoint>(),
mEditingHost);
if (insertPaddingBRResultOrError.isErr()) [[unlikely]] {
NS_WARNING(
"HTMLEditor::InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded() "
"failed");
return insertPaddingBRResultOrError.propagateErr();
}
CreateLineBreakResult insertPaddingBRResult =
insertPaddingBRResultOrError.unwrap();
insertPaddingBRResult.IgnoreCaretPointSuggestion();
trackPointToPutCaret.Flush(StopTracking::Yes);
if (!insertPaddingBRResult.Handled()) {
// If the line break is followed by visible content, we want to put caret
// to start of the following content to preserve the style at start of the
// line.
const WSScanResult nextThingOfBR =
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
{WSRunScanner::Option::OnlyEditableNodes,
WSRunScanner::Option::StopAtAnyEmptyInlineContainers},
insertLineBreakResult.AfterLineBreak<EditorRawDOMPoint>());
if (nextThingOfBR.InVisibleOrCollapsibleCharacters() ||
nextThingOfBR.ReachedSpecialContent()) {
pointToPutCaret = nextThingOfBR.PointAtReachedContent<EditorDOMPoint>();
} else if (nextThingOfBR.ReachedEmptyInlineContainerElement()) {
pointToPutCaret = EditorDOMPoint(nextThingOfBR.ElementPtr(), 0u);
}
}
}
nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed");
return rv;
}
nsresult HTMLEditor::AutoInsertLineBreakHandler::HandleInsertLinefeed() {
nsresult rv = mHTMLEditor.EnsureNoPaddingBRElementForEmptyEditor();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
"failed, but ignored");
if (NS_SUCCEEDED(rv) && mHTMLEditor.SelectionRef().IsCollapsed()) {
nsresult rv =
mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(mEditingHost);
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
"failed, but ignored");
if (NS_SUCCEEDED(rv)) {
nsresult rv = mHTMLEditor.PrepareInlineStylesForCaret();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
}
}
const EditorDOMPoint atStartOfSelection =
mHTMLEditor.GetFirstSelectionStartPoint<EditorDOMPoint>();
if (NS_WARN_IF(!atStartOfSelection.IsInContentNode())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(atStartOfSelection.IsSetAndValidInComposedDoc());
// Do nothing if the node is read-only
if (!HTMLEditUtils::IsSimplyEditableNode(
*atStartOfSelection.GetContainer())) {
return NS_SUCCESS_DOM_NO_OPERATION;
}
Result<EditorDOMPoint, nsresult> insertLineFeedResult =
AutoInsertLineBreakHandler::InsertLinefeed(
mHTMLEditor, atStartOfSelection, mEditingHost);
if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) {
NS_WARNING("AutoInsertLineBreakHandler::InsertLinefeed() failed");
return insertLineFeedResult.unwrapErr();
}
EditorDOMPoint pointToPutCaret = insertLineFeedResult.unwrap();
// If the line break is followed by visible content, we want to put caret
// to start of the following content to preserve the style at start of the
// line.
const WSScanResult nextThingOfLinefeed =
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
{WSRunScanner::Option::OnlyEditableNodes,
WSRunScanner::Option::StopAtAnyEmptyInlineContainers},
pointToPutCaret);
if (nextThingOfLinefeed.InVisibleOrCollapsibleCharacters() ||
nextThingOfLinefeed.ReachedSpecialContent()) {
pointToPutCaret =
nextThingOfLinefeed.PointAtReachedContent<EditorDOMPoint>();
} else if (nextThingOfLinefeed.ReachedEmptyInlineContainerElement()) {
pointToPutCaret = EditorDOMPoint(nextThingOfLinefeed.ElementPtr(), 0u);
}
rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed");
return rv;
}
// static
Result<EditorDOMPoint, nsresult>
HTMLEditor::AutoInsertLineBreakHandler::InsertLinefeed(
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToBreak,
const Element& aEditingHost) {
if (NS_WARN_IF(!aPointToBreak.IsSet())) {
return Err(NS_ERROR_INVALID_ARG);
}
const RefPtr<Document> document = aHTMLEditor.GetDocument();
MOZ_DIAGNOSTIC_ASSERT(document);
if (NS_WARN_IF(!document)) {
return Err(NS_ERROR_FAILURE);
}
// TODO: The following code is duplicated from `HandleInsertText`. They
// should be merged when we fix bug 92921.
Result<EditorDOMPoint, nsresult> setStyleResult =
aHTMLEditor.CreateStyleForInsertText(aPointToBreak, aEditingHost);
if (MOZ_UNLIKELY(setStyleResult.isErr())) {
NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
return setStyleResult.propagateErr();
}
EditorDOMPoint pointToInsert = setStyleResult.inspect().IsSet()
? setStyleResult.inspect()
: aPointToBreak;
if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
NS_WARN_IF(!pointToInsert.IsInContentNode())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
MOZ_ASSERT(pointToInsert.IsSetAndValid());
// The node may not be able to have a text node so that we need to check it
// here.
pointToInsert = HTMLEditUtils::GetPossiblePointToInsert(
pointToInsert, *nsGkAtoms::textTagName, aEditingHost);
if (NS_WARN_IF(!pointToInsert.IsSet())) {
return Err(NS_ERROR_FAILURE);
}
MOZ_ASSERT(pointToInsert.IsInContentNode());
// FIXME: If the computed point does not preformat linefeed, we should switch
// back to inserting a <br>. However, I think it should be handled before
// calling this.
AutoRestore<bool> disableListener(
aHTMLEditor.EditSubActionDataRef().mAdjustChangedRangeFromListener);
aHTMLEditor.EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
// TODO: We don't need AutoTransactionsConserveSelection here in the normal
// cases, but removing this may cause the behavior with the legacy
// mutation event listeners. We should try to delete this in a bug.
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
AutoTrackDOMPoint trackingInsertingPosition(aHTMLEditor.RangeUpdaterRef(),
&pointToInsert);
Result<CreateLineBreakResult, nsresult> insertLinefeedResultOrError =
aHTMLEditor.InsertLineBreak(WithTransaction::Yes, LineBreakType::Linefeed,
pointToInsert, eNext);
if (MOZ_UNLIKELY(insertLinefeedResultOrError.isErr())) {
NS_WARNING(
"HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
"LineBreakType::Linefeed, eNext) failed");
return insertLinefeedResultOrError.propagateErr();
}
trackingInsertingPosition.Flush(StopTracking::Yes);
CreateLineBreakResult insertLinefeedResult =
insertLinefeedResultOrError.unwrap();
EditorDOMPoint pointToPutCaret = insertLinefeedResult.UnwrapCaretPoint();
// Insert a padding <br> if the inserted linefeed is followed by a block
// boundary. Note that it should always be <br> for avoiding padding line
// breaks appear in `.textContent` value.
if (pointToPutCaret.IsInContentNode() && pointToPutCaret.IsEndOfContainer()) {
AutoTrackDOMPoint trackingInsertedPosition(aHTMLEditor.RangeUpdaterRef(),
&pointToInsert);
AutoTrackDOMPoint trackingNewCaretPosition(aHTMLEditor.RangeUpdaterRef(),
&pointToPutCaret);
Result<CreateLineBreakResult, nsresult> insertPaddingBRResultOrError =
aHTMLEditor.InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded(
insertLinefeedResult.AfterLineBreak<EditorDOMPoint>(),
aEditingHost);
if (insertPaddingBRResultOrError.isErr()) [[unlikely]] {
NS_WARNING(
"HTMLEditor::InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded() "
"failed");
return insertPaddingBRResultOrError.propagateErr();
}
insertPaddingBRResultOrError.unwrap().IgnoreCaretPointSuggestion();
}
// manually update the doc changed range so that
// OnEndHandlingTopLevelEditSubActionInternal will clean up the correct
// portion of the document.
MOZ_ASSERT(pointToPutCaret.IsSet());
if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
// XXX Here is odd. We did mChangedRange->SetStartAndEnd(pointToInsert,
// pointToPutCaret), but it always fails because of the latter is unset.
// Therefore, always returning NS_ERROR_FAILURE from here is the
// traditional behavior...
// TODO: Stop updating the interline position of Selection with fixing here
// and returning expected point.
DebugOnly<nsresult> rvIgnored =
aHTMLEditor.SelectionRef().SetInterlinePosition(
InterlinePosition::EndOfLine);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Selection::SetInterlinePosition(InterlinePosition::"
"EndOfLine) failed, but ignored");
if (NS_FAILED(aHTMLEditor.TopLevelEditSubActionDataRef()
.mChangedRange->CollapseTo(pointToInsert))) {
NS_WARNING("nsRange::CollapseTo() failed");
return Err(NS_ERROR_FAILURE);
}
NS_WARNING(
"We always return NS_ERROR_FAILURE here because of a failure of "
"updating mChangedRange");
return Err(NS_ERROR_FAILURE);
}
if (NS_FAILED(aHTMLEditor.TopLevelEditSubActionDataRef()
.mChangedRange->SetStartAndEnd(
pointToInsert.ToRawRangeBoundary(),
pointToPutCaret.ToRawRangeBoundary()))) {
NS_WARNING("nsRange::SetStartAndEnd() failed");
return Err(NS_ERROR_FAILURE);
}
pointToPutCaret.SetInterlinePosition(InterlinePosition::EndOfLine);
return pointToPutCaret;
}
} // namespace mozilla