/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include"SelectionState.h"
#include"AutoClonedRangeArray.h"// for AutoClonedRangeArray #include"EditorUtils.h"// for EditorUtils #include"EditorLineBreak.h"// for EditorLineBreak #include"HTMLEditHelpers.h"// for DeleteRangeResult
#include"ErrorList.h" #include"mozilla/Assertions.h"// for MOZ_ASSERT, etc. #include"mozilla/IntegerRange.h"// for IntegerRange #include"mozilla/Likely.h"// For MOZ_LIKELY and MOZ_UNLIKELY #include"mozilla/RangeUtils.h"// for RangeUtils #include"mozilla/dom/RangeBinding.h" #include"mozilla/dom/Selection.h"// for Selection #include"nsAString.h"// for nsAString::Length #include"nsCycleCollectionParticipant.h" #include"nsDebug.h"// for NS_WARNING, etc. #include"nsError.h"// for NS_OK, etc. #include"nsIContent.h"// for nsIContent #include"nsISupportsImpl.h"// for nsRange::Release #include"nsRange.h"// for nsRange
/****************************************************************************** * mozilla::SelectionState * * Class for recording selection info. Stores selection as collection of * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store * ranges since dom gravity will possibly change the ranges.
******************************************************************************/
SelectionState::SelectionState(const AutoClonedSelectionRangeArray& aRanges)
: mDirection(aRanges.GetDirection()) {
mArray.SetCapacity(aRanges.Ranges().Length()); for (const OwningNonNull<nsRange>& range : aRanges.Ranges()) {
RefPtr<RangeItem> rangeItem = new RangeItem();
rangeItem->StoreRange(range);
mArray.AppendElement(std::move(rangeItem));
}
}
void SelectionState::SaveSelection(Selection& aSelection) { // if we need more items in the array, new them if (mArray.Length() < aSelection.RangeCount()) { for (uint32_t i = mArray.Length(); i < aSelection.RangeCount(); i++) {
mArray.AppendElement();
mArray[i] = new RangeItem();
}
} elseif (mArray.Length() > aSelection.RangeCount()) { // else if we have too many, delete them
mArray.TruncateLength(aSelection.RangeCount());
}
// now store the selection ranges const uint32_t rangeCount = aSelection.RangeCount(); for (const uint32_t i : IntegerRange(rangeCount)) {
MOZ_ASSERT(aSelection.RangeCount() == rangeCount); const nsRange* range = aSelection.GetRangeAt(i);
MOZ_ASSERT(range); if (MOZ_UNLIKELY(NS_WARN_IF(!range))) { continue;
}
mArray[i]->StoreRange(*range);
}
mDirection = aSelection.GetDirection();
}
nsresult SelectionState::RestoreSelection(Selection& aSelection) { // clear out selection
IgnoredErrorResult ignoredError;
aSelection.RemoveAllRanges(ignoredError);
NS_WARNING_ASSERTION(!ignoredError.Failed(), "Selection::RemoveAllRanges() failed, but ignored");
aSelection.SetDirection(mDirection);
ErrorResult error; const CopyableAutoTArray<RefPtr<RangeItem>, 10> rangeItems(mArray); for (const RefPtr<RangeItem>& rangeItem : rangeItems) {
RefPtr<nsRange> range = rangeItem->GetRange(); if (!range) {
NS_WARNING("RangeItem::GetRange() failed"); return NS_ERROR_FAILURE;
}
aSelection.AddRangeAndSelectFramesAndNotifyListeners(*range, error); if (error.Failed()) {
NS_WARNING( "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed"); return error.StealNSResult();
}
} return NS_OK;
}
void SelectionState::ApplyTo(AutoClonedSelectionRangeArray& aRanges) {
aRanges.RemoveAllRanges();
aRanges.SetDirection(mDirection); for (const RefPtr<RangeItem>& rangeItem : mArray) {
RefPtr<nsRange> range = rangeItem->GetRange(); if (MOZ_UNLIKELY(!range)) { continue;
}
aRanges.Ranges().AppendElement(std::move(range));
}
}
bool SelectionState::Equals(const SelectionState& aOther) const { if (mArray.Length() != aOther.mArray.Length()) { returnfalse;
} if (mArray.IsEmpty()) { returnfalse; // XXX Why?
} if (mDirection != aOther.mDirection) { returnfalse;
}
for (uint32_t i : IntegerRange(mArray.Length())) { if (NS_WARN_IF(!mArray[i]) || NS_WARN_IF(!aOther.mArray[i]) ||
!mArray[i]->Equals(*aOther.mArray[i])) { returnfalse;
}
} // if we got here, they are equal returntrue;
}
/****************************************************************************** * mozilla::RangeUpdater * * Class for updating nsRanges in response to editor actions.
******************************************************************************/
RangeUpdater::RangeUpdater() : mLocked(false) {}
void RangeUpdater::RegisterRangeItem(RangeItem& aRangeItem) { if (mArray.Contains(&aRangeItem)) {
NS_ERROR("tried to register an already registered range"); return; // don't register it again. It would get doubly adjusted.
}
mArray.AppendElement(&aRangeItem);
}
void RangeUpdater::DropRangeItem(RangeItem& aRangeItem) {
NS_WARNING_ASSERTION(
mArray.Contains(&aRangeItem), "aRangeItem is not in the range, but tried to removed from it");
mArray.RemoveElement(&aRangeItem);
}
void RangeUpdater::RegisterSelectionState(SelectionState& aSelectionState) { for (RefPtr<RangeItem>& rangeItem : aSelectionState.mArray) { if (NS_WARN_IF(!rangeItem)) { continue;
}
RegisterRangeItem(*rangeItem);
}
}
void RangeUpdater::DropSelectionState(SelectionState& aSelectionState) { for (RefPtr<RangeItem>& rangeItem : aSelectionState.mArray) { if (NS_WARN_IF(!rangeItem)) { continue;
}
DropRangeItem(*rangeItem);
}
}
// gravity methods:
template <typename PT, typename CT>
nsresult RangeUpdater::SelAdjCreateNode( const EditorDOMPointBase<PT, CT>& aPoint) { if (mLocked) { // lock set by Will/DidReplaceParent, etc... return NS_OK;
} if (mArray.IsEmpty()) { return NS_OK;
}
if (NS_WARN_IF(!aPoint.IsSetAndValid())) { return NS_ERROR_INVALID_ARG;
}
for (RefPtr<RangeItem>& rangeItem : mArray) { if (NS_WARN_IF(!rangeItem)) { return NS_ERROR_FAILURE;
} if (rangeItem->mStartContainer == aPoint.GetContainer() &&
rangeItem->mStartOffset > aPoint.Offset()) {
rangeItem->mStartOffset++;
} if (rangeItem->mEndContainer == aPoint.GetContainer() &&
rangeItem->mEndOffset > aPoint.Offset()) {
rangeItem->mEndOffset++;
}
} return NS_OK;
}
void RangeUpdater::SelAdjDeleteNode(nsINode& aNodeToDelete) { if (mLocked) { // lock set by Will/DidReplaceParent, etc... return;
}
if (mArray.IsEmpty()) { return;
}
EditorRawDOMPoint atNodeToDelete(&aNodeToDelete);
NS_ASSERTION(atNodeToDelete.IsSetAndValid(), "aNodeToDelete must be an orphan node or this is called " "during mutation"); // check for range endpoints that are after aNodeToDelete and in the same // parent for (RefPtr<RangeItem>& rangeItem : mArray) {
MOZ_ASSERT(rangeItem);
// check for range endpoints that are in aNodeToDelete if (rangeItem->mStartContainer == &aNodeToDelete) {
rangeItem->mStartContainer = atNodeToDelete.GetContainer();
rangeItem->mStartOffset = atNodeToDelete.Offset();
} if (rangeItem->mEndContainer == &aNodeToDelete) {
rangeItem->mEndContainer = atNodeToDelete.GetContainer();
rangeItem->mEndOffset = atNodeToDelete.Offset();
}
// check for range endpoints that are in descendants of aNodeToDelete bool updateEndBoundaryToo = false; if (EditorUtils::IsDescendantOf(*rangeItem->mStartContainer,
aNodeToDelete)) {
updateEndBoundaryToo =
rangeItem->mStartContainer == rangeItem->mEndContainer;
rangeItem->mStartContainer = atNodeToDelete.GetContainer();
rangeItem->mStartOffset = atNodeToDelete.Offset();
}
// avoid having to call IsDescendantOf() for common case of range startnode // == range endnode. if (updateEndBoundaryToo ||
EditorUtils::IsDescendantOf(*rangeItem->mEndContainer, aNodeToDelete)) {
rangeItem->mEndContainer = atNodeToDelete.GetContainer();
rangeItem->mEndOffset = atNodeToDelete.Offset();
}
}
}
nsresult RangeUpdater::SelAdjSplitNode(nsIContent& aOriginalContent,
uint32_t aSplitOffset,
nsIContent& aNewContent) { if (mLocked) { // lock set by Will/DidReplaceParent, etc... return NS_OK;
}
if (mArray.IsEmpty()) { return NS_OK;
}
EditorRawDOMPoint atNewNode(&aNewContent); if (NS_WARN_IF(!atNewNode.IsSetAndValid())) { return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
uint32_t& aOffset) -> void { if (aContainer == atNewNode.GetContainer()) { // When we create a right node, we insert it after the left node. // In this case, // - `{}<left/>` should become `{}<left/><right/>` (0 -> 0) // - `<left/>{}` should become `<left/><right/>{}` (1 -> 2) // - `{<left/>}` should become `{<left/><right/>}` (0 -> 0, 1 -> 2} // Therefore, we need to increate the offset only when the offset equals // or is larger than the offset at the right node. if (aOffset >= atNewNode.Offset()) {
aOffset++;
}
} // If point is in the range which are moved from aOriginalContent to // aNewContent, we need to change its container to aNewContent and may need // to adjust the offset. If point is in the range which are not moved from // aOriginalContent, we may need to adjust the offset. if (aContainer != &aOriginalContent) { return;
} if (aOffset >= aSplitOffset) {
aContainer = &aNewContent;
aOffset -= aSplitOffset;
}
};
for (RefPtr<RangeItem>& rangeItem : mArray) { if (NS_WARN_IF(!rangeItem)) { return NS_ERROR_FAILURE;
}
AdjustDOMPoint(rangeItem->mStartContainer, rangeItem->mStartOffset);
AdjustDOMPoint(rangeItem->mEndContainer, rangeItem->mEndOffset);
} return NS_OK;
}
nsresult RangeUpdater::SelAdjJoinNodes( const EditorRawDOMPoint& aStartOfRightContent, const nsIContent& aRemovedContent, const EditorDOMPoint& aOldPointAtRightContent) {
MOZ_ASSERT(aStartOfRightContent.IsSetAndValid());
MOZ_ASSERT(aOldPointAtRightContent.IsSet()); // Invalid point in most cases
MOZ_ASSERT(aOldPointAtRightContent.HasOffset());
if (mLocked) { // lock set by Will/DidReplaceParent, etc... return NS_OK;
}
if (mArray.IsEmpty()) { return NS_OK;
}
auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
uint32_t& aOffset) -> void { // FYI: Typically, containers of aOldPointAtRightContent and // aStartOfRightContent are same. They are different when one of the // node was moved to somewhere and they are joined by undoing splitting // a node. if (aContainer == &aRemovedContent) { // If the point is in the removed content, move the point to the new // point in the joined node. If left node content is moved into // right node, the offset should be same. Otherwise, we need to advance // the offset to length of the removed content.
aContainer = aStartOfRightContent.GetContainer();
aOffset += aStartOfRightContent.Offset();
} // TODO: If aOldPointAtRightContent.GetContainer() was in aRemovedContent, // we fail to adjust container and offset here because we need to // make point to where aRemoveContent was. However, collecting all // ancestors of the right content may be expensive. What's the best // approach to fix this? elseif (aContainer == aOldPointAtRightContent.GetContainer()) { // If the point is in common parent of joined content nodes and it // pointed after the right content node, decrease the offset. if (aOffset > aOldPointAtRightContent.Offset()) {
aOffset--;
} // If it pointed the right content node, adjust it to point ex-first // content of the right node. elseif (aOffset == aOldPointAtRightContent.Offset()) {
aContainer = aStartOfRightContent.GetContainer();
aOffset = aStartOfRightContent.Offset();
}
}
};
for (RefPtr<RangeItem>& rangeItem : mArray) { if (NS_WARN_IF(!rangeItem)) { return NS_ERROR_FAILURE;
}
AdjustDOMPoint(rangeItem->mStartContainer, rangeItem->mStartOffset);
AdjustDOMPoint(rangeItem->mEndContainer, rangeItem->mEndOffset);
}
return NS_OK;
}
void RangeUpdater::SelAdjReplaceText(const Text& aTextNode, uint32_t aOffset,
uint32_t aReplacedLength,
uint32_t aInsertedLength) { if (mLocked) { // lock set by Will/DidReplaceParent, etc... return;
}
// First, adjust selection for insertion because when offset is in the // replaced range, it's adjusted to aOffset and never modified by the // insertion if we adjust selection for deletion first.
SelAdjInsertText(aTextNode, aOffset, aInsertedLength);
// Then, adjust selection for deletion.
SelAdjDeleteText(aTextNode, aOffset, aReplacedLength);
}
void RangeUpdater::SelAdjInsertText(const Text& aTextNode, uint32_t aOffset,
uint32_t aInsertedLength) { if (mLocked) { // lock set by Will/DidReplaceParent, etc... return;
}
for (RefPtr<RangeItem>& rangeItem : mArray) {
MOZ_ASSERT(rangeItem);
/****************************************************************************** * mozilla::RangeItem * * Helper struct for SelectionState. This stores range endpoints.
******************************************************************************/
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.