Bug 1810403: Allow `nsRange`s to be in multiple `Selection`s. r=masayuki

The Custom Highlight API allows a use case where a `Range` of a `Highlight`
is also used as `Selection`. Due to the decision to use the `Selection` mechanism
to display `Highlight`s, a `Range` can be part of several `Selection`s.
Since the `Range` has a pointer to its associated `Selection`
to notify about changes, this must be adapted to allow several `Selections`.

As a tradeoff of performance and memory usage, the `Selection`s are stored
as `mozilla::LinkedList`. A helper class `mozilla::SelectionListWrapper`
was implemented to allow `Selection`s to be in multiple of these lists
and without having to be derived from `LinkedListElement<T>`.

To simplify usage of the list, the use case  "does this range belong to Selection x?"
is wrapped into the convenience method`IsInSelection(Selection&)`;
The method previously named like this was renamed to `IsInAnySelection()`
to be named more precisely.

Registering and unregistering of the closest common inclusive ancestor
of the `Range` is done when the first `Selection` is registered and
the last `Selection` is unregistered.

Differential Revision: https://phabricator.services.mozilla.com/D169597
This commit is contained in:
Jan-Niklas Jaeschke 2023-02-21 12:25:28 +00:00
Родитель 5f6910e75e
Коммит 2243494a74
11 изменённых файлов: 210 добавлений и 94 удалений

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

