зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
5f6910e75e
Коммит
2243494a74
|
@ -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>
|
Загрузка…
Ссылка в новой задаче