From ebf3cd624088e6abe8bb9073cc8301714c439501 Mon Sep 17 00:00:00 2001 From: Jan-Niklas Jaeschke Date: Fri, 3 Mar 2023 14:59:47 +0000 Subject: [PATCH] Bug 1808565, part 1: Adapt `Selection` to support `StaticRange`s. r=webidl,saschanaz,masayuki,smaug This change is necessary to support the [CSS Highlight API](https://drafts.csswg.org/css-highlight-api-1/), which uses `Selection` internally. To replace `nsRange` with `AbstractRange`, some sections needed to be adapted since `nsRange`-specific features were used. Therefore, some methods (such as `GetRangeAt()`) may only be called if the `Selection` is *not* of type `SelectionType::eHighlight`, as it (per spec) returns an `nsRange`. These methods will now `MOZ_ASSERT` if called for a highlight selection. Additional methods are implemented which return `AbstractRange` instead and are safe to be called for every selection type. This commit also improves support of highlight features: - Invalidation of highlight ranges: adding/removing Ranges in-place instead of removing and re-adding the Selection object associated with the highlight. - Ranges are only associated with the Selection that shares the same Document - Fixed minor IDL issue Differential Revision: https://phabricator.services.mozilla.com/D170582 --- accessible/base/TextLeafRange.cpp | 4 +- accessible/generic/HyperTextAccessible.cpp | 4 +- dom/base/AbstractRange.h | 4 + dom/base/ContentIterator.cpp | 6 +- dom/base/ContentIterator.h | 6 +- dom/base/Highlight.cpp | 58 +++--- dom/base/Highlight.h | 6 +- dom/base/HighlightRegistry.cpp | 67 +++++-- dom/base/HighlightRegistry.h | 36 +++- dom/base/Selection.cpp | 168 +++++++++++++----- dom/base/Selection.h | 47 +++-- dom/base/StaticRange.h | 9 + dom/base/StyledRange.cpp | 3 +- dom/base/StyledRange.h | 8 +- dom/base/nsINode.cpp | 12 +- dom/base/nsRange.cpp | 8 +- dom/base/nsRange.h | 8 + dom/webidl/Highlight.webidl | 12 +- .../spellcheck/src/mozInlineSpellChecker.cpp | 4 +- layout/generic/nsFrameSelection.cpp | 55 +++++- layout/generic/nsFrameSelection.h | 18 +- ...ight-iteration-with-modifications.html.ini | 3 - .../Highlight-iteration.html.ini | 30 ---- ...ht-setlike-tampered-Set-prototype.html.ini | 3 - .../Highlight-setlike.html.ini | 54 ------ .../HighlightRegistry-maplike.html.ini | 5 - ...ghlight-painting-invalidation-002.html.ini | 2 - 27 files changed, 396 insertions(+), 244 deletions(-) delete mode 100644 testing/web-platform/meta/css/css-highlight-api/Highlight-iteration.html.ini delete mode 100644 testing/web-platform/meta/css/css-highlight-api/Highlight-setlike-tampered-Set-prototype.html.ini delete mode 100644 testing/web-platform/meta/css/css-highlight-api/Highlight-setlike.html.ini delete mode 100644 testing/web-platform/meta/css/css-highlight-api/HighlightRegistry-maplike.html.ini delete mode 100644 testing/web-platform/meta/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html.ini diff --git a/accessible/base/TextLeafRange.cpp b/accessible/base/TextLeafRange.cpp index f9a06c5f5306..8878408d1dc3 100644 --- a/accessible/base/TextLeafRange.cpp +++ b/accessible/base/TextLeafRange.cpp @@ -479,8 +479,8 @@ static nsTArray FindDOMSpellingErrors(LocalAccessible* aAcc, ? dom::CharacterData::FromNode(node)->TextLength() : RenderedToContentOffset(aAcc, aRenderedEnd); nsTArray domRanges; - domSel->GetRangesForIntervalArray(node, contentStart, node, contentEnd, - aAllowAdjacent, &domRanges); + domSel->GetDynamicRangesForIntervalArray(node, contentStart, node, contentEnd, + aAllowAdjacent, &domRanges); return domRanges; } diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp index 3dce2f734add..520f30c9fe3e 100644 --- a/accessible/generic/HyperTextAccessible.cpp +++ b/accessible/generic/HyperTextAccessible.cpp @@ -1838,8 +1838,8 @@ void HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType, if (!startNode) return; uint32_t childCount = startNode->GetChildCount(); - nsresult rv = domSel->GetRangesForIntervalArray(startNode, 0, startNode, - childCount, true, aRanges); + nsresult rv = domSel->GetDynamicRangesForIntervalArray( + startNode, 0, startNode, childCount, true, aRanges); NS_ENSURE_SUCCESS_VOID(rv); // Remove collapsed ranges diff --git a/dom/base/AbstractRange.h b/dom/base/AbstractRange.h index ae17bbbd55fc..748e6a8351da 100644 --- a/dom/base/AbstractRange.h +++ b/dom/base/AbstractRange.h @@ -71,6 +71,10 @@ class AbstractRange : public nsISupports, public nsWrapperCache { nsINode* GetStartContainer() const { return mStart.Container(); } nsINode* GetEndContainer() const { return mEnd.Container(); } + Document* GetComposedDocOfContainers() const { + return mStart.Container() ? mStart.Container()->GetComposedDoc() : nullptr; + } + // FYI: Returns 0 if it's not positioned. uint32_t StartOffset() const { return static_cast( diff --git a/dom/base/ContentIterator.cpp b/dom/base/ContentIterator.cpp index dbcb4627e260..279f1a35c10d 100644 --- a/dom/base/ContentIterator.cpp +++ b/dom/base/ContentIterator.cpp @@ -18,6 +18,8 @@ namespace mozilla { +using namespace dom; + static bool ComparePostMode(const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd, nsINode& aNode) { nsINode* parent = aNode.GetParentNode(); @@ -162,7 +164,7 @@ nsresult ContentIteratorBase::Init(nsINode* aRoot) { return NS_OK; } -nsresult ContentIteratorBase::Init(nsRange* aRange) { +nsresult ContentIteratorBase::Init(AbstractRange* aRange) { if (NS_WARN_IF(!aRange)) { return NS_ERROR_INVALID_ARG; } @@ -735,7 +737,7 @@ nsresult ContentSubtreeIterator::Init(nsINode* aRoot) { return NS_ERROR_NOT_IMPLEMENTED; } -nsresult ContentSubtreeIterator::Init(nsRange* aRange) { +nsresult ContentSubtreeIterator::Init(AbstractRange* aRange) { MOZ_ASSERT(aRange); if (NS_WARN_IF(!aRange->IsPositioned())) { diff --git a/dom/base/ContentIterator.h b/dom/base/ContentIterator.h index a6884962d2de..4035b6659319 100644 --- a/dom/base/ContentIterator.h +++ b/dom/base/ContentIterator.h @@ -38,7 +38,7 @@ class ContentIteratorBase { */ virtual nsresult Init(nsINode* aRoot); - virtual nsresult Init(nsRange* aRange); + virtual nsresult Init(dom::AbstractRange* aRange); virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer, uint32_t aEndOffset); virtual nsresult Init(const RawRangeBoundary& aStart, @@ -175,7 +175,7 @@ class ContentSubtreeIterator final : public ContentIteratorBase { */ virtual nsresult Init(nsINode* aRoot) override; - virtual nsresult Init(nsRange* aRange) override; + virtual nsresult Init(dom::AbstractRange* aRange) override; virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer, uint32_t aEndOffset) override; virtual nsresult Init(const RawRangeBoundary& aStartBoundary, @@ -233,7 +233,7 @@ class ContentSubtreeIterator final : public ContentIteratorBase { // the range's start and end nodes will never be considered "in" it. nsIContent* GetTopAncestorInRange(nsINode* aNode) const; - RefPtr mRange; + RefPtr mRange; // See . AutoTArray mInclusiveAncestorsOfEndContainer; diff --git a/dom/base/Highlight.cpp b/dom/base/Highlight.cpp index 78cdb9935319..b6934bb0e638 100644 --- a/dom/base/Highlight.cpp +++ b/dom/base/Highlight.cpp @@ -17,6 +17,7 @@ #include "PresShell.h" #include "Selection.h" +#include "nsFrameSelection.h" #include "nsPIDOMWindow.h" namespace mozilla::dom { @@ -81,49 +82,36 @@ void Highlight::RemoveFromHighlightRegistry( already_AddRefed Highlight::CreateHighlightSelection( const nsAtom* aHighlightName, nsFrameSelection* aFrameSelection, ErrorResult& aRv) const { + MOZ_ASSERT(aFrameSelection); + MOZ_ASSERT(aFrameSelection->GetPresShell()); RefPtr selection = MakeRefPtr(SelectionType::eHighlight, aFrameSelection); selection->SetHighlightName(aHighlightName); - - for (auto const& range : mRanges) { - // this is safe because `Highlight::Add()` ensures all ranges are - // dynamic. - RefPtr dynamicRange = range->AsDynamicRange(); - selection->AddRangeAndSelectFramesAndNotifyListeners(*dynamicRange, aRv); - if (aRv.Failed()) { - return nullptr; + SelectionBatcher selectionBatcher(selection, __FUNCTION__); + // NOLINTNEXTLINE(performance-for-range-copy) + for (const RefPtr range : mRanges) { + if (range->GetComposedDocOfContainers() == + aFrameSelection->GetPresShell()->GetDocument()) { + selection->AddHighlightRangeAndSelectFramesAndNotifyListeners(*range, + aRv); } } return selection.forget(); } -void Highlight::NotifyChangesToRegistries(ErrorResult& aRv) { - for (RefPtr highlightRegistry : - mHighlightRegistries.Keys()) { - MOZ_ASSERT(highlightRegistry); - highlightRegistry->HighlightPropertiesChanged(*this, aRv); - if (aRv.Failed()) { - return; - } - } -} - void Highlight::Add(AbstractRange& aRange, ErrorResult& aRv) { - if (aRange.IsStaticRange()) { - // TODO (jjaschke) Selection needs to be able to deal with StaticRanges - // (Bug 1808565) - aRv.ThrowUnknownError("Support for StaticRanges is not implemented yet!"); - return; - } Highlight_Binding::SetlikeHelpers::Add(this, aRange, aRv); if (aRv.Failed()) { return; } if (!mRanges.Contains(&aRange)) { mRanges.AppendElement(&aRange); - NotifyChangesToRegistries(aRv); - if (aRv.Failed()) { - return; + for (const RefPtr registry : + mHighlightRegistries.Keys()) { + registry->MaybeAddRangeToHighlightSelection(aRange, *this, aRv); + if (aRv.Failed()) { + return; + } } } } @@ -132,15 +120,23 @@ void Highlight::Clear(ErrorResult& aRv) { Highlight_Binding::SetlikeHelpers::Clear(this, aRv); if (!aRv.Failed()) { mRanges.Clear(); - NotifyChangesToRegistries(aRv); + for (const RefPtr registry : + mHighlightRegistries.Keys()) { + registry->RemoveHighlightSelection(*this); + } } } -void Highlight::Delete(AbstractRange& aRange, ErrorResult& aRv) { +bool Highlight::Delete(AbstractRange& aRange, ErrorResult& aRv) { if (Highlight_Binding::SetlikeHelpers::Delete(this, aRange, aRv)) { mRanges.RemoveElement(&aRange); - NotifyChangesToRegistries(aRv); + for (const RefPtr registry : + mHighlightRegistries.Keys()) { + registry->MaybeRemoveRangeFromHighlightSelection(aRange, *this); + } + return true; } + return false; } JSObject* Highlight::WrapObject(JSContext* aCx, diff --git a/dom/base/Highlight.h b/dom/base/Highlight.h index 769494272db2..3983838ec77d 100644 --- a/dom/base/Highlight.h +++ b/dom/base/Highlight.h @@ -149,12 +149,12 @@ class Highlight final : public nsISupports, public nsWrapperCache { * internal one. * * Also notifies all `HighlightRegistry` instances. + * + * @return As per spec, returns true if the range was deleted. */ - MOZ_CAN_RUN_SCRIPT void Delete(AbstractRange& aRange, ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT bool Delete(AbstractRange& aRange, ErrorResult& aRv); private: - MOZ_CAN_RUN_SCRIPT void NotifyChangesToRegistries(ErrorResult& aRv); - RefPtr mWindow; /** diff --git a/dom/base/HighlightRegistry.cpp b/dom/base/HighlightRegistry.cpp index e1c763e633c7..17fbe83ca5a2 100644 --- a/dom/base/HighlightRegistry.cpp +++ b/dom/base/HighlightRegistry.cpp @@ -59,8 +59,50 @@ JSObject* HighlightRegistry::WrapObject(JSContext* aCx, return HighlightRegistry_Binding::Wrap(aCx, this, aGivenProto); } -void HighlightRegistry::HighlightPropertiesChanged(Highlight& aHighlight, - ErrorResult& aRv) { +void HighlightRegistry::MaybeAddRangeToHighlightSelection(AbstractRange& aRange, + Highlight& aHighlight, + ErrorResult& aRv) { + RefPtr frameSelection = GetFrameSelection(); + if (!frameSelection) { + return; + } + MOZ_ASSERT(frameSelection->GetPresShell()); + if (!frameSelection->GetPresShell()->GetDocument() || + frameSelection->GetPresShell()->GetDocument() != + aRange.GetComposedDocOfContainers()) { + // ranges that belong to a different document must not be added. + return; + } + for (auto const& iter : mHighlightsOrdered) { + if (iter.second() != &aHighlight) { + continue; + } + + const RefPtr highlightName = iter.first(); + frameSelection->AddHighlightSelectionRange(highlightName, aHighlight, + aRange, aRv); + } +} + +void HighlightRegistry::MaybeRemoveRangeFromHighlightSelection( + AbstractRange& aRange, Highlight& aHighlight) { + RefPtr frameSelection = GetFrameSelection(); + if (!frameSelection) { + return; + } + MOZ_ASSERT(frameSelection->GetPresShell()); + + for (auto const& iter : mHighlightsOrdered) { + if (iter.second() != &aHighlight) { + continue; + } + + const RefPtr highlightName = iter.first(); + frameSelection->RemoveHighlightSelectionRange(highlightName, aRange); + } +} + +void HighlightRegistry::RemoveHighlightSelection(Highlight& aHighlight) { RefPtr frameSelection = GetFrameSelection(); if (!frameSelection) { return; @@ -70,12 +112,8 @@ void HighlightRegistry::HighlightPropertiesChanged(Highlight& aHighlight, continue; } - RefPtr highlightName = iter.first(); + const RefPtr highlightName = iter.first(); frameSelection->RemoveHighlightSelection(highlightName); - frameSelection->AddHighlightSelection(highlightName, aHighlight, aRv); - if (aRv.Failed()) { - return; - } } } @@ -135,8 +173,8 @@ void HighlightRegistry::Clear(ErrorResult& aRv) { } auto frameSelection = GetFrameSelection(); for (auto const& iter : mHighlightsOrdered) { - const auto& highlightName = iter.first(); - const auto& highlight = iter.second(); + const RefPtr highlightName = iter.first(); + const RefPtr& highlight = iter.second(); highlight->RemoveFromHighlightRegistry(*this, *highlightName); if (frameSelection) { frameSelection->RemoveHighlightSelection(highlightName); @@ -145,9 +183,9 @@ void HighlightRegistry::Clear(ErrorResult& aRv) { mHighlightsOrdered.Clear(); } -void HighlightRegistry::Delete(const nsAString& aKey, ErrorResult& aRv) { +bool HighlightRegistry::Delete(const nsAString& aKey, ErrorResult& aRv) { if (!HighlightRegistry_Binding::MaplikeHelpers::Delete(this, aKey, aRv)) { - return; + return false; } RefPtr highlightNameAtom = NS_AtomizeMainThread(aKey); auto foundIter = @@ -155,9 +193,9 @@ void HighlightRegistry::Delete(const nsAString& aKey, ErrorResult& aRv) { [&highlightNameAtom](auto const& aElm) { return aElm.first() == highlightNameAtom; }); - if (foundIter == mHighlightsOrdered.cend()) { - return; - } + MOZ_ASSERT(foundIter != mHighlightsOrdered.cend(), + "HighlightRegistry: maplike and internal data are out of sync!"); + RefPtr highlight = foundIter->second(); mHighlightsOrdered.RemoveElementAt(foundIter); @@ -165,6 +203,7 @@ void HighlightRegistry::Delete(const nsAString& aKey, ErrorResult& aRv) { frameSelection->RemoveHighlightSelection(highlightNameAtom); } highlight->RemoveFromHighlightRegistry(*this, *highlightNameAtom); + return true; } RefPtr HighlightRegistry::GetFrameSelection() { diff --git a/dom/base/HighlightRegistry.h b/dom/base/HighlightRegistry.h index ac30ef7ef53f..0452eea0a0a6 100644 --- a/dom/base/HighlightRegistry.h +++ b/dom/base/HighlightRegistry.h @@ -23,6 +23,7 @@ class ErrorResult; } namespace mozilla::dom { +class AbstractRange; class Document; class Highlight; @@ -62,10 +63,33 @@ class HighlightRegistry final : public nsISupports, public nsWrapperCache { ErrorResult& aRv); /** - * @brief Propagates changes to a highlight to the `FrameSelection`. + * @brief Adds the Range to the Highlight Selection if it belongs to the same + * Document. + * + * If no Highlight Selection for this highlight exists, it will be created. + * This may occur when a Highlight is added to the Registry after the + * nsFrameSelection is created. */ - MOZ_CAN_RUN_SCRIPT void HighlightPropertiesChanged(Highlight& aHighlight, - ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT void MaybeAddRangeToHighlightSelection( + AbstractRange& aRange, Highlight& aHighlight, ErrorResult& aRv); + + /** + * @brief Removes the Range from the Highlight Selection if it belongs to the + * same Document. + * + * @note If the last range of a highlight selection is removed, the selection + * itself is *not* removed. + */ + MOZ_CAN_RUN_SCRIPT void MaybeRemoveRangeFromHighlightSelection( + AbstractRange& aRange, Highlight& aHighlight); + + /** + * @brief Removes the highlight selections associated with the highlight. + * + * This method is called when the Highlight is cleared + * (i.e., all Ranges are removed). + */ + MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelection(Highlight& aHighlight); // WebIDL interface @@ -92,15 +116,17 @@ class HighlightRegistry final : public nsISupports, public nsWrapperCache { * * If a `FrameSelection` is present, all highlight selections are removed. */ - void Clear(ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT void Clear(ErrorResult& aRv); /** * @brief Removes the highlight named `aKey` from the registry. * * This call removes the combination of `this` and `aKey` from the highlight. * If a `FrameSelection` is present, the highlight selection is removed. + * + * @return true if `aKey` existed and was deleted. */ - void Delete(const nsAString& aKey, ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT bool Delete(const nsAString& aKey, ErrorResult& aRv); private: /** diff --git a/dom/base/Selection.cpp b/dom/base/Selection.cpp index e57a2c021a71..a2d90f96fd60 100644 --- a/dom/base/Selection.cpp +++ b/dom/base/Selection.cpp @@ -23,6 +23,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/SelectionBinding.h" #include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/StaticRange.h" #include "mozilla/ErrorResult.h" #include "mozilla/HTMLEditor.h" #include "mozilla/IntegerRange.h" @@ -685,12 +686,15 @@ void Selection::SetAnchorFocusRange(size_t aIndex) { if (aIndex >= mStyledRanges.Length()) { return; } - mAnchorFocusRange = mStyledRanges.mRanges[aIndex].mRange; + // Highlight selections may contain static ranges. + MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); + AbstractRange* anchorFocusRange = mStyledRanges.mRanges[aIndex].mRange; + mAnchorFocusRange = anchorFocusRange->AsDynamicRange(); } static int32_t CompareToRangeStart(const nsINode& aCompareNode, uint32_t aCompareOffset, - const nsRange& aRange) { + const AbstractRange& aRange) { MOZ_ASSERT(aRange.GetStartContainer()); nsINode* start = aRange.GetStartContainer(); // If the nodes that we're comparing are not in the same document, assume that @@ -709,7 +713,7 @@ static int32_t CompareToRangeStart(const nsINode& aCompareNode, static int32_t CompareToRangeEnd(const nsINode& aCompareNode, uint32_t aCompareOffset, - const nsRange& aRange) { + const AbstractRange& aRange) { MOZ_ASSERT(aRange.IsPositioned()); nsINode* end = aRange.GetEndContainer(); // If the nodes that we're comparing are not in the same document or in the @@ -730,14 +734,14 @@ static int32_t CompareToRangeEnd(const nsINode& aCompareNode, size_t Selection::StyledRanges::FindInsertionPoint( const nsTArray* aElementArray, const nsINode& aPointNode, uint32_t aPointOffset, - int32_t (*aComparator)(const nsINode&, uint32_t, const nsRange&)) { + int32_t (*aComparator)(const nsINode&, uint32_t, const AbstractRange&)) { int32_t beginSearch = 0; int32_t endSearch = aElementArray->Length(); // one beyond what to check if (endSearch) { int32_t center = endSearch - 1; // Check last index, then binary search do { - const nsRange* range = (*aElementArray)[center].mRange; + const AbstractRange* range = (*aElementArray)[center].mRange; int32_t cmp{aComparator(aPointNode, aPointOffset, *range)}; @@ -766,7 +770,7 @@ size_t Selection::StyledRanges::FindInsertionPoint( // static nsresult Selection::StyledRanges::SubtractRange( StyledRange& aRange, nsRange& aSubtract, nsTArray* aOutput) { - nsRange* range = aRange.mRange; + AbstractRange* range = aRange.mRange; if (NS_WARN_IF(!range->IsPositioned())) { return NS_ERROR_UNEXPECTED; } @@ -1080,7 +1084,9 @@ nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps( // Remove all the overlapping ranges for (size_t i = startIndex; i < endIndex; ++i) { - mRanges[i].mRange->UnregisterSelection(mSelection); + if (mRanges[i].mRange->IsDynamicRange()) { + mRanges[i].mRange->AsDynamicRange()->UnregisterSelection(mSelection); + } } mRanges.RemoveElementsAt(startIndex, endIndex - startIndex); @@ -1102,9 +1108,12 @@ nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps( mRanges.InsertElementsAt(startIndex, temp); for (uint32_t i = 0; i < temp.Length(); ++i) { - MOZ_KnownLive(temp[i].mRange)->RegisterSelection(MOZ_KnownLive(mSelection)); - // `MOZ_KnownLive` is required because of - // https://bugzilla.mozilla.org/show_bug.cgi?id=1622253. + if (temp[i].mRange->IsDynamicRange()) { + MOZ_KnownLive(temp[i].mRange->AsDynamicRange()) + ->RegisterSelection(MOZ_KnownLive(mSelection)); + // `MOZ_KnownLive` is required because of + // https://bugzilla.mozilla.org/show_bug.cgi?id=1622253. + } } aOutIndex->emplace(startIndex + insertionPoint); @@ -1112,7 +1121,7 @@ nsresult Selection::StyledRanges::MaybeAddRangeAndTruncateOverlaps( } nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection( - nsRange& aRange) { + AbstractRange& aRange) { // Find the range's index & remove it. We could use FindInsertionPoint to // get O(log n) time, but that requires many expensive DOM comparisons. // For even several thousand items, this is probably faster because the @@ -1128,7 +1137,9 @@ nsresult Selection::StyledRanges::RemoveRangeAndUnregisterSelection( if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR; mRanges.RemoveElementAt(idx); - aRange.UnregisterSelection(mSelection); + if (aRange.IsDynamicRange()) { + aRange.AsDynamicRange()->UnregisterSelection(mSelection); + } return NS_OK; } nsresult Selection::RemoveCollapsedRanges() { @@ -1170,7 +1181,7 @@ void Selection::Clear(nsPresContext* aPresContext) { bool Selection::StyledRanges::HasEqualRangeBoundariesAt( const nsRange& aRange, size_t aRangeIndex) const { if (aRangeIndex < mRanges.Length()) { - const nsRange* range = mRanges[aRangeIndex].mRange; + const AbstractRange* range = mRanges[aRangeIndex].mRange; return range->HasEqualBoundaries(aRange); } return false; @@ -1181,9 +1192,10 @@ void Selection::GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset, bool aAllowAdjacent, nsTArray>& aReturn, mozilla::ErrorResult& aRv) { - nsTArray results; - nsresult rv = GetRangesForIntervalArray(&aBeginNode, aBeginOffset, &aEndNode, - aEndOffset, aAllowAdjacent, &results); + AutoTArray results; + nsresult rv = + GetDynamicRangesForIntervalArray(&aBeginNode, aBeginOffset, &aEndNode, + aEndOffset, aAllowAdjacent, &results); if (NS_FAILED(rv)) { aRv.Throw(rv); return; @@ -1195,9 +1207,10 @@ void Selection::GetRangesForInterval(nsINode& aBeginNode, uint32_t aBeginOffset, } } -nsresult Selection::GetRangesForIntervalArray( +nsresult Selection::GetAbstractRangesForIntervalArray( nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode, - uint32_t aEndOffset, bool aAllowAdjacent, nsTArray* aRanges) { + uint32_t aEndOffset, bool aAllowAdjacent, + nsTArray* aRanges) { if (NS_WARN_IF(!aBeginNode)) { return NS_ERROR_UNEXPECTED; } @@ -1226,6 +1239,23 @@ nsresult Selection::GetRangesForIntervalArray( return NS_OK; } +nsresult Selection::GetDynamicRangesForIntervalArray( + nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode, + uint32_t aEndOffset, bool aAllowAdjacent, nsTArray* aRanges) { + MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); + AutoTArray abstractRanges; + nsresult rv = GetAbstractRangesForIntervalArray( + aBeginNode, aBeginOffset, aEndNode, aEndOffset, aAllowAdjacent, + &abstractRanges); + NS_ENSURE_SUCCESS(rv, rv); + aRanges->Clear(); + aRanges->SetCapacity(abstractRanges.Length()); + for (auto* abstractRange : abstractRanges) { + aRanges->AppendElement(abstractRange->AsDynamicRange()); + } + return NS_OK; +} + nsresult Selection::StyledRanges::GetIndicesForInterval( const nsINode* aBeginNode, uint32_t aBeginOffset, const nsINode* aEndNode, uint32_t aEndOffset, bool aAllowAdjacent, Maybe& aStartIndex, @@ -1254,7 +1284,7 @@ nsresult Selection::StyledRanges::GetIndicesForInterval( &CompareToRangeStart)}; if (endsBeforeIndex == 0) { - const nsRange* endRange = mRanges[endsBeforeIndex].mRange; + const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange; // If the interval is strictly before the range at index 0, we can optimize // by returning now - all ranges start after the given interval @@ -1289,9 +1319,9 @@ nsresult Selection::StyledRanges::GetIndicesForInterval( // In the final case, there can be two such ranges, a collapsed range, and // an adjacent range (they will appear in mRanges in that // order). For this final case, we need to increment endsBeforeIndex, until - // one of the first two possibilites hold + // one of the first two possibilities hold while (endsBeforeIndex < mRanges.Length()) { - const nsRange* endRange = mRanges[endsBeforeIndex].mRange; + const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange; if (!endRange->StartRef().Equals(aEndNode, aEndOffset)) { break; } @@ -1309,7 +1339,7 @@ nsresult Selection::StyledRanges::GetIndicesForInterval( // order). For this final case, we only need to take action if both those // ranges exist, and we are pointing to the collapsed range - we need to // point to the adjacent range - const nsRange* beginRange = mRanges[beginsAfterIndex].mRange; + const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange; if (beginsAfterIndex > 0 && beginRange->Collapsed() && beginRange->EndRef().Equals(aBeginNode, aBeginOffset)) { beginRange = mRanges[beginsAfterIndex - 1].mRange; @@ -1322,7 +1352,7 @@ nsresult Selection::StyledRanges::GetIndicesForInterval( // need to take action is when the range at beginsAfterIndex ends on // the given interval's start point, but that range isn't collapsed (a // collapsed range should be included in the returned results). - const nsRange* beginRange = mRanges[beginsAfterIndex].mRange; + const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange; if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset) && !beginRange->Collapsed()) { beginsAfterIndex++; @@ -1333,7 +1363,7 @@ nsresult Selection::StyledRanges::GetIndicesForInterval( // represents the point at the end of the interval - this range should be // included if (endsBeforeIndex < mRanges.Length()) { - const nsRange* endRange = mRanges[endsBeforeIndex].mRange; + const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange; if (endRange->StartRef().Equals(aEndNode, aEndOffset) && endRange->Collapsed()) { endsBeforeIndex++; @@ -1465,8 +1495,12 @@ nsresult Selection::SelectFramesOfInclusiveDescendantsOfContent( } void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) { + // this method is currently only called in a user-initiated context. + // therefore it is safe to assume that we are not in a Highlight selection + // and we only have to deal with nsRanges (no StaticRanges). + MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); for (size_t i = 0; i < mStyledRanges.Length(); ++i) { - nsRange* range = mStyledRanges.mRanges[i].mRange; + nsRange* range = mStyledRanges.mRanges[i].mRange->AsDynamicRange(); MOZ_ASSERT(range->IsInAnySelection()); SelectFrames(aPresContext, range, range->IsInAnySelection()); } @@ -1476,14 +1510,19 @@ void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) { * The idea of this helper method is to select or deselect "top to bottom", * traversing through the frames */ -nsresult Selection::SelectFrames(nsPresContext* aPresContext, nsRange* aRange, - bool aSelect) const { +nsresult Selection::SelectFrames(nsPresContext* aPresContext, + AbstractRange* aRange, bool aSelect) const { if (!mFrameSelection || !aPresContext || !aPresContext->GetPresShell()) { // nothing to do return NS_OK; } MOZ_ASSERT(aRange && aRange->IsPositioned()); + if (aRange->IsStaticRange() && !aRange->AsStaticRange()->IsValid()) { + // TODO jjaschke: Actions necessary to unselect invalid static ranges? + return NS_OK; + } + if (mFrameSelection->IsInTableSelectionMode()) { nsINode* node = aRange->GetClosestCommonInclusiveAncestor(); nsIFrame* frame = node->IsContent() @@ -1629,10 +1668,10 @@ UniquePtr Selection::LookUpSelection( return aDetailsHead; } - nsTArray overlappingRanges; - nsresult rv = GetRangesForIntervalArray(aContent, aContentOffset, aContent, - aContentOffset + aContentLength, - false, &overlappingRanges); + nsTArray overlappingRanges; + nsresult rv = GetAbstractRangesForIntervalArray( + aContent, aContentOffset, aContent, aContentOffset + aContentLength, + false, &overlappingRanges); if (NS_FAILED(rv)) { return aDetailsHead; } @@ -1644,7 +1683,10 @@ UniquePtr Selection::LookUpSelection( UniquePtr detailsHead = std::move(aDetailsHead); for (size_t i = 0; i < overlappingRanges.Length(); i++) { - nsRange* range = overlappingRanges[i]; + AbstractRange* range = overlappingRanges[i]; + if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) { + continue; + } nsINode* startNode = range->GetStartContainer(); nsINode* endNode = range->GetEndContainer(); uint32_t startOffset = range->StartOffset(); @@ -1786,13 +1828,15 @@ 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(mSelection); + if (mRanges[i].mRange->IsDynamicRange()) { + mRanges[i].mRange->AsDynamicRange()->UnregisterSelection(mSelection); + } } } void Selection::StyledRanges::Clear() { mRanges.Clear(); } -StyledRange* Selection::StyledRanges::FindRangeData(nsRange* aRange) { +StyledRange* Selection::StyledRanges::FindRangeData(AbstractRange* aRange) { NS_ENSURE_TRUE(aRange, nullptr); for (uint32_t i = 0; i < mRanges.Length(); i++) { if (mRanges[i].mRange == aRange) { @@ -2009,6 +2053,26 @@ void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange, NotifySelectionListeners(); } +void Selection::AddHighlightRangeAndSelectFramesAndNotifyListeners( + AbstractRange& aRange, mozilla::ErrorResult& aRv) { + MOZ_ASSERT(mSelectionType == SelectionType::eHighlight); + + mStyledRanges.mRanges.AppendElement(StyledRange{&aRange}); + if (aRange.IsDynamicRange()) { + RefPtr range = aRange.AsDynamicRange(); + range->RegisterSelection(*this); + } + if (!mFrameSelection) { + return; // nothing to do + } + + RefPtr presContext = GetPresContext(); + SelectFrames(presContext, &aRange, true); + + // Be aware, this instance may be destroyed after this call. + NotifySelectionListeners(); +} + // Selection::RemoveRangeAndUnselectFramesAndNotifyListeners // // Removes the given range from the selection. The tricky part is updating @@ -2022,7 +2086,7 @@ void Selection::AddRangeAndSelectFramesAndNotifyListeners(nsRange& aRange, // selected frames after we've cleared the bit from ours. void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners( - nsRange& aRange, ErrorResult& aRv) { + AbstractRange& aRange, ErrorResult& aRv) { nsresult rv = mStyledRanges.RemoveRangeAndUnregisterSelection(aRange); if (NS_FAILED(rv)) { aRv.Throw(rv); @@ -2056,9 +2120,9 @@ void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners( SelectFrames(presContext, &aRange, false); // add back the selected bit for each range touching our nodes - nsTArray affectedRanges; - rv = GetRangesForIntervalArray(beginNode, beginOffset, endNode, endOffset, - true, &affectedRanges); + nsTArray affectedRanges; + rv = GetAbstractRangesForIntervalArray(beginNode, beginOffset, endNode, + endOffset, true, &affectedRanges); if (NS_FAILED(rv)) { aRv.Throw(rv); return; @@ -2232,7 +2296,7 @@ void Selection::CollapseToStart(ErrorResult& aRv) { } // Get the first range - const nsRange* firstRange = mStyledRanges.mRanges[0].mRange; + const AbstractRange* firstRange = mStyledRanges.mRanges[0].mRange; if (!firstRange) { aRv.Throw(NS_ERROR_FAILURE); return; @@ -2269,7 +2333,7 @@ void Selection::CollapseToEnd(ErrorResult& aRv) { } // Get the last range - const nsRange* lastRange = mStyledRanges.mRanges[cnt - 1].mRange; + const AbstractRange* lastRange = mStyledRanges.mRanges[cnt - 1].mRange; if (!lastRange) { aRv.Throw(NS_ERROR_FAILURE); return; @@ -2308,11 +2372,25 @@ nsRange* Selection::GetRangeAt(uint32_t aIndex, ErrorResult& aRv) { return range; } -nsRange* Selection::GetRangeAt(uint32_t aIndex) const { +AbstractRange* Selection::GetAbstractRangeAt(uint32_t aIndex) const { StyledRange empty(nullptr); return mStyledRanges.mRanges.SafeElementAt(aIndex, empty).mRange; } +nsRange* Selection::GetRangeAt(uint32_t aIndex) const { + // This method per IDL spec returns a dynamic range. + // Therefore, it must be ensured that it is only called + // for a selection which contains dynamic ranges exclusively. + // Highlight Selections are allowed to contain StaticRanges, + // therefore this method must not be called. + MOZ_ASSERT(mSelectionType != SelectionType::eHighlight); + AbstractRange* abstractRange = GetAbstractRangeAt(aIndex); + if (!abstractRange) { + return nullptr; + } + return abstractRange->AsDynamicRange(); +} + nsresult Selection::SetAnchorFocusToRange(nsRange* aRange) { NS_ENSURE_STATE(mAnchorFocusRange); @@ -2737,9 +2815,9 @@ bool Selection::ContainsNode(nsINode& aNode, bool aAllowPartial, nodeLength = aNode.GetChildCount(); } - nsTArray overlappingRanges; - rv = GetRangesForIntervalArray(&aNode, 0, &aNode, nodeLength, false, - &overlappingRanges); + nsTArray overlappingRanges; + rv = GetAbstractRangesForIntervalArray(&aNode, 0, &aNode, nodeLength, false, + &overlappingRanges); if (NS_FAILED(rv)) { aRv.Throw(rv); return false; @@ -3074,7 +3152,7 @@ void Selection::RemoveSelectionListener( Element* Selection::StyledRanges::GetCommonEditingHost() const { Element* editingHost = nullptr; for (const StyledRange& rangeData : mRanges) { - const nsRange* range = rangeData.mRange; + const AbstractRange* range = rangeData.mRange; MOZ_ASSERT(range); nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor(); if (!commonAncestorNode || !commonAncestorNode->IsContent()) { diff --git a/dom/base/Selection.h b/dom/base/Selection.h index 9d42f88583be..1fd324334e9a 100644 --- a/dom/base/Selection.h +++ b/dom/base/Selection.h @@ -230,6 +230,17 @@ class Selection final : public nsSupportsWeakReference, */ nsRange* GetRangeAt(uint32_t aIndex) const; + /** + * @brief Get the |AbstractRange| at |aIndex|. + * + * This method is safe to be called for every selection type. + * However, |StaticRange|s only occur for |SelectionType::eHighlight|. + * If the SelectionType may be eHighlight, this method must be called instead + * of |GetRangeAt()|. + * + * Returns null if |aIndex| is out of bounds. + */ + AbstractRange* GetAbstractRangeAt(uint32_t aIndex) const; // Get the anchor-to-focus range if we don't care which end is // anchor and which end is focus. const nsRange* GetAnchorFocusRange() const { return mAnchorFocusRange; } @@ -365,7 +376,7 @@ class Selection final : public nsSupportsWeakReference, * Callers need to keep `aRange` alive. */ MOZ_CAN_RUN_SCRIPT void RemoveRangeAndUnselectFramesAndNotifyListeners( - nsRange& aRange, mozilla::ErrorResult& aRv); + AbstractRange& aRange, mozilla::ErrorResult& aRv); MOZ_CAN_RUN_SCRIPT void RemoveAllRanges(mozilla::ErrorResult& aRv); @@ -547,6 +558,9 @@ class Selection final : public nsSupportsWeakReference, MOZ_CAN_RUN_SCRIPT void AddRangeAndSelectFramesAndNotifyListeners( nsRange& aRange, mozilla::ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT void AddHighlightRangeAndSelectFramesAndNotifyListeners( + AbstractRange& aRange, mozilla::ErrorResult& aRv); + /** * Adds all children of the specified node to the selection. * @param aNode the parent of the children to be added to the selection. @@ -662,7 +676,7 @@ class Selection final : public nsSupportsWeakReference, */ void SetCanCacheFrameOffset(bool aCanCacheFrameOffset); - // Selection::GetRangesForIntervalArray + // Selection::GetAbstractRangesForIntervalArray // // Fills a nsTArray with the ranges overlapping the range specified by // the given endpoints. Ranges in the selection exactly adjacent to the @@ -681,10 +695,23 @@ class Selection final : public nsSupportsWeakReference, // // Now that overlapping ranges are disallowed, there can be a maximum of // 2 adjacent ranges - nsresult GetRangesForIntervalArray(nsINode* aBeginNode, uint32_t aBeginOffset, - nsINode* aEndNode, uint32_t aEndOffset, - bool aAllowAdjacent, - nsTArray* aRanges); + nsresult GetAbstractRangesForIntervalArray(nsINode* aBeginNode, + uint32_t aBeginOffset, + nsINode* aEndNode, + uint32_t aEndOffset, + bool aAllowAdjacent, + nsTArray* aRanges); + + /** + * Converts the results of |GetAbstractRangesForIntervalArray()| to |nsRange|. + * + * |StaticRange|s can only occur in Selections of type |eHighlight|. + * Therefore, this method must not be called for this selection type + * as not every |AbstractRange| can be cast to |nsRange|. + */ + nsresult GetDynamicRangesForIntervalArray( + nsINode* aBeginNode, uint32_t aBeginOffset, nsINode* aEndNode, + uint32_t aEndOffset, bool aAllowAdjacent, nsTArray* aRanges); /** * Modifies the cursor Bidi level after a change in keyboard direction @@ -792,7 +819,7 @@ class Selection final : public nsSupportsWeakReference, PostContentIterator& aPostOrderIter, nsIContent* aContent, bool aSelected) const; - nsresult SelectFrames(nsPresContext* aPresContext, nsRange* aRange, + nsresult SelectFrames(nsPresContext* aPresContext, AbstractRange* aRange, bool aSelect) const; /** @@ -817,7 +844,7 @@ class Selection final : public nsSupportsWeakReference, explicit StyledRanges(Selection& aSelection) : mSelection(aSelection) {} void Clear(); - StyledRange* FindRangeData(nsRange* aRange); + StyledRange* FindRangeData(AbstractRange* aRange); using Elements = AutoTArray; @@ -825,7 +852,7 @@ class Selection final : public nsSupportsWeakReference, nsresult RemoveCollapsedRanges(); - nsresult RemoveRangeAndUnregisterSelection(nsRange& aRange); + nsresult RemoveRangeAndUnregisterSelection(AbstractRange& aRange); /** * Binary searches the given sorted array of ranges for the insertion point @@ -841,7 +868,7 @@ class Selection final : public nsSupportsWeakReference, static size_t FindInsertionPoint( const nsTArray* aElementArray, const nsINode& aPointNode, uint32_t aPointOffset, - int32_t (*aComparator)(const nsINode&, uint32_t, const nsRange&)); + int32_t (*aComparator)(const nsINode&, uint32_t, const AbstractRange&)); /** * Works on the same principle as GetRangesForIntervalArray, however diff --git a/dom/base/StaticRange.h b/dom/base/StaticRange.h index 11a14f330a43..c89699fe6e4e 100644 --- a/dom/base/StaticRange.h +++ b/dom/base/StaticRange.h @@ -60,6 +60,15 @@ class StaticRange final : public AbstractRange { const RangeBoundaryBase& aStartBoundary, const RangeBoundaryBase& aEndBoundary, ErrorResult& aRv); + /** + * Returns true if the range is valid. + * + * @see https://dom.spec.whatwg.org/#staticrange-valid + */ + bool IsValid() const { + return mStart.IsSetAndValid() && mEnd.IsSetAndValid(); + } + protected: explicit StaticRange(nsINode* aNode) : AbstractRange(aNode, /* aIsDynamicRange = */ false) {} diff --git a/dom/base/StyledRange.cpp b/dom/base/StyledRange.cpp index 8fb28314cb04..8ab3b10186c0 100644 --- a/dom/base/StyledRange.cpp +++ b/dom/base/StyledRange.cpp @@ -7,4 +7,5 @@ #include "mozilla/dom/StyledRange.h" #include "nsRange.h" -StyledRange::StyledRange(nsRange* aRange) : mRange(aRange) {} +StyledRange::StyledRange(mozilla::dom::AbstractRange* aRange) + : mRange(aRange) {} diff --git a/dom/base/StyledRange.h b/dom/base/StyledRange.h index 0347fd0ef863..a88c5eb13e95 100644 --- a/dom/base/StyledRange.h +++ b/dom/base/StyledRange.h @@ -10,12 +10,14 @@ #include "mozilla/RefPtr.h" #include "mozilla/TextRange.h" -class nsRange; +namespace mozilla::dom { +class AbstractRange; +} struct StyledRange { - explicit StyledRange(nsRange* aRange); + explicit StyledRange(mozilla::dom::AbstractRange* aRange); - RefPtr mRange; + RefPtr mRange; mozilla::TextRangeStyle mTextRangeStyle; }; diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 2adca685bcf6..a0ada26345be 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -309,7 +309,7 @@ class IsItemInRangeComparator { MOZ_ASSERT(aStartOffset <= aEndOffset); } - int operator()(const nsRange* const aRange) const { + int operator()(const AbstractRange* const aRange) const { int32_t cmp = nsContentUtils::ComparePoints_Deprecated( &mNode, mEndOffset, aRange->GetStartContainer(), aRange->StartOffset(), nullptr, mCache); @@ -373,25 +373,25 @@ bool nsINode::IsSelected(const uint32_t aStartOffset, while (high != low) { size_t middle = low + (high - low) / 2; - const nsRange* const range = selection->GetRangeAt(middle); + const AbstractRange* const range = selection->GetAbstractRangeAt(middle); int result = comparator(range); if (result == 0) { if (!range->Collapsed()) { return true; } - const nsRange* middlePlus1; - const nsRange* middleMinus1; + const AbstractRange* middlePlus1; + const AbstractRange* middleMinus1; // if node end > start of middle+1, result = 1 if (middle + 1 < high && - (middlePlus1 = selection->GetRangeAt(middle + 1)) && + (middlePlus1 = selection->GetAbstractRangeAt(middle + 1)) && nsContentUtils::ComparePoints_Deprecated( this, aEndOffset, middlePlus1->GetStartContainer(), middlePlus1->StartOffset(), nullptr, &cache) > 0) { result = 1; // if node start < end of middle - 1, result = -1 } else if (middle >= 1 && - (middleMinus1 = selection->GetRangeAt(middle - 1)) && + (middleMinus1 = selection->GetAbstractRangeAt(middle - 1)) && nsContentUtils::ComparePoints_Deprecated( this, aStartOffset, middleMinus1->GetEndContainer(), middleMinus1->EndOffset(), nullptr, &cache) < 0) { diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index 9783a835bceb..b0abb9a41b28 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -893,15 +893,15 @@ void nsRange::NotifySelectionListenersAfterRangeSet() { // 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()) { + if (IsPartOfOneSelectionOnly()) { + RefPtr selection = mSelections.getFirst()->Get(); + selection->NotifySelectionListeners(calledByJSRestorer.SavedValue()); + } else { SelectionListLocalCopy copiedSelections{mSelections}; for (const auto* selectionWrapper : copiedSelections.Get()) { RefPtr selection = selectionWrapper->Get(); selection->NotifySelectionListeners(calledByJSRestorer.SavedValue()); } - } else { - RefPtr selection = mSelections.getFirst()->Get(); - selection->NotifySelectionListeners(calledByJSRestorer.SavedValue()); } } } diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h index 3a53cc5f545e..21d17731639b 100644 --- a/dom/base/nsRange.h +++ b/dom/base/nsRange.h @@ -351,6 +351,14 @@ class nsRange final : public mozilla::dom::AbstractRange, bool IsPointComparableToRange(const nsINode& aContainer, uint32_t aOffset, ErrorResult& aErrorResult) const; + /** + * @brief Returns true if the range is part of exactly one |Selection|. + */ + bool IsPartOfOneSelectionOnly() const { + return !mSelections.isEmpty() && + mSelections.getFirst() == mSelections.getLast(); + }; + public: /** * This helper function gets rects and correlated text for the given range. diff --git a/dom/webidl/Highlight.webidl b/dom/webidl/Highlight.webidl index 435e388c5232..e09f0aed3f28 100644 --- a/dom/webidl/Highlight.webidl +++ b/dom/webidl/Highlight.webidl @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is - * https://www.w3.org/TR/css-highlight-api-1/ + * https://drafts.csswg.org/css-highlight-api-1/ * * Copyright © 2021 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C * liability, trademark and document use rules apply. @@ -12,7 +12,7 @@ /** * Enum defining the available highlight types. - * See https://www.w3.org/TR/css-highlight-api-1/#enumdef-highlighttype + * See https://drafts.csswg.org/css-highlight-api-1/#enumdef-highlighttype */ enum HighlightType { "highlight", @@ -24,7 +24,7 @@ enum HighlightType { * Definition of a highlight object, consisting of a set of ranges, * a priority and a highlight type. * - * See https://www.w3.org/TR/css-highlight-api-1/#highlight + * See https://drafts.csswg.org/css-highlight-api-1/#highlight */ [Pref="dom.customHighlightAPI.enabled", Exposed=Window] interface Highlight { @@ -45,13 +45,13 @@ partial interface Highlight { [Throws] undefined clear(); [Throws] - undefined delete(AbstractRange range); + boolean delete(AbstractRange range); }; /** * Registry object that contains all Highlights associated with a Document. * - * See https://www.w3.org/TR/css-highlight-api-1/#highlightregistry + * See https://drafts.csswg.org/css-highlight-api-1/#highlightregistry */ [Pref="dom.customHighlightAPI.enabled", Exposed=Window] interface HighlightRegistry { @@ -67,5 +67,5 @@ partial interface HighlightRegistry { [Throws] undefined clear(); [Throws] - undefined delete(DOMString key); + boolean delete(DOMString key); }; diff --git a/extensions/spellcheck/src/mozInlineSpellChecker.cpp b/extensions/spellcheck/src/mozInlineSpellChecker.cpp index 79956fa9f34f..d144fff3171f 100644 --- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp +++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp @@ -1722,8 +1722,8 @@ nsresult mozInlineSpellChecker::IsPointInSelection(Selection& aSelection, *aRange = nullptr; nsTArray ranges; - nsresult rv = aSelection.GetRangesForIntervalArray(aNode, aOffset, aNode, - aOffset, true, &ranges); + nsresult rv = aSelection.GetDynamicRangesForIntervalArray( + aNode, aOffset, aNode, aOffset, true, &ranges); NS_ENSURE_SUCCESS(rv, rv); if (ranges.Length() == 0) return NS_OK; // no matches diff --git a/layout/generic/nsFrameSelection.cpp b/layout/generic/nsFrameSelection.cpp index 0a019af7e47d..d65387cd1e04 100644 --- a/layout/generic/nsFrameSelection.cpp +++ b/layout/generic/nsFrameSelection.cpp @@ -235,7 +235,8 @@ struct MOZ_RAII AutoPrepareFocusRange { // Scripted command or the user is starting a new explicit multi-range // selection. for (StyledRange& entry : ranges) { - entry.mRange->SetIsGenerated(false); + MOZ_ASSERT(entry.mRange->IsDynamicRange()); + entry.mRange->AsDynamicRange()->SetIsGenerated(false); } return; } @@ -280,16 +281,19 @@ struct MOZ_RAII AutoPrepareFocusRange { nsRange* result{nullptr}; if (aSelection.GetDirection() == eDirNext) { for (size_t i = 0; i < len; ++i) { - if (ranges[i].mRange->IsGenerated()) { - result = ranges[i].mRange; + // This function is only called for selections with type == eNormal. + // (see MOZ_ASSERT in constructor). + // Therefore, all ranges must be dynamic. + if (ranges[i].mRange->AsDynamicRange()->IsGenerated()) { + result = ranges[i].mRange->AsDynamicRange(); break; } } } else { size_t i = len; while (i--) { - if (ranges[i].mRange->IsGenerated()) { - result = ranges[i].mRange; + if (ranges[i].mRange->AsDynamicRange()->IsGenerated()) { + result = ranges[i].mRange->AsDynamicRange(); break; } } @@ -303,7 +307,13 @@ struct MOZ_RAII AutoPrepareFocusRange { nsTArray& ranges = aSelection.mStyledRanges.mRanges; size_t i = ranges.Length(); while (i--) { - nsRange* range = ranges[i].mRange; + // This function is only called for selections with type == eNormal. + // (see MOZ_ASSERT in constructor). + // Therefore, all ranges must be dynamic. + if (!ranges[i].mRange->IsDynamicRange()) { + continue; + } + nsRange* range = ranges[i].mRange->AsDynamicRange(); if (range->IsGenerated()) { range->UnregisterSelection(aSelection); aSelection.SelectFrames(presContext, range, false); @@ -1589,7 +1599,38 @@ void nsFrameSelection::AddHighlightSelection( } void nsFrameSelection::RemoveHighlightSelection(const nsAtom* aHighlightName) { - mHighlightSelections.Remove(aHighlightName); + if (auto maybeSelection = mHighlightSelections.MaybeGet(aHighlightName)) { + RefPtr selection = *maybeSelection; + selection->RemoveAllRanges(IgnoreErrors()); + mHighlightSelections.Remove(aHighlightName); + } +} + +void nsFrameSelection::AddHighlightSelectionRange( + const nsAtom* aHighlightName, const mozilla::dom::Highlight& aHighlight, + mozilla::dom::AbstractRange& aRange, ErrorResult& aRv) { + if (auto lookupResult = mHighlightSelections.Lookup(aHighlightName)) { + RefPtr selection = lookupResult.Data(); + selection->AddHighlightRangeAndSelectFramesAndNotifyListeners(aRange, aRv); + } else { + // if the selection does not exist yet, add all of its ranges and exit. + RefPtr selection = + aHighlight.CreateHighlightSelection(aHighlightName, this, aRv); + if (aRv.Failed()) { + return; + } + lookupResult.Data() = selection; + } +} + +void nsFrameSelection::RemoveHighlightSelectionRange( + const nsAtom* aHighlightName, mozilla::dom::AbstractRange& aRange) { + if (const auto lookupResult = mHighlightSelections.Lookup(aHighlightName)) { + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) + RefPtr selection = lookupResult.Data(); + selection->RemoveRangeAndUnselectFramesAndNotifyListeners(aRange, + IgnoreErrors()); + } } nsresult nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType, diff --git a/layout/generic/nsFrameSelection.h b/layout/generic/nsFrameSelection.h index 2797ecd1e49a..00e0dc07d79b 100644 --- a/layout/generic/nsFrameSelection.h +++ b/layout/generic/nsFrameSelection.h @@ -431,8 +431,24 @@ class nsFrameSelection final { /** * @brief Removes the Highlight selection identified by `aHighlightName`. */ - void RemoveHighlightSelection(const nsAtom* aHighlightName); + MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelection( + const nsAtom* aHighlightName); + /** + * @brief Adds a new range to the highlight selection. + * + * If there is no highlight selection for the given highlight yet, it is + * created using |AddHighlightSelection|. + */ + MOZ_CAN_RUN_SCRIPT void AddHighlightSelectionRange( + const nsAtom* aHighlightName, const mozilla::dom::Highlight& aHighlight, + mozilla::dom::AbstractRange& aRange, mozilla::ErrorResult& aRv); + + /** + * @brief Removes a range from a highlight selection. + */ + MOZ_CAN_RUN_SCRIPT void RemoveHighlightSelectionRange( + const nsAtom* aHighlightName, mozilla::dom::AbstractRange& aRange); /** * ScrollSelectionIntoView scrolls a region of the selection, * so that it is visible in the scrolled view. diff --git a/testing/web-platform/meta/css/css-highlight-api/Highlight-iteration-with-modifications.html.ini b/testing/web-platform/meta/css/css-highlight-api/Highlight-iteration-with-modifications.html.ini index 6e79780eb1f4..289f07910c6d 100644 --- a/testing/web-platform/meta/css/css-highlight-api/Highlight-iteration-with-modifications.html.ini +++ b/testing/web-platform/meta/css/css-highlight-api/Highlight-iteration-with-modifications.html.ini @@ -13,6 +13,3 @@ [Highlight iteration is not modified when the range that was pointed to by the iterator was deleted using .clear() after starting the iteration] expected: FAIL - - [Highlight iteration is not modified when a range that was already visited is deleted and there are still ranges to visit] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-highlight-api/Highlight-iteration.html.ini b/testing/web-platform/meta/css/css-highlight-api/Highlight-iteration.html.ini deleted file mode 100644 index b2607fe647b3..000000000000 --- a/testing/web-platform/meta/css/css-highlight-api/Highlight-iteration.html.ini +++ /dev/null @@ -1,30 +0,0 @@ -[Highlight-iteration.html] - [Highlight can be iterated over all of its ranges initializing the iterator with customHighlight[Symbol.iterator\]() and adding two ranges by passing them to the constructor] - expected: FAIL - - [Highlight can be iterated over all of its ranges initializing the iterator with customHighlight.values() and adding two ranges by passing them to the constructor] - expected: FAIL - - [Highlight can be iterated over all of its ranges initializing the iterator with customHighlight.keys() and adding two ranges by passing them to the constructor] - expected: FAIL - - [Highlight can be iterated over all of its ranges initializing the iterator with customHighlight[Symbol.iterator\]() and adding two ranges by passing them to the add function] - expected: FAIL - - [Highlight can be iterated over all of its ranges initializing the iterator with customHighlight.values() and adding two ranges by passing them to the add function] - expected: FAIL - - [Highlight can be iterated over all of its ranges initializing the iterator with customHighlight.keys() and adding two ranges by passing them to the add function] - expected: FAIL - - [Highlight can be iterated over all of its ranges initializing the iterator with .entries() and adding two ranges by passing them to the constructor] - expected: FAIL - - [Highlight can be iterated over all of its ranges initializing the iterator with .entries() and adding two ranges by passing them to the add function] - expected: FAIL - - [Highlight can be iterated through using forEach when it has two ranges that were added by passing them to the constructor] - expected: FAIL - - [Highlight can be iterated through using forEach when it has two ranges that were added by passing them to the add function] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-highlight-api/Highlight-setlike-tampered-Set-prototype.html.ini b/testing/web-platform/meta/css/css-highlight-api/Highlight-setlike-tampered-Set-prototype.html.ini deleted file mode 100644 index e865d22fd1b7..000000000000 --- a/testing/web-platform/meta/css/css-highlight-api/Highlight-setlike-tampered-Set-prototype.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[Highlight-setlike-tampered-Set-prototype.html] - [Highlight is a setlike interface that works as expected even if Set.prototype is tampered.] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-highlight-api/Highlight-setlike.html.ini b/testing/web-platform/meta/css/css-highlight-api/Highlight-setlike.html.ini deleted file mode 100644 index c62b2c8fff74..000000000000 --- a/testing/web-platform/meta/css/css-highlight-api/Highlight-setlike.html.ini +++ /dev/null @@ -1,54 +0,0 @@ -[Highlight-setlike.html] - [Highlight delete method works as expected (using the following combination of ranges [[object Range\], [object Range\], [object Range\]\])] - expected: FAIL - - [Highlight delete method works as expected (using the following combination of ranges [[object StaticRange\], [object StaticRange\], [object StaticRange\]\])] - expected: FAIL - - [Highlight delete method works as expected (using the following combination of ranges [[object Range\], [object StaticRange\], [object Range\]\])] - expected: FAIL - - [Highlight delete method works as expected (using the following combination of ranges [[object StaticRange\], [object Range\], [object StaticRange\]\])] - expected: FAIL - - [Highlight add and has methods work as expected (using the following combination of ranges [[object StaticRange\], [object StaticRange\], [object StaticRange\]\])] - expected: FAIL - - [Highlight constructor behaves like a set when using equal ranges (using the following combination of ranges [[object StaticRange\], [object StaticRange\], [object StaticRange\]\])] - expected: FAIL - - [Highlight constructor works as expected when called with one range (using the following combination of ranges [[object StaticRange\], [object StaticRange\], [object StaticRange\]\])] - expected: FAIL - - [Highlight constructor works as expected when called with two ranges (using the following combination of ranges [[object StaticRange\], [object StaticRange\], [object StaticRange\]\])] - expected: FAIL - - [Highlight clear method works as expected (using the following combination of ranges [[object StaticRange\], [object StaticRange\], [object StaticRange\]\])] - expected: FAIL - - [Highlight add and has methods work as expected (using the following combination of ranges [[object Range\], [object StaticRange\], [object Range\]\])] - expected: FAIL - - [Highlight constructor behaves like a set when using equal ranges (using the following combination of ranges [[object Range\], [object StaticRange\], [object Range\]\])] - expected: FAIL - - [Highlight constructor works as expected when called with two ranges (using the following combination of ranges [[object Range\], [object StaticRange\], [object Range\]\])] - expected: FAIL - - [Highlight clear method works as expected (using the following combination of ranges [[object Range\], [object StaticRange\], [object Range\]\])] - expected: FAIL - - [Highlight add and has methods work as expected (using the following combination of ranges [[object StaticRange\], [object Range\], [object StaticRange\]\])] - expected: FAIL - - [Highlight constructor behaves like a set when using equal ranges (using the following combination of ranges [[object StaticRange\], [object Range\], [object StaticRange\]\])] - expected: FAIL - - [Highlight constructor works as expected when called with one range (using the following combination of ranges [[object StaticRange\], [object Range\], [object StaticRange\]\])] - expected: FAIL - - [Highlight constructor works as expected when called with two ranges (using the following combination of ranges [[object StaticRange\], [object Range\], [object StaticRange\]\])] - expected: FAIL - - [Highlight clear method works as expected (using the following combination of ranges [[object StaticRange\], [object Range\], [object StaticRange\]\])] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-highlight-api/HighlightRegistry-maplike.html.ini b/testing/web-platform/meta/css/css-highlight-api/HighlightRegistry-maplike.html.ini deleted file mode 100644 index 7eb18f45a074..000000000000 --- a/testing/web-platform/meta/css/css-highlight-api/HighlightRegistry-maplike.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[HighlightRegistry-maplike.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [HighlightRegistry has a maplike interface.] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html.ini b/testing/web-platform/meta/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html.ini deleted file mode 100644 index 45b77c50af4e..000000000000 --- a/testing/web-platform/meta/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[custom-highlight-painting-invalidation-002.html] - expected: FAIL