Bug 1781994 - part 2: Make `HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges` stop touching `Selection` directly r=m_kato

Depends on D154343

Differential Revision: https://phabricator.services.mozilla.com/D154344
This commit is contained in:
Masayuki Nakano 2022-08-16 00:08:07 +00:00
Родитель 9f9cd9e318
Коммит fcecac970b
5 изменённых файлов: 169 добавлений и 116 удалений

Просмотреть файл

@ -1206,21 +1206,42 @@ class EditorDOMRangeBase final {
}
template <typename MaybeOtherPointType>
MOZ_NEVER_INLINE_DEBUG void SetStart(const MaybeOtherPointType& aStart) {
void SetStart(const MaybeOtherPointType& aStart) {
mStart = aStart.template To<PointType>();
}
void SetStart(PointType&& aStart) { mStart = std::move(aStart); }
template <typename MaybeOtherPointType>
MOZ_NEVER_INLINE_DEBUG void SetEnd(const MaybeOtherPointType& aEnd) {
void SetEnd(const MaybeOtherPointType& aEnd) {
mEnd = aEnd.template To<PointType>();
}
void SetEnd(PointType&& aEnd) { mEnd = std::move(aEnd); }
template <typename StartPointType, typename EndPointType>
MOZ_NEVER_INLINE_DEBUG void SetStartAndEnd(const StartPointType& aStart,
const EndPointType& aEnd) {
void SetStartAndEnd(const StartPointType& aStart, const EndPointType& aEnd) {
MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
aStart.EqualsOrIsBefore(aEnd));
mStart = aStart.template To<PointType>();
mEnd = aEnd.template To<PointType>();
}
template <typename StartPointType>
void SetStartAndEnd(const StartPointType& aStart, PointType&& aEnd) {
MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
aStart.EqualsOrIsBefore(aEnd));
mStart = aStart.template To<PointType>();
mEnd = std::move(aEnd);
}
template <typename EndPointType>
void SetStartAndEnd(PointType&& aStart, const EndPointType& aEnd) {
MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
aStart.EqualsOrIsBefore(aEnd));
mStart = std::move(aStart);
mEnd = aEnd.template To<PointType>();
}
void SetStartAndEnd(PointType&& aStart, PointType&& aEnd) {
MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
aStart.EqualsOrIsBefore(aEnd));
mStart = std::move(aStart);
mEnd = std::move(aEnd);
}
void Clear() {
mStart.Clear();
mEnd.Clear();
@ -1248,6 +1269,11 @@ class EditorDOMRangeBase final {
MOZ_ASSERT(IsPositioned());
return IsPositioned() && mStart.GetContainer() == mEnd.GetContainer();
}
bool InAdjacentSiblings() const {
MOZ_ASSERT(IsPositioned());
return IsPositioned() &&
mStart.GetContainer()->GetNextSibling() == mEnd.GetContainer();
}
bool IsInContentNodes() const {
MOZ_ASSERT(IsPositioned());
return IsPositioned() && mStart.IsInContentNode() && mEnd.IsInContentNode();

Просмотреть файл

@ -936,60 +936,84 @@ class MOZ_STACK_CLASS SplitRangeOffResult final {
}
/**
* This is at right node of split at start point.
* The start boundary is at the right of split at split point. The end
* boundary is at right node of split at end point, i.e., the end boundary
* points out of the range to have been split off.
*/
constexpr const EditorDOMPoint& SplitPointAtStart() const {
return mSplitPointAtStart;
}
constexpr const EditorDOMRange& RangeRef() const { return mRange; }
/**
* This is at right node of split at end point. I.e., not in the range.
* This is after the range.
* Suggest caret position to aHTMLEditor.
*/
constexpr const EditorDOMPoint& SplitPointAtEnd() const {
return mSplitPointAtEnd;
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SuggestCaretPointTo(
const HTMLEditor& aHTMLEditor, const SuggestCaretOptions& aOptions) const;
/**
* IgnoreCaretPointSuggestion() should be called if the method does not want
* to use caret position recommended by this instance.
*/
void IgnoreCaretPointSuggestion() const { mHandledCaretPoint = true; }
bool HasCaretPointSuggestion() const { return mCaretPoint.IsSet(); }
constexpr EditorDOMPoint&& UnwrapCaretPoint() {
mHandledCaretPoint = true;
return std::move(mCaretPoint);
}
bool MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
const SuggestCaretOptions& aOptions) {
MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
MOZ_ASSERT(
!aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt));
if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
!mCaretPoint.IsSet()) {
return false;
}
aPointToPutCaret = UnwrapCaretPoint();
return true;
}
bool MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
const HTMLEditor& aHTMLEditor,
const SuggestCaretOptions& aOptions);
SplitRangeOffResult() = delete;
/**
* Constructor for success case.
*
* @param aTrackedRangeStart This should be at topmost right node
* child at start point if actually split
* there, or at start point to be tried
* to split. Note that if the method
* allows to run script after splitting
* at start point, the point should be
* tracked with AutoTrackDOMPoint.
* @param aTrackedRangeStart The range whose start is at topmost
* right node child at start point if
* actually split there, or at the point
* to be tried to split, and whose end is
* at topmost right node child at end point
* if actually split there, or at the point
* to be tried to split. Note that if the
* method allows to run script after
* splitting the range boundaries, they
* should be tracked with
* AutoTrackDOMRange.
* @param aSplitNodeResultAtStart Raw split node result at start point.
* @param aTrackedRangeEnd This should be at topmost right node
* child at end point if actually split
* here, or at end point to be tried to
* split. As same as aTrackedRangeStart,
* this value should be tracked while
* running some script.
* @param aSplitNodeResultAtEnd Raw split node result at start point.
*/
SplitRangeOffResult(EditorDOMPoint&& aTrackedRangeStart,
SplitRangeOffResult(EditorDOMRange&& aTrackedRange,
SplitNodeResult&& aSplitNodeResultAtStart,
EditorDOMPoint&& aTrackedRangeEnd,
SplitNodeResult&& aSplitNodeResultAtEnd)
: mSplitPointAtStart(aTrackedRangeStart),
mSplitPointAtEnd(aTrackedRangeEnd),
: mRange(std::move(aTrackedRange)),
mRv(NS_OK),
mHandled(aSplitNodeResultAtStart.Handled() ||
aSplitNodeResultAtEnd.Handled()) {
MOZ_ASSERT(mSplitPointAtStart.IsSet());
MOZ_ASSERT(mSplitPointAtEnd.IsSet());
MOZ_ASSERT(mRange.StartRef().IsSet());
MOZ_ASSERT(mRange.EndRef().IsSet());
MOZ_ASSERT(aSplitNodeResultAtStart.isOk());
MOZ_ASSERT(aSplitNodeResultAtEnd.isOk());
// The given results are created for creating this instance so that the
// caller may not need to handle with them. For making who taking the
// reposible clearer, we should move them into this constructor.
// responsible clearer, we should move them into this constructor.
SplitNodeResult splitNodeResultAtStart(std::move(aSplitNodeResultAtStart));
SplitNodeResult splitNodeResultAtEnd(std::move(aSplitNodeResultAtEnd));
splitNodeResultAtStart.IgnoreCaretPointSuggestion();
splitNodeResultAtEnd.IgnoreCaretPointSuggestion();
splitNodeResultAtStart.MoveCaretPointTo(
mCaretPoint, {SuggestCaret::OnlyIfHasSuggestion});
splitNodeResultAtEnd.MoveCaretPointTo(mCaretPoint,
{SuggestCaret::OnlyIfHasSuggestion});
}
explicit SplitRangeOffResult(nsresult aRv) : mRv(aRv), mHandled(false) {
@ -1002,8 +1026,7 @@ class MOZ_STACK_CLASS SplitRangeOffResult final {
SplitRangeOffResult& operator=(SplitRangeOffResult&& aOther) = default;
private:
EditorDOMPoint mSplitPointAtStart;
EditorDOMPoint mSplitPointAtEnd;
EditorDOMRange mRange;
// If you need to store previous and/or next node at start/end point,
// you might be able to use `SplitNodeResult::GetPreviousNode()` etc in the
@ -1011,9 +1034,14 @@ class MOZ_STACK_CLASS SplitRangeOffResult final {
// the node might have gone with another DOM tree mutation. So, be careful
// if you do it.
// The point which is a good point to put caret from point of view the
// splitter.
EditorDOMPoint mCaretPoint;
nsresult mRv;
bool mHandled;
bool mutable mHandledCaretPoint = false;
};
/******************************************************************************

Просмотреть файл

@ -886,20 +886,19 @@ class HTMLEditor final : public EditorBase,
/**
* SplitAncestorStyledInlineElementsAtRangeEdges() splits all ancestor inline
* elements in the block at both aStartPoint and aEndPoint if given style
* matches with some of them.
* elements in the block at aRange if given style matches with some of them.
*
* @param aStartPoint Start of range to split ancestor inline elements.
* @param aEndPoint End of range to split ancestor inline elements.
* @param aRange Ancestor inline elements of the start and end boundaries
* will be split.
* @param aProperty The style tag name which you want to split. Set
* nullptr if you want to split any styled elements.
* @param aAttribute Attribute name if aProperty has some styles like
* nsGkAtoms::font.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitRangeOffResult
SplitAncestorStyledInlineElementsAtRangeEdges(
const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint,
nsAtom* aProperty, nsAtom* aAttribute);
SplitAncestorStyledInlineElementsAtRangeEdges(const EditorDOMRange& aRange,
nsAtom* aProperty,
nsAtom* aAttribute);
/**
* SplitAncestorStyledInlineElementsAt() splits ancestor inline elements at

Просмотреть файл

@ -911,35 +911,27 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::SetInlinePropertyOnNode(
}
SplitRangeOffResult HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
const EditorDOMPoint& aStartOfRange, const EditorDOMPoint& aEndOfRange,
nsAtom* aProperty, nsAtom* aAttribute) {
const EditorDOMRange& aRange, nsAtom* aProperty, nsAtom* aAttribute) {
MOZ_ASSERT(IsEditActionDataAvailable());
if (NS_WARN_IF(!aStartOfRange.IsSet()) || NS_WARN_IF(!aEndOfRange.IsSet())) {
return SplitRangeOffResult(NS_ERROR_INVALID_ARG);
if (NS_WARN_IF(!aRange.IsPositioned())) {
return SplitRangeOffResult(NS_ERROR_FAILURE);
}
EditorDOMPoint startOfRange(aStartOfRange);
EditorDOMPoint endOfRange(aEndOfRange);
EditorDOMRange range(aRange);
// split any matching style nodes above the start of range
SplitNodeResult resultAtStart = [&]() MOZ_CAN_RUN_SCRIPT {
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &endOfRange);
AutoTrackDOMRange tracker(RangeUpdaterRef(), &range);
SplitNodeResult result = SplitAncestorStyledInlineElementsAt(
startOfRange, aProperty, aAttribute);
range.StartRef(), aProperty, aAttribute);
if (result.isErr()) {
NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
return SplitNodeResult(result.unwrapErr());
}
nsresult rv = result.SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
if (NS_FAILED(rv)) {
NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
return SplitNodeResult(rv);
}
tracker.FlushAndStopTracking();
if (result.Handled()) {
startOfRange = result.AtSplitPoint<EditorDOMPoint>();
auto startOfRange = result.AtSplitPoint<EditorDOMPoint>();
if (!startOfRange.IsSet()) {
result.IgnoreCaretPointSuggestion();
NS_WARNING(
@ -947,6 +939,7 @@ SplitRangeOffResult HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
"split point");
return SplitNodeResult(NS_ERROR_FAILURE);
}
range.SetStart(std::move(startOfRange));
}
return result;
}();
@ -956,22 +949,16 @@ SplitRangeOffResult HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
// second verse, same as the first...
SplitNodeResult resultAtEnd = [&]() MOZ_CAN_RUN_SCRIPT {
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &startOfRange);
SplitNodeResult result =
SplitAncestorStyledInlineElementsAt(endOfRange, aProperty, aAttribute);
AutoTrackDOMRange tracker(RangeUpdaterRef(), &range);
SplitNodeResult result = SplitAncestorStyledInlineElementsAt(
range.EndRef(), aProperty, aAttribute);
if (result.isErr()) {
NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
return SplitNodeResult(result.unwrapErr());
}
nsresult rv = result.SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
if (NS_FAILED(rv)) {
NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
return SplitNodeResult(rv);
}
tracker.FlushAndStopTracking();
if (result.Handled()) {
endOfRange = result.AtSplitPoint<EditorDOMPoint>();
auto endOfRange = result.AtSplitPoint<EditorDOMPoint>();
if (!endOfRange.IsSet()) {
result.IgnoreCaretPointSuggestion();
NS_WARNING(
@ -979,15 +966,17 @@ SplitRangeOffResult HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
"split point");
return SplitNodeResult(NS_ERROR_FAILURE);
}
range.SetEnd(std::move(endOfRange));
}
return result;
}();
if (resultAtEnd.isErr()) {
resultAtStart.IgnoreCaretPointSuggestion();
return SplitRangeOffResult(resultAtEnd.unwrapErr());
}
return SplitRangeOffResult(std::move(startOfRange), std::move(resultAtStart),
std::move(endOfRange), std::move(resultAtEnd));
return SplitRangeOffResult(std::move(range), std::move(resultAtStart),
std::move(resultAtEnd));
}
SplitNodeResult HTMLEditor::SplitAncestorStyledInlineElementsAt(
@ -2232,8 +2221,7 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(
// them as appropriate
SplitRangeOffResult splitRangeOffResult =
SplitAncestorStyledInlineElementsAtRangeEdges(
EditorDOMPoint(range->StartRef()),
EditorDOMPoint(range->EndRef()), MOZ_KnownLive(style.mProperty),
EditorDOMRange(range), MOZ_KnownLive(style.mProperty),
MOZ_KnownLive(style.mAttribute));
if (splitRangeOffResult.isErr()) {
NS_WARNING(
@ -2241,21 +2229,21 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(
"failed");
return splitRangeOffResult.unwrapErr();
}
// Ignore caret suggestion because of dontChangeMySelection.
splitRangeOffResult.IgnoreCaretPointSuggestion();
// XXX Modifying `range` means that we may modify ranges in `Selection`.
// Is this intentional? Note that the range may be not in
// `Selection` too. It seems that at least one of them is not
// an unexpected case.
const EditorDOMPoint& startOfRange(
splitRangeOffResult.SplitPointAtStart());
const EditorDOMPoint& endOfRange(splitRangeOffResult.SplitPointAtEnd());
if (NS_WARN_IF(!startOfRange.IsSet()) ||
NS_WARN_IF(!endOfRange.IsSet())) {
const EditorDOMRange& splitRange = splitRangeOffResult.RangeRef();
if (NS_WARN_IF(!splitRange.IsPositioned())) {
continue;
}
nsresult rv = range->SetStartAndEnd(startOfRange.ToRawRangeBoundary(),
endOfRange.ToRawRangeBoundary());
nsresult rv =
range->SetStartAndEnd(splitRange.StartRef().ToRawRangeBoundary(),
splitRange.EndRef().ToRawRangeBoundary());
// Note that modifying a range in `Selection` may run script so that
// we might have been destroyed here.
if (NS_WARN_IF(Destroyed())) {
@ -2268,34 +2256,37 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(
// Collect editable nodes which are entirely contained in the range.
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
if (startOfRange.GetContainer() == endOfRange.GetContainer() &&
startOfRange.IsInTextNode()) {
if (!EditorUtils::IsEditableContent(*startOfRange.ContainerAs<Text>(),
EditorType::HTML)) {
if (splitRange.InSameContainer() &&
splitRange.StartRef().IsInTextNode()) {
if (!EditorUtils::IsEditableContent(
*splitRange.StartRef().ContainerAs<Text>(),
EditorType::HTML)) {
continue;
}
arrayOfContents.AppendElement(*startOfRange.ContainerAs<Text>());
} else if (startOfRange.IsInTextNode() && endOfRange.IsInTextNode() &&
startOfRange.GetContainer()->GetNextSibling() ==
endOfRange.GetContainer()) {
if (EditorUtils::IsEditableContent(*startOfRange.ContainerAs<Text>(),
EditorType::HTML)) {
arrayOfContents.AppendElement(*startOfRange.ContainerAs<Text>());
}
if (EditorUtils::IsEditableContent(*endOfRange.ContainerAs<Text>(),
EditorType::HTML)) {
arrayOfContents.AppendElement(*endOfRange.ContainerAs<Text>());
}
if (arrayOfContents.IsEmpty()) {
arrayOfContents.AppendElement(
*splitRange.StartRef().ContainerAs<Text>());
} else if (splitRange.IsInTextNodes() &&
splitRange.InAdjacentSiblings()) {
// Adjacent siblings are in a same element, so the editable state of
// both text nodes are always same.
if (!EditorUtils::IsEditableContent(
*splitRange.StartRef().ContainerAs<Text>(),
EditorType::HTML)) {
continue;
}
arrayOfContents.AppendElement(
*splitRange.StartRef().ContainerAs<Text>());
arrayOfContents.AppendElement(
*splitRange.EndRef().ContainerAs<Text>());
} else {
// Append first node if it's a text node but selected not entirely.
if (startOfRange.IsInTextNode() &&
!startOfRange.IsStartOfContainer() &&
EditorUtils::IsEditableContent(*startOfRange.ContainerAs<Text>(),
EditorType::HTML)) {
arrayOfContents.AppendElement(*startOfRange.ContainerAs<Text>());
if (splitRange.StartRef().IsInTextNode() &&
!splitRange.StartRef().IsStartOfContainer() &&
EditorUtils::IsEditableContent(
*splitRange.StartRef().ContainerAs<Text>(),
EditorType::HTML)) {
arrayOfContents.AppendElement(
*splitRange.StartRef().ContainerAs<Text>());
}
// Append all entirely selected nodes.
ContentSubtreeIterator subtreeIter;
@ -2313,11 +2304,13 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(
}
}
// Append last node if it's a text node but selected not entirely.
if (startOfRange.GetContainer() != endOfRange.GetContainer() &&
endOfRange.IsInTextNode() && !endOfRange.IsEndOfContainer() &&
EditorUtils::IsEditableContent(*endOfRange.ContainerAs<Text>(),
EditorType::HTML)) {
arrayOfContents.AppendElement(*endOfRange.ContainerAs<Text>());
if (!splitRange.InSameContainer() &&
splitRange.EndRef().IsInTextNode() &&
!splitRange.EndRef().IsEndOfContainer() &&
EditorUtils::IsEditableContent(
*splitRange.EndRef().ContainerAs<Text>(), EditorType::HTML)) {
arrayOfContents.AppendElement(
*splitRange.EndRef().ContainerAs<Text>());
}
}
@ -2385,11 +2378,11 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(
// don't join text nodes when removing a style. Therefore, there
// may be multiple text nodes as adjacent siblings. That's the
// reason why we need to handle text nodes in this loop.
uint32_t startOffset = content == startOfRange.GetContainer()
? startOfRange.Offset()
uint32_t startOffset = content == splitRange.StartRef().GetContainer()
? splitRange.StartRef().Offset()
: 0;
uint32_t endOffset = content == endOfRange.GetContainer()
? endOfRange.Offset()
uint32_t endOffset = content == splitRange.EndRef().GetContainer()
? splitRange.EndRef().Offset()
: content->Length();
nsresult rv = SetInlinePropertyOnTextNode(
MOZ_KnownLive(*content->AsText()), startOffset, endOffset,

Просмотреть файл

@ -399,14 +399,21 @@ class MOZ_STACK_CLASS AutoTrackDOMRange final {
mStartPointTracker.emplace(aRangeUpdater, &mStartPoint);
mEndPointTracker.emplace(aRangeUpdater, &mEndPoint);
}
~AutoTrackDOMRange() {
if (!mRangeRefPtr && !mRangeOwningNonNull) {
// The destructor of the trackers will update automatically.
~AutoTrackDOMRange() { FlushAndStopTracking(); }
void FlushAndStopTracking() {
if (!mStartPointTracker || !mEndPointTracker) {
return;
}
// Otherwise, destroy them now.
mStartPointTracker.reset();
mEndPointTracker.reset();
if (!mRangeRefPtr && !mRangeOwningNonNull) {
// This must be created with EditorDOMRange or EditorDOMPoints. In the
// cases, destroying mStartPointTracker and mEndPointTracker has done
// everything which we need to do.
return;
}
// Otherwise, update the DOM ranges by ourselves.
if (mRangeRefPtr) {
(*mRangeRefPtr)
->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),