@ -966,11 +966,10 @@ nsresult Selection::AddRangesForUserSelectableNodes(
GetDirection() == eDirPrevious ? 0 : rangesToAdd.Length() - 1;
for (size_t i = 0; i < rangesToAdd.Length(); ++i) {
Maybe<size_t> index;
const RefPtr<Selection> selection{this};
// `MOZ_KnownLive` needed because of broken static analysis
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253#c1).
nsresult rv = mStyledRanges.MaybeAddRangeAndTruncateOverlaps(
MOZ_KnownLive(rangesToAdd[i]), &index, *selection);
MOZ_KnownLive(rangesToAdd[i]), &index);
NS_ENSURE_SUCCESS(rv, rv);
if (i == newAnchorFocusIndex) {
*aOutIndex = index;
@ -1007,13 +1006,11 @@ nsresult Selection::AddRangesForSelectableNodes(
aDispatchSelectstartEvent);
}
const RefPtr<Selection> selection{this};
return mStyledRanges.MaybeAddRangeAndTruncateOverlaps(aRange, aOutIndex,
*selection);
return mStyledRanges.MaybeAddRangeAndTruncateOverlaps(aRange, aOutIndex);
}
nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
nsRange* aRange, Maybe<size_t>* aOutIndex, Selection& aSelection) {
nsRange* aRange, Maybe<size_t>* aOutIndex) {
MOZ_ASSERT(aRange);
MOZ_ASSERT(aRange->IsPositioned());
MOZ_ASSERT(aOutIndex);
@ -1024,7 +1021,7 @@ nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
mRanges.AppendElement(StyledRange(aRange));
aRange->RegisterSelection(aSelection);
aRange->RegisterSelection(MOZ_KnownLive(mSelection));
aOutIndex->emplace(0u);
return NS_OK;
@ -1064,7 +1061,7 @@ nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
mRanges.InsertElementAt(startIndex, StyledRange(aRange));
aRange->RegisterSelection(aSelection);
aRange->RegisterSelection(MOZ_KnownLive(mSelection));
aOutIndex->emplace(startIndex);
return NS_OK;
}
@ -1083,7 +1080,7 @@ nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
// Remove all the overlapping ranges
for (size_t i = startIndex; i < endIndex; ++i) {
mRanges[i].mRange->UnregisterSelection();
mRanges[i].mRange->UnregisterSelection(mSelection);
}
mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
@ -1105,7 +1102,7 @@ nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps(
mRanges.InsertElementsAt(startIndex, temp);
for (uint32_t i = 0; i < temp.Length(); ++i) {
MOZ_KnownLive(temp[i].mRange)->RegisterSelection(aSelection);
MOZ_KnownLive(temp[i].mRange)->RegisterSelection(MOZ_KnownLive(mSelection));
// `MOZ_KnownLive` is required because of
// https://bugzilla.mozilla.org/show_bug.cgi?id=1622253.
}
@ -1131,7 +1128,7 @@ nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection(
if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR;
mRanges.RemoveElementAt(idx);
aRange.UnregisterSelection();
aRange.UnregisterSelection(mSelection);
return NS_OK;
}
nsresult Selection::RemoveCollapsedRanges() {
@ -1470,8 +1467,8 @@ nsresult Selection::SelectFramesOfInclusiveDescendantsOfContent(
void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) {
for (size_t i = 0; i < mStyledRanges.Length(); ++i) {
nsRange* range = mStyledRanges.mRanges[i].mRange;
MOZ_ASSERT(range->IsInSelection());
SelectFrames(aPresContext, range, range->IsInSelection());
MOZ_ASSERT(range->IsInAnySelection());
SelectFrames(aPresContext, range, range->IsInAnySelection());
}
}
@ -1789,7 +1786,7 @@ void Selection::SetAncestorLimiter(nsIContent* aLimiter) {
void Selection::StyledRanges::UnregisterSelection() {
uint32_t count = mRanges.Length();
for (uint32_t i = 0; i < count; ++i) {
mRanges[i].mRange->UnregisterSelection();
mRanges[i].mRange->UnregisterSelection(mSelection);
}
}
@ -1946,19 +1943,16 @@ void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange,
Document* aDocument,
ErrorResult& aRv) {
// If the given range is part of another Selection, we need to clone the
// range first.
RefPtr<nsRange> range;
if (aRange.IsInSelection()) {
RefPtr<nsRange> range = &aRange;
if (aRange.IsInAnySelection()) {
if (aRange.IsInSelection(*this)) {
// If we already have the range, we don't need to handle this.
if (aRange.GetSelection() == this) {
return;
}
// Because of performance reason, when there is a cached range, let's use
// it. Otherwise, clone the range.
if (mSelectionType != SelectionType::eNormal &&
mSelectionType != SelectionType::eHighlight) {
range = aRange.CloneRange();
} else {
range = &aRange;
}
}
nsINode* rangeRoot = range->GetRoot();

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

@ -814,6 +814,7 @@ class Selection final : public nsSupportsWeakReference,
void Disconnect();
struct StyledRanges {
explicit StyledRanges(Selection& aSelection) : mSelection(aSelection) {}
void Clear();
StyledRange* FindRangeData(nsRange* aRange);
@ -871,8 +872,8 @@ class Selection final : public nsSupportsWeakReference,
* it. Hence it'll always be in [0, mRanges.Length()).
* This is nothing only when the method returns an error.
*/
MOZ_CAN_RUN_SCRIPT nsresult MaybeAddRangeAndTruncateOverlaps(
nsRange* aRange, Maybe<size_t>* aOutIndex, Selection& aSelection);
MOZ_CAN_RUN_SCRIPT nsresult
MaybeAddRangeAndTruncateOverlaps(nsRange* aRange, Maybe<size_t>* aOutIndex);
/**
* GetCommonEditingHost() returns common editing host of all
@ -929,9 +930,11 @@ class Selection final : public nsSupportsWeakReference,
// a possible solution, allowing the calculation of the overlap interval in
// O(log n) time, though this would require rebalancing and other overhead.
Elements mRanges;
Selection& mSelection;
};
StyledRanges mStyledRanges;
StyledRanges mStyledRanges{*this};
RefPtr<nsRange> mAnchorFocusRange;
RefPtr<nsFrameSelection> mFrameSelection;

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

@ -343,7 +343,6 @@ bool nsINode::IsSelected(const uint32_t aStartOffset,
// Collect the selection objects for potential ranges.
nsTHashSet<Selection*> ancestorSelections;
Selection* prevSelection = nullptr;
for (; n; n = GetClosestCommonInclusiveAncestorForRangeInSelection(
n->GetParentNode())) {
const LinkedList<nsRange>* ranges =
@ -352,14 +351,12 @@ bool nsINode::IsSelected(const uint32_t aStartOffset,
continue;
}
for (const nsRange* range : *ranges) {
MOZ_ASSERT(range->IsInSelection(),
"Why is this range registeed with a node?");
MOZ_ASSERT(range->IsInAnySelection(),
"Why is this range registered with a node?");
// Looks like that IsInSelection() assert fails sometimes...
if (range->IsInSelection()) {
Selection* selection = range->GetSelection();
if (prevSelection != selection) {
prevSelection = selection;
ancestorSelections.Insert(selection);
if (range->IsInAnySelection()) {
for (const auto* selectionWrapper : range->GetSelections()) {
ancestorSelections.Insert(selectionWrapper->Get());
}
}
}

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

@ -50,6 +50,11 @@
using namespace mozilla;
using namespace mozilla::dom;
SelectionListWrapper::SelectionListWrapper(Selection* aSelection)
: mSelection(aSelection) {}
NS_IMPL_CYCLE_COLLECTION(SelectionListWrapper)
Selection* SelectionListWrapper::Get() const { return mSelection; }
template already_AddRefed<nsRange> nsRange::Create(
const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
ErrorResult& aRv);
@ -129,7 +134,7 @@ static void InvalidateAllFrames(nsINode* aNode) {
nsTArray<RefPtr<nsRange>>* nsRange::sCachedRanges = nullptr;
nsRange::~nsRange() {
NS_ASSERTION(!IsInSelection(), "deleting nsRange that is in use");
NS_ASSERTION(!IsInAnySelection(), "deleting nsRange that is in use");
// we want the side effects (releases and list removals)
DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
@ -141,7 +146,8 @@ nsRange::nsRange(nsINode* aNode)
mNextStartRef(nullptr),
mNextEndRef(nullptr) {
// printf("Size of nsRange: %zu\n", sizeof(nsRange));
static_assert(sizeof(nsRange) <= 208,
static_assert(sizeof(nsRange) <= 216,
"nsRange size shouldn't be increased as far as possible");
}
@ -188,6 +194,7 @@ NS_INTERFACE_MAP_END_INHERITING(AbstractRange)
NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsRange, AbstractRange)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelections);
// We _could_ just rely on Reset() to
// UnregisterClosestCommonInclusiveAncestor(), but it wouldn't know we're
// calling it from Unlink and so would do more work than it really needs to.
@ -204,6 +211,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsRange, AbstractRange)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelections)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsRange, AbstractRange)
@ -259,7 +267,8 @@ static void UnmarkDescendants(nsINode* aNode) {
void nsRange::RegisterClosestCommonInclusiveAncestor(nsINode* aNode) {
MOZ_ASSERT(aNode, "bad arg");
MOZ_DIAGNOSTIC_ASSERT(IsInSelection(), "registering range not in selection");
MOZ_DIAGNOSTIC_ASSERT(IsInAnySelection(),
"registering range not in selection");
mRegisteredClosestCommonInclusiveAncestor = aNode;
@ -437,7 +446,7 @@ void nsRange::CharacterDataChanged(nsIContent* aContent,
}
bool isCommonAncestor =
IsInSelection() && mStart.Container() == mEnd.Container();
IsInAnySelection() && mStart.Container() == mEnd.Container();
if (isCommonAncestor) {
UnregisterClosestCommonInclusiveAncestor(mStart.Container(), false);
RegisterClosestCommonInclusiveAncestor(newStart.Container());
@ -489,7 +498,7 @@ void nsRange::CharacterDataChanged(nsIContent* aContent,
newEnd = {aInfo.mDetails->mNextSibling, newEndOffset};
bool isCommonAncestor =
IsInSelection() && mStart.Container() == mEnd.Container();
IsInAnySelection() && mStart.Container() == mEnd.Container();
if (isCommonAncestor && !newStart.Container()) {
// The split occurs inside the range.
UnregisterClosestCommonInclusiveAncestor(mStart.Container(), false);
@ -557,7 +566,7 @@ void nsRange::ContentAppended(nsIContent* aFirstNewContent) {
nsINode* container = aFirstNewContent->GetParentNode();
MOZ_ASSERT(container);
if (container->IsMaybeSelected() && IsInSelection()) {
if (container->IsMaybeSelected() && IsInAnySelection()) {
nsINode* child = aFirstNewContent;
while (child) {
if (!child
@ -840,19 +849,61 @@ bool nsRange::IntersectsNode(nsINode& aNode, ErrorResult& aRv) {
return false;
}
/**
* @brief Helper class that creates a local copy of `nsRange::mSelections`.
*
* This class uses the RAII principle to create a local copy of
* `nsRange::mSelections`, which is safely iterable while modifications may
* occur on the original.
* When going out of scope, the local copy is being deleted.
*/
class MOZ_RAII SelectionListLocalCopy final {
public:
explicit SelectionListLocalCopy(
mozilla::LinkedList<RefPtr<SelectionListWrapper>>& aSelectionList) {
for (const auto* elem : aSelectionList) {
mSelectionList.insertBack(new SelectionListWrapper(elem->Get()));
}
}
mozilla::LinkedList<RefPtr<SelectionListWrapper>>& Get() {
return mSelectionList;
}
~SelectionListLocalCopy() { mSelectionList.clear(); }
private:
mozilla::LinkedList<RefPtr<SelectionListWrapper>> mSelectionList;
};
void nsRange::NotifySelectionListenersAfterRangeSet() {
if (mSelection) {
if (!mSelections.isEmpty()) {
// Our internal code should not move focus with using this instance while
// it's calling Selection::NotifySelectionListeners() which may move focus
// or calls selection listeners. So, let's set mCalledByJS to false here
// since non-*JS() methods don't set it to false.
AutoCalledByJSRestore calledByJSRestorer(*this);
mCalledByJS = false;
// Be aware, this range may be modified or stop being a range for selection
// after this call. Additionally, the selection instance may have gone.
RefPtr<Selection> selection = mSelection.get();
// Notify all Selections. This may modify the range,
// remove it from the selection, or the selection itself may have gone after
// the call. Also, new selections may be added.
// To ensure that listeners are notified for all *current* selections,
// create a copy of the list of selections and use that for iterating. This
// way selections can be added or removed safely during iteration.
// To save allocation cost, the copy is only created if there is more than
// one Selection present (which will barely ever be the case).
if (mSelections.getFirst() != mSelections.getLast()) {
SelectionListLocalCopy copiedSelections{mSelections};
for (const auto* selectionWrapper : copiedSelections.Get()) {
RefPtr<Selection> selection = selectionWrapper->Get();
selection->NotifySelectionListeners(calledByJSRestorer.SavedValue());
}
} else {
RefPtr<Selection> selection = mSelections.getFirst()->Get();
selection->NotifySelectionListeners(calledByJSRestorer.SavedValue());
}
}
}
/******************************************************
@ -929,7 +980,7 @@ void nsRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
bool checkCommonAncestor =
(mStart.Container() != aStartBoundary.Container() ||
mEnd.Container() != aEndBoundary.Container()) &&
IsInSelection() && !aNotInsertedYet;
IsInAnySelection() && !aNotInsertedYet;
// GetClosestCommonInclusiveAncestor is unreliable while we're unlinking
// (could return null if our start/end have already been unlinked), so make
@ -948,7 +999,7 @@ void nsRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
RegisterClosestCommonInclusiveAncestor(newCommonAncestor);
} else {
MOZ_DIAGNOSTIC_ASSERT(!mIsPositioned, "unexpected disconnected nodes");
mSelection = nullptr;
mSelections.clear();
MOZ_DIAGNOSTIC_ASSERT(
!mRegisteredClosestCommonInclusiveAncestor,
"How can we have a registered common ancestor when we "
@ -970,42 +1021,48 @@ void nsRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
// the world could be observed by a selection listener while the range was in
// an invalid state. So we run it off of a script runner to ensure it runs
// after the mutation observers have finished running.
if (mSelection) {
if (!mSelections.isEmpty()) {
nsContentUtils::AddScriptRunner(
NewRunnableMethod("NotifySelectionListenersAfterRangeSet", this,
&nsRange::NotifySelectionListenersAfterRangeSet));
}
}
void nsRange::RegisterSelection(Selection& aSelection) {
// A range can belong to at most one Selection instance.
MOZ_ASSERT(!mSelection);
bool nsRange::IsInSelection(const Selection& aSelection) const {
for (const auto* selectionWrapper : mSelections) {
if (selectionWrapper->Get() == &aSelection) {
return true;
}
}
return false;
}
if (mSelection == &aSelection) {
void nsRange::RegisterSelection(Selection& aSelection) {
if (IsInSelection(aSelection)) {
return;
}
// Extra step in case our parent failed to ensure the above precondition.
if (mSelection) {
const RefPtr<nsRange> range{this};
const RefPtr<Selection> selection{mSelection};
selection->RemoveRangeAndUnselectFramesAndNotifyListeners(*range,
IgnoreErrors());
}
mSelection = &aSelection;
bool isFirstSelection = mSelections.isEmpty();
mSelections.insertBack(new SelectionListWrapper(&aSelection));
if (isFirstSelection && !mRegisteredClosestCommonInclusiveAncestor) {
nsINode* commonAncestor = GetClosestCommonInclusiveAncestor();
MOZ_ASSERT(commonAncestor, "unexpected disconnected nodes");
RegisterClosestCommonInclusiveAncestor(commonAncestor);
}
}
Selection* nsRange::GetSelection() const { return mSelection; }
const mozilla::LinkedList<RefPtr<mozilla::SelectionListWrapper>>&
nsRange::GetSelections() const {
return mSelections;
}
void nsRange::UnregisterSelection() {
mSelection = nullptr;
if (mRegisteredClosestCommonInclusiveAncestor) {
void nsRange::UnregisterSelection(Selection& aSelection) {
for (auto* selectionWrapper : mSelections) {
if (selectionWrapper->Get() == &aSelection) {
selectionWrapper->remove();
break;
}
}
if (mSelections.isEmpty() && mRegisteredClosestCommonInclusiveAncestor) {
UnregisterClosestCommonInclusiveAncestor(
mRegisteredClosestCommonInclusiveAncestor, false);
MOZ_DIAGNOSTIC_ASSERT(
@ -2948,7 +3005,7 @@ nsresult nsRange::GetUsedFontFaces(nsLayoutUtils::UsedFontFaceList& aResult,
}
nsINode* nsRange::GetRegisteredClosestCommonInclusiveAncestor() {
MOZ_ASSERT(IsInSelection(),
MOZ_ASSERT(IsInAnySelection(),
"GetRegisteredClosestCommonInclusiveAncestor only valid for range "
"in selection");
MOZ_ASSERT(mRegisteredClosestCommonInclusiveAncestor);
@ -2970,7 +3027,7 @@ nsRange::AutoInvalidateSelection::~AutoInvalidateSelection() {
// with selections, ranges, etc. But if it still is, we should check whether
// we have a different common ancestor now, and if so invalidate its subtree
// so it paints the selection it's in now.
if (mRange->IsInSelection()) {
if (mRange->IsInAnySelection()) {
nsINode* commonAncestor =
mRange->GetRegisteredClosestCommonInclusiveAncestor();
// XXXbz can commonAncestor really be null here? I wouldn't think so! If

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

@ -20,6 +20,7 @@
#include "mozilla/ErrorResult.h"
#include "mozilla/LinkedList.h"
#include "mozilla/RangeBoundary.h"
#include "mozilla/RefPtr.h"
#include "mozilla/WeakPtr.h"
namespace mozilla {
@ -33,6 +34,26 @@ class DOMRectList;
class InspectorFontFace;
class Selection;
} // namespace dom
/**
* @brief Wrapper class to allow storing a |Selection| in a |LinkedList|.
*
* This helper allows an |nsRange| to store all |Selection|s associated with it
* in a |mozilla::LinkedList|.
*/
class SelectionListWrapper
: public LinkedListElement<RefPtr<SelectionListWrapper>> {
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SelectionListWrapper)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(SelectionListWrapper)
public:
explicit SelectionListWrapper(dom::Selection* aSelection);
/// Returns the stored |Selection|.
dom::Selection* Get() const;
private:
~SelectionListWrapper() = default;
WeakPtr<dom::Selection> mSelection;
};
} // namespace mozilla
class nsRange final : public mozilla::dom::AbstractRange,
@ -92,20 +113,26 @@ class nsRange final : public mozilla::dom::AbstractRange,
nsINode* GetRoot() const { return mRoot; }
/**
* Return true iff this range is part of a Selection object
* Return true if this range is part of a Selection object
* and isn't detached.
*/
bool IsInSelection() const { return !!mSelection; }
bool IsInAnySelection() const { return !mSelections.isEmpty(); }
MOZ_CAN_RUN_SCRIPT void RegisterSelection(
mozilla::dom::Selection& aSelection);
void UnregisterSelection();
void UnregisterSelection(mozilla::dom::Selection& aSelection);
/**
* Returns pointer to a Selection if the range is associated with a Selection.
* Returns a list of all Selections the range is associated with.
*/
mozilla::dom::Selection* GetSelection() const;
const mozilla::LinkedList<RefPtr<mozilla::SelectionListWrapper>>&
GetSelections() const;
/**
* Return true if this range is in |aSelection|.
*/
bool IsInSelection(const mozilla::dom::Selection& aSelection) const;
/**
* Return true if this range was generated.
@ -415,7 +442,7 @@ class nsRange final : public mozilla::dom::AbstractRange,
struct MOZ_STACK_CLASS AutoInvalidateSelection {
explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) {
if (!mRange->IsInSelection() || sIsNested) {
if (!mRange->IsInAnySelection() || sIsNested) {
return;
}
sIsNested = true;
@ -432,16 +459,18 @@ class nsRange final : public mozilla::dom::AbstractRange,
#ifdef DEBUG
bool IsCleared() const {
return !mRoot && !mRegisteredClosestCommonInclusiveAncestor &&
!mSelection && !mNextStartRef && !mNextEndRef;
mSelections.isEmpty() && !mNextStartRef && !mNextEndRef;
}
#endif // #ifdef DEBUG
nsCOMPtr<nsINode> mRoot;
// mRegisteredClosestCommonInclusiveAncestor is only non-null when the range
// IsInSelection(). It's kept alive via mStart/mEnd,
// IsInAnySelection(). It's kept alive via mStart/mEnd,
// because we update it any time those could become disconnected from it.
nsINode* MOZ_NON_OWNING_REF mRegisteredClosestCommonInclusiveAncestor;
mozilla::WeakPtr<mozilla::dom::Selection> mSelection;
// A Range can be part of multiple |Selection|s. This is a very rare use case.
mozilla::LinkedList<RefPtr<mozilla::SelectionListWrapper>> mSelections;
// These raw pointers are used to remember a child that is about
// to be inserted between a CharacterData call and a subsequent

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

@ -429,7 +429,7 @@ AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
bool changed = false;
for (auto& range : mRanges) {
MOZ_ASSERT(!range->IsInSelection(),
MOZ_ASSERT(!range->IsInAnySelection(),
"Changing range in selection may cause running script");
Result<bool, nsresult> result =
WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
@ -939,7 +939,7 @@ AutoRangeArray::SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
// Correct the range.
// The new end parent becomes the parent node of the text.
MOZ_ASSERT(!range->IsInSelection());
MOZ_ASSERT(!range->IsInAnySelection());
range->SetEnd(unwrappedSplitAtEndResult.AtNextContent<EditorRawDOMPoint>()
.ToRawRangeBoundary(),
ignoredError);

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

@ -4298,7 +4298,7 @@ WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
const HTMLEditor& aHTMLEditor, nsRange& aRange,
const Element* aEditingHost) {
MOZ_ASSERT(aRange.IsPositioned());
MOZ_ASSERT(!aRange.IsInSelection(),
MOZ_ASSERT(!aRange.IsInAnySelection(),
"Changing range in selection may cause running script");
if (NS_WARN_IF(!aRange.GetStartContainer()) ||

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

@ -1834,11 +1834,12 @@ void mozInlineSpellChecker::UpdateRangesForMisspelledWords(
const size_t indexOfOldRangeToKeep = aOldRangesForSomeWords.IndexOf(
nodeOffsetRange, 0, CompareRangeAndNodeOffsetRange{});
if (indexOfOldRangeToKeep != aOldRangesForSomeWords.NoIndex &&
aOldRangesForSomeWords[indexOfOldRangeToKeep]->GetSelection() ==
&aSpellCheckerSelection /** TODO: warn in case the old range doesn't
aOldRangesForSomeWords[indexOfOldRangeToKeep]->IsInSelection(
aSpellCheckerSelection)) {
/** TODO: warn in case the old range doesn't
belong to the selection. This is not critical,
because other code can always remove them
before the actual spellchecking happens. */) {
before the actual spellchecking happens. */
MOZ_LOG(sInlineSpellCheckerLog, LogLevel::Verbose,
("%s: reusing old range.", __FUNCTION__));

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

@ -305,7 +305,7 @@ struct MOZ_RAII AutoPrepareFocusRange {
while (i--) {
nsRange* range = ranges[i].mRange;
if (range->IsGenerated()) {
range->UnregisterSelection();
range->UnregisterSelection(aSelection);
aSelection.SelectFrames(presContext, range, false);
ranges.RemoveElementAt(i);
}

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

@ -0,0 +1 @@
prefs: [dom.customHighlightAPI.enabled:true]

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

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
</head>
<body>
<span>One two</span>
<script>
promise_test(async function (t) {
await new Promise(resolve => {
window.onload = resolve;
})
const range = document.createRange();
range.setStart(document.body, 0);
range.setEnd(document.body, 1);
const highlight = new Highlight(range);
CSS.highlights.set("foo", highlight);
document.getSelection().addRange(range);
const highlightRange = highlight.entries().next().value[0];
const selectionRange = document.getSelection().getRangeAt(0);
assert_equals(
highlightRange,
selectionRange,
"The same range must be present in the highlight and the Selection."
);
}, "Range is shared between a custom highlight and the document's Selection.");
</script>
</body>
</html>