mirror of
https://github.com/HeyPuter/firefox.git
synced 2026-11-13 20:14:57 -10:00
Differential Revision: https://phabricator.services.mozilla.com/D293086
1043 lines
37 KiB
C++
1043 lines
37 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/dom/AbstractRange.h"
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/ContentIterator.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/RangeUtils.h"
|
|
#include "mozilla/SelectionMovementUtils.h"
|
|
#include "mozilla/dom/AbstractRangeBinding.h"
|
|
#include "mozilla/dom/ChildIterator.h"
|
|
#include "mozilla/dom/CrossShadowBoundaryRange.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/DocumentInlines.h"
|
|
#include "mozilla/dom/Selection.h"
|
|
#include "mozilla/dom/ShadowIncludingTreeIterator.h"
|
|
#include "mozilla/dom/StaticRange.h"
|
|
#include "mozilla/dom/TreeIterator.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsFmtString.h"
|
|
#include "nsINode.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsRange.h"
|
|
#include "nsTArray.h"
|
|
#include "nsTextFrame.h"
|
|
|
|
namespace mozilla::dom {
|
|
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
|
|
nsRange* aRange, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
|
|
nsRange* aRange, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
|
|
nsRange* aRange, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RawRangeBoundary& aStartBoundary,
|
|
const RawRangeBoundary& aEndBoundary, nsRange* aRange,
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
|
|
StaticRange* aRange,
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
|
|
StaticRange* aRange,
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
|
|
StaticRange* aRange,
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RawRangeBoundary& aStartBoundary,
|
|
const RawRangeBoundary& aEndBoundary, StaticRange* aRange,
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
|
|
template bool AbstractRange::MaybeCacheToReuse(nsRange& aInstance);
|
|
template bool AbstractRange::MaybeCacheToReuse(StaticRange& aInstance);
|
|
template bool AbstractRange::MaybeCacheToReuse(
|
|
CrossShadowBoundaryRange& aInstance);
|
|
|
|
bool AbstractRange::sHasShutDown = false;
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractRange)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractRange)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractRange)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AbstractRange)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractRange)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
|
|
// mStart and mEnd may depend on or be depended on some other members in
|
|
// concrete classes so that they should be unlinked in sub classes.
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
tmp->mSelections.Clear();
|
|
// Unregistering of the common inclusive ancestors would by design
|
|
// also happen when the actual implementations unlink `mStart`/`mEnd`.
|
|
// This may introduce additional overhead which is not needed when unlinking,
|
|
// therefore this is done here beforehand.
|
|
if (tmp->mRegisteredClosestCommonInclusiveAncestor) {
|
|
tmp->UnregisterClosestCommonInclusiveAncestor(IsUnlinking::Yes);
|
|
}
|
|
MOZ_DIAGNOSTIC_ASSERT(!tmp->isInList(),
|
|
"Shouldn't be registered now that we're unlinking");
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractRange)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStart)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEnd)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegisteredClosestCommonInclusiveAncestor)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
void AbstractRange::UpdateDescendantsInFlattenedTree(nsINode& aNode,
|
|
bool aMarkDescendants) {
|
|
auto UpdateDescendant = [aMarkDescendants](nsINode* node) {
|
|
if (aMarkDescendants) {
|
|
node->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
|
|
} else {
|
|
node->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
|
|
}
|
|
};
|
|
|
|
nsINode* target = &aNode;
|
|
|
|
if (target->IsDocument()) {
|
|
if (auto* rootElement = aNode.AsDocument()->GetRootElement()) {
|
|
target = rootElement;
|
|
UpdateDescendant(target);
|
|
}
|
|
}
|
|
|
|
if (!target || !target->IsContent()) {
|
|
return;
|
|
}
|
|
|
|
TreeIterator<FlattenedChildIterator> iter(*target->AsContent());
|
|
iter.GetNext(); // Skip aNode itself.
|
|
while (nsIContent* curNode = iter.GetCurrent()) {
|
|
UpdateDescendant(curNode);
|
|
if (curNode->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
|
|
iter.GetNextSkippingChildren();
|
|
} else {
|
|
iter.GetNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AbstractRange::MarkDescendants(nsINode& aNode) {
|
|
// Set NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection on
|
|
// aNode's descendants unless aNode is already marked as a range common
|
|
// ancestor or a descendant of one, in which case all of our descendants have
|
|
// the bit set already.
|
|
if (!aNode.IsMaybeSelected()) {
|
|
// If aNode has a web-exposed shadow root, use this shadow tree and ignore
|
|
// the children of aNode.
|
|
|
|
UpdateDescendantsInFlattenedTree(aNode, true /* aMarkDescendants */);
|
|
}
|
|
}
|
|
|
|
void AbstractRange::UnmarkDescendants(nsINode& aNode) {
|
|
// Unset NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection
|
|
// on aNode's descendants unless aNode is a descendant of another range common
|
|
// ancestor. Also, exclude descendants of range common ancestors (but not the
|
|
// common ancestor itself).
|
|
if (!aNode
|
|
.IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
|
|
UpdateDescendantsInFlattenedTree(aNode, false /* aMarkDescendants */);
|
|
}
|
|
}
|
|
|
|
// NOTE: If you need to change default value of members of AbstractRange,
|
|
// update nsRange::Create(nsINode* aNode) and ClearForReuse() too.
|
|
AbstractRange::AbstractRange(nsINode* aNode, bool aIsDynamicRange,
|
|
TreeKind aBoundaryTreeKind)
|
|
: mStart(aBoundaryTreeKind),
|
|
mEnd(aBoundaryTreeKind),
|
|
mRegisteredClosestCommonInclusiveAncestor(nullptr),
|
|
mIsPositioned(false),
|
|
mIsGenerated(false),
|
|
mCalledByJS(false),
|
|
mIsDynamicRange(aIsDynamicRange) {
|
|
mRefCnt.SetIsOnMainThread();
|
|
Init(aNode);
|
|
}
|
|
|
|
AbstractRange::~AbstractRange() = default;
|
|
|
|
void AbstractRange::Init(nsINode* aNode) {
|
|
MOZ_ASSERT(aNode, "range isn't in a document!");
|
|
mOwner = aNode->OwnerDoc();
|
|
}
|
|
|
|
// static
|
|
void AbstractRange::Shutdown() {
|
|
sHasShutDown = true;
|
|
if (nsTArray<RefPtr<nsRange>>* cachedRanges = nsRange::sCachedRanges) {
|
|
nsRange::sCachedRanges = nullptr;
|
|
cachedRanges->Clear();
|
|
delete cachedRanges;
|
|
}
|
|
if (nsTArray<RefPtr<StaticRange>>* cachedRanges =
|
|
StaticRange::sCachedRanges) {
|
|
StaticRange::sCachedRanges = nullptr;
|
|
cachedRanges->Clear();
|
|
delete cachedRanges;
|
|
}
|
|
if (nsTArray<RefPtr<CrossShadowBoundaryRange>>* cachedRanges =
|
|
CrossShadowBoundaryRange::sCachedRanges) {
|
|
CrossShadowBoundaryRange::sCachedRanges = nullptr;
|
|
cachedRanges->Clear();
|
|
delete cachedRanges;
|
|
}
|
|
}
|
|
|
|
// static
|
|
template <class RangeType>
|
|
bool AbstractRange::MaybeCacheToReuse(RangeType& aInstance) {
|
|
static const size_t kMaxRangeCache = 64;
|
|
|
|
// If the instance is not used by JS and the cache is not yet full, we
|
|
// should reuse it. Otherwise, delete it.
|
|
if (sHasShutDown || aInstance.GetWrapperMaybeDead() || aInstance.GetFlags() ||
|
|
(RangeType::sCachedRanges &&
|
|
RangeType::sCachedRanges->Length() == kMaxRangeCache)) {
|
|
return false;
|
|
}
|
|
|
|
aInstance.ClearForReuse();
|
|
|
|
if (!RangeType::sCachedRanges) {
|
|
RangeType::sCachedRanges = new nsTArray<RefPtr<RangeType>>(16);
|
|
}
|
|
RangeType::sCachedRanges->AppendElement(&aInstance);
|
|
return true;
|
|
}
|
|
|
|
nsINode* AbstractRange::GetClosestCommonInclusiveAncestor(
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
|
|
if (!mIsPositioned) {
|
|
return nullptr;
|
|
}
|
|
nsINode* startContainer = ShadowDOMSelectionHelpers::GetStartContainer(
|
|
this, aAllowCrossShadowBoundary);
|
|
nsINode* endContainer = ShadowDOMSelectionHelpers::GetEndContainer(
|
|
this, aAllowCrossShadowBoundary);
|
|
|
|
if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
|
|
if (startContainer == endContainer) {
|
|
return startContainer;
|
|
}
|
|
// Since both the start container and the end container are
|
|
// guaranteed to be in the same composed document.
|
|
// If one of the boundary is a document, use that document
|
|
// as the common ancestor since both nodes.
|
|
const bool oneBoundaryIsDocument =
|
|
(startContainer && startContainer->IsDocument()) ||
|
|
(endContainer && endContainer->IsDocument());
|
|
if (oneBoundaryIsDocument) {
|
|
MOZ_ASSERT_IF(
|
|
startContainer && startContainer->IsDocument(),
|
|
!endContainer || endContainer->GetComposedDoc() == startContainer);
|
|
MOZ_ASSERT_IF(
|
|
endContainer && endContainer->IsDocument(),
|
|
!startContainer || startContainer->GetComposedDoc() == endContainer);
|
|
|
|
return startContainer ? startContainer->GetComposedDoc()
|
|
: endContainer->GetComposedDoc();
|
|
}
|
|
|
|
const auto rescope = [](nsINode*& aContainer) {
|
|
if (!aContainer) {
|
|
return;
|
|
}
|
|
// RangeBoundary allows the container to be shadow roots; When
|
|
// this happens, we should use the shadow host here.
|
|
if (auto* shadowRoot = ShadowRoot::FromNode(aContainer)) {
|
|
aContainer = shadowRoot->GetHost();
|
|
return;
|
|
}
|
|
};
|
|
|
|
rescope(startContainer);
|
|
rescope(endContainer);
|
|
|
|
return nsContentUtils::GetCommonFlattenedTreeAncestorForSelection(
|
|
startContainer ? startContainer->AsContent() : nullptr,
|
|
endContainer ? endContainer->AsContent() : nullptr);
|
|
}
|
|
return nsContentUtils::GetClosestCommonInclusiveAncestor(startContainer,
|
|
endContainer);
|
|
}
|
|
|
|
// static
|
|
template <typename SPT, typename SRT, typename EPT, typename ERT,
|
|
typename RangeType>
|
|
nsresult AbstractRange::SetStartAndEndInternal(
|
|
const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
|
|
const RangeBoundaryBase<EPT, ERT>& aEndBoundary, RangeType* aRange,
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
|
if (NS_WARN_IF(!aStartBoundary.IsSet()) ||
|
|
NS_WARN_IF(!aEndBoundary.IsSet())) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsINode* newStartRoot =
|
|
RangeUtils::ComputeRootNode(aStartBoundary.GetContainer());
|
|
if (!newStartRoot) {
|
|
return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
|
|
}
|
|
if (!aStartBoundary.IsSetAndValid()) {
|
|
return NS_ERROR_DOM_INDEX_SIZE_ERR;
|
|
}
|
|
|
|
if (aStartBoundary.GetContainer() == aEndBoundary.GetContainer()) {
|
|
if (!aEndBoundary.IsSetAndValid()) {
|
|
return NS_ERROR_DOM_INDEX_SIZE_ERR;
|
|
}
|
|
// XXX: Offsets - handle this more efficiently.
|
|
// If the end offset is less than the start offset, this should be
|
|
// collapsed at the end offset.
|
|
if (*aStartBoundary.Offset(
|
|
RangeBoundaryBase<SPT, SRT>::OffsetFilter::kValidOffsets) >
|
|
*aEndBoundary.Offset(
|
|
RangeBoundaryBase<EPT, ERT>::OffsetFilter::kValidOffsets)) {
|
|
aRange->DoSetRange(aEndBoundary, aEndBoundary, newStartRoot);
|
|
} else {
|
|
aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsINode* newEndRoot =
|
|
RangeUtils::ComputeRootNode(aEndBoundary.GetContainer());
|
|
if (!newEndRoot) {
|
|
return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
|
|
}
|
|
if (!aEndBoundary.IsSetAndValid()) {
|
|
return NS_ERROR_DOM_INDEX_SIZE_ERR;
|
|
}
|
|
|
|
// Different root
|
|
if (newStartRoot != newEndRoot) {
|
|
if (aRange->IsStaticRange()) {
|
|
// StaticRange allows nodes in different trees, so set start and end
|
|
// accordingly
|
|
aRange->DoSetRange(aStartBoundary, aEndBoundary, newEndRoot);
|
|
} else {
|
|
MOZ_ASSERT(aRange->IsDynamicRange());
|
|
// In contrast, nsRange keeps both. It has a pair of start and end
|
|
// which they have been collapsed to one end, and it also may have a pair
|
|
// of start and end which are the original value.
|
|
aRange->DoSetRange(aEndBoundary, aEndBoundary, newEndRoot);
|
|
|
|
// Don't create the cross shadow bounday range if the one of the roots is
|
|
// an UA widget regardless whether the boundaries are allowed to cross
|
|
// shadow boundary or not.
|
|
if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes &&
|
|
!IsRootUAWidget(newStartRoot) && !IsRootUAWidget(newEndRoot)) {
|
|
const auto startInFlat =
|
|
aStartBoundary.AsRangeBoundaryInFlatTree(RangeBoundaryFor::Start);
|
|
const auto endInFlat =
|
|
aEndBoundary.AsRangeBoundaryInFlatTree(RangeBoundaryFor::End);
|
|
if (MOZ_UNLIKELY(!startInFlat.IsSet() || !endInFlat.IsSet())) {
|
|
NS_WARNING_ASSERTION(
|
|
!startInFlat.IsSet(),
|
|
nsFmtCString(
|
|
FMT_STRING("aStartBoundary={} could not convert to a "
|
|
"point in the flat tree"),
|
|
aStartBoundary)
|
|
.get());
|
|
NS_WARNING_ASSERTION(
|
|
!endInFlat.IsSet(),
|
|
nsFmtCString(FMT_STRING("aEndBoundary={} could not convert to a "
|
|
"point in the flat tree"),
|
|
aEndBoundary)
|
|
.get());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
aRange->AsDynamicRange()
|
|
->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(startInFlat,
|
|
endInFlat);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
const bool useFlatTree =
|
|
aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes;
|
|
const Maybe<int32_t> pointOrder =
|
|
useFlatTree ? nsContentUtils::ComparePoints<TreeKind::Flat>(
|
|
aStartBoundary, aEndBoundary)
|
|
: nsContentUtils::ComparePoints<TreeKind::ShadowIncludingDOM>(
|
|
aStartBoundary, aEndBoundary);
|
|
if (!pointOrder) {
|
|
// Safely return a value but also detected this in debug builds.
|
|
MOZ_ASSERT_UNREACHABLE("The boundaries are not connected");
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// If the end point is before the start point, this should be collapsed at
|
|
// the end point.
|
|
if (*pointOrder == 1) {
|
|
aRange->DoSetRange(aEndBoundary, aEndBoundary, newEndRoot);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Otherwise, set the range as specified. However, the order may be opposite
|
|
// in the same tree if the given range is for the flat tree. Thus, we need to
|
|
// recompute the order within the same tree if we computed the order in the
|
|
// flat tree.
|
|
if (!useFlatTree) {
|
|
aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
|
|
} else {
|
|
const Maybe<int32_t> pointOrderInSameTree =
|
|
nsContentUtils::ComparePoints<TreeKind::DOM>(aStartBoundary,
|
|
aEndBoundary);
|
|
if (MOZ_UNLIKELY(pointOrderInSameTree.isNothing())) {
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"The boundaries are not connected in the same DOM tree");
|
|
aRange->DoSetRange(aEndBoundary, aEndBoundary, newStartRoot);
|
|
} else if (*pointOrderInSameTree != 1) {
|
|
aRange->DoSetRange(aStartBoundary, aEndBoundary, newStartRoot);
|
|
} else {
|
|
aRange->DoSetRange(aEndBoundary, aStartBoundary, newStartRoot);
|
|
}
|
|
}
|
|
|
|
if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes &&
|
|
aRange->IsDynamicRange()) {
|
|
const bool isCollapsing = aStartBoundary == aEndBoundary;
|
|
const auto startInFlat = aStartBoundary
|
|
.AsRangeBoundaryInFlatTree(
|
|
isCollapsing ? RangeBoundaryFor::Collapsed
|
|
: RangeBoundaryFor::Start)
|
|
.AsRaw();
|
|
const auto endInFlat =
|
|
isCollapsing
|
|
? startInFlat
|
|
: aEndBoundary.AsRangeBoundaryInFlatTree(RangeBoundaryFor::End)
|
|
.AsRaw();
|
|
if (MOZ_UNLIKELY(!startInFlat.IsSet() || !endInFlat.IsSet())) {
|
|
NS_WARNING_ASSERTION(
|
|
!startInFlat.IsSet(),
|
|
nsFmtCString(FMT_STRING("aStartBoundary={} could not convert to a "
|
|
"point in the flat tree"),
|
|
aStartBoundary)
|
|
.get());
|
|
NS_WARNING_ASSERTION(
|
|
!endInFlat.IsSet(),
|
|
nsFmtCString(FMT_STRING("aEndBoundary={} could not convert to a "
|
|
"point in the flat tree"),
|
|
aEndBoundary)
|
|
.get());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
aRange->AsDynamicRange()->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
|
startInFlat, endInFlat);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool AbstractRange::IsInSelection(const Selection& aSelection) const {
|
|
return mSelections.Contains(&aSelection);
|
|
}
|
|
|
|
void AbstractRange::RegisterSelection(Selection& aSelection) {
|
|
if (IsInSelection(aSelection)) {
|
|
return;
|
|
}
|
|
bool isFirstSelection = mSelections.IsEmpty();
|
|
mSelections.AppendElement(&aSelection);
|
|
const bool isValidRange = !IsStaticRange() || AsStaticRange()->IsValid();
|
|
if (isFirstSelection && !mRegisteredClosestCommonInclusiveAncestor &&
|
|
isValidRange) {
|
|
nsINode* commonAncestor =
|
|
GetClosestCommonInclusiveAncestor(AllowRangeCrossShadowBoundary::Yes);
|
|
MOZ_ASSERT(commonAncestor, "unexpected disconnected nodes");
|
|
RegisterClosestCommonInclusiveAncestor(commonAncestor);
|
|
}
|
|
}
|
|
|
|
const nsTArray<WeakPtr<Selection>>& AbstractRange::GetSelections() const {
|
|
return mSelections;
|
|
}
|
|
|
|
void AbstractRange::UnregisterSelection(const Selection& aSelection,
|
|
IsUnlinking aIsUnlinking) {
|
|
mSelections.RemoveElement(&aSelection);
|
|
if (mSelections.IsEmpty() && mRegisteredClosestCommonInclusiveAncestor) {
|
|
UnregisterClosestCommonInclusiveAncestor(aIsUnlinking);
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
!mRegisteredClosestCommonInclusiveAncestor,
|
|
"How can we have a registered common ancestor when we "
|
|
"just unregistered?");
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
!isInList(),
|
|
"Shouldn't be registered if we have no "
|
|
"mRegisteredClosestCommonInclusiveAncestor after unregistering");
|
|
}
|
|
}
|
|
|
|
void AbstractRange::RegisterClosestCommonInclusiveAncestor(nsINode* aNode) {
|
|
MOZ_ASSERT(aNode, "bad arg");
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(IsInAnySelection(),
|
|
"registering range not in selection");
|
|
|
|
mRegisteredClosestCommonInclusiveAncestor = aNode;
|
|
|
|
MarkDescendants(*aNode);
|
|
|
|
UniquePtr<LinkedList<AbstractRange>>& ranges =
|
|
aNode->GetClosestCommonInclusiveAncestorRangesPtr();
|
|
if (!ranges) {
|
|
ranges = MakeUnique<LinkedList<AbstractRange>>();
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!isInList());
|
|
ranges->insertBack(this);
|
|
aNode->SetClosestCommonInclusiveAncestorForRangeInSelection();
|
|
}
|
|
|
|
void AbstractRange::UnregisterClosestCommonInclusiveAncestor(
|
|
IsUnlinking aIsUnlinking) {
|
|
if (!mRegisteredClosestCommonInclusiveAncestor) {
|
|
return;
|
|
}
|
|
nsCOMPtr oldClosestCommonInclusiveAncestor =
|
|
mRegisteredClosestCommonInclusiveAncestor;
|
|
mRegisteredClosestCommonInclusiveAncestor = nullptr;
|
|
LinkedList<AbstractRange>* ranges =
|
|
oldClosestCommonInclusiveAncestor
|
|
->GetExistingClosestCommonInclusiveAncestorRanges();
|
|
MOZ_ASSERT(ranges);
|
|
|
|
#ifdef DEBUG
|
|
bool found = false;
|
|
for (AbstractRange* range : *ranges) {
|
|
if (range == this) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
MOZ_ASSERT(found,
|
|
"We should be in the list on our registered common ancestor");
|
|
#endif // DEBUG
|
|
|
|
remove();
|
|
|
|
// We don't want to waste time unmarking flags on nodes that are
|
|
// being unlinked anyway.
|
|
if (aIsUnlinking == IsUnlinking::No && ranges->isEmpty()) {
|
|
oldClosestCommonInclusiveAncestor
|
|
->ClearClosestCommonInclusiveAncestorForRangeInSelection();
|
|
UnmarkDescendants(*oldClosestCommonInclusiveAncestor);
|
|
}
|
|
oldClosestCommonInclusiveAncestor = nullptr;
|
|
}
|
|
|
|
void AbstractRange::UpdateCommonAncestorIfNecessary() {
|
|
nsINode* oldCommonAncestor = mRegisteredClosestCommonInclusiveAncestor;
|
|
nsINode* newCommonAncestor =
|
|
GetClosestCommonInclusiveAncestor(AllowRangeCrossShadowBoundary::Yes);
|
|
if (newCommonAncestor != oldCommonAncestor) {
|
|
UnregisterClosestCommonInclusiveAncestor();
|
|
|
|
if (newCommonAncestor) {
|
|
RegisterClosestCommonInclusiveAncestor(newCommonAncestor);
|
|
} else {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mIsPositioned, "unexpected disconnected nodes");
|
|
mSelections.Clear();
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
!mRegisteredClosestCommonInclusiveAncestor,
|
|
"How can we have a registered common ancestor when we "
|
|
"didn't register ourselves?");
|
|
MOZ_DIAGNOSTIC_ASSERT(!isInList(),
|
|
"Shouldn't be registered if we have no "
|
|
"mRegisteredClosestCommonInclusiveAncestor");
|
|
}
|
|
}
|
|
}
|
|
|
|
const RangeBoundary& AbstractRange::MayCrossShadowBoundaryStartRef() const {
|
|
return IsDynamicRange() ? AsDynamicRange()->MayCrossShadowBoundaryStartRef()
|
|
: mStart;
|
|
}
|
|
|
|
const RangeBoundary& AbstractRange::MayCrossShadowBoundaryEndRef() const {
|
|
return IsDynamicRange() ? AsDynamicRange()->MayCrossShadowBoundaryEndRef()
|
|
: mEnd;
|
|
}
|
|
|
|
nsIContent* AbstractRange::GetMayCrossShadowBoundaryChildAtStartOffset() const {
|
|
return IsDynamicRange()
|
|
? AsDynamicRange()->GetMayCrossShadowBoundaryChildAtStartOffset()
|
|
: mStart.GetChildAtOffset();
|
|
}
|
|
|
|
nsIContent* AbstractRange::GetMayCrossShadowBoundaryChildAtEndOffset() const {
|
|
return IsDynamicRange()
|
|
? AsDynamicRange()->GetMayCrossShadowBoundaryChildAtEndOffset()
|
|
: mEnd.GetChildAtOffset();
|
|
}
|
|
|
|
nsINode* AbstractRange::GetMayCrossShadowBoundaryStartContainer() const {
|
|
return IsDynamicRange()
|
|
? AsDynamicRange()->GetMayCrossShadowBoundaryStartContainer()
|
|
: mStart.GetContainer();
|
|
}
|
|
|
|
nsINode* AbstractRange::GetMayCrossShadowBoundaryEndContainer() const {
|
|
return IsDynamicRange()
|
|
? AsDynamicRange()->GetMayCrossShadowBoundaryEndContainer()
|
|
: mEnd.GetContainer();
|
|
}
|
|
|
|
bool AbstractRange::MayCrossShadowBoundary() const {
|
|
return IsDynamicRange() ? !!AsDynamicRange()->GetCrossShadowBoundaryRange()
|
|
: false;
|
|
}
|
|
|
|
uint32_t AbstractRange::MayCrossShadowBoundaryStartOffset() const {
|
|
return IsDynamicRange()
|
|
? AsDynamicRange()->MayCrossShadowBoundaryStartOffset()
|
|
: static_cast<uint32_t>(*mStart.Offset(
|
|
RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
|
|
}
|
|
|
|
uint32_t AbstractRange::MayCrossShadowBoundaryEndOffset() const {
|
|
return IsDynamicRange()
|
|
? AsDynamicRange()->MayCrossShadowBoundaryEndOffset()
|
|
: static_cast<uint32_t>(*mEnd.Offset(
|
|
RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
|
|
}
|
|
|
|
nsINode* AbstractRange::GetParentObject() const { return mOwner; }
|
|
|
|
JSObject* AbstractRange::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
MOZ_CRASH("Must be overridden");
|
|
}
|
|
|
|
bool AbstractRange::AreNormalRangeAndCrossShadowBoundaryRangeCollapsed() const {
|
|
if (!Collapsed()) {
|
|
return false;
|
|
}
|
|
|
|
// We know normal range is collapsed at this point
|
|
if (IsStaticRange()) {
|
|
return true;
|
|
}
|
|
|
|
if (const CrossShadowBoundaryRange* crossShadowBoundaryRange =
|
|
AsDynamicRange()->GetCrossShadowBoundaryRange()) {
|
|
return crossShadowBoundaryRange->Collapsed();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AbstractRange::ClearForReuse() {
|
|
mOwner = nullptr;
|
|
mStart = RangeBoundary(mStart.GetTreeKind());
|
|
mEnd = RangeBoundary(mEnd.GetTreeKind());
|
|
mIsPositioned = false;
|
|
mIsGenerated = false;
|
|
mCalledByJS = false;
|
|
}
|
|
|
|
/*static*/
|
|
bool AbstractRange::IsRootUAWidget(const nsINode* aRoot) {
|
|
MOZ_ASSERT(aRoot);
|
|
if (const ShadowRoot* shadowRoot = ShadowRoot::FromNode(aRoot)) {
|
|
return shadowRoot->IsUAWidget();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
already_AddRefed<StaticRange> AbstractRange::GetShrunkenRangeToVisibleLeaves()
|
|
const {
|
|
if (NS_WARN_IF(!IsPositioned()) || NS_WARN_IF(Collapsed()) ||
|
|
NS_WARN_IF(IsStaticRange() && !AsStaticRange()->IsValid())) {
|
|
return nullptr;
|
|
}
|
|
|
|
const RawRangeBoundary startBoundary =
|
|
SelectionMovementUtils::GetFirstVisiblePointAtLeaf(*this);
|
|
if (MOZ_UNLIKELY(!startBoundary.IsSet())) {
|
|
return nullptr;
|
|
}
|
|
const RawRangeBoundary endBoundary =
|
|
SelectionMovementUtils::GetLastVisiblePointAtLeaf(*this);
|
|
if (MOZ_UNLIKELY(!endBoundary.IsSet())) {
|
|
return nullptr;
|
|
}
|
|
IgnoredErrorResult error;
|
|
RefPtr<StaticRange> range =
|
|
StaticRange::Create(startBoundary, endBoundary, error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
error.SuppressException();
|
|
return nullptr;
|
|
}
|
|
return range.forget();
|
|
}
|
|
|
|
static void ExtractRectFromOffset(nsIFrame* aFrame, const int32_t aOffset,
|
|
nsRect* aR, bool aFlushToOriginEdge,
|
|
bool aClampToEdge) {
|
|
MOZ_ASSERT(aFrame);
|
|
MOZ_ASSERT(aR);
|
|
|
|
nsPoint point;
|
|
aFrame->GetPointFromOffset(aOffset, &point);
|
|
|
|
// Determine if aFrame has a vertical writing mode, which will change our math
|
|
// on the output rect.
|
|
bool isVertical = aFrame->GetWritingMode().IsVertical();
|
|
|
|
if (!aClampToEdge && !aR->Contains(point)) {
|
|
// If point is outside aR, and we aren't clamping, output an empty rect
|
|
// with origin at the point.
|
|
if (isVertical) {
|
|
aR->SetHeight(0);
|
|
aR->y = point.y;
|
|
} else {
|
|
aR->SetWidth(0);
|
|
aR->x = point.x;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (aClampToEdge) {
|
|
point = aR->ClampPoint(point);
|
|
}
|
|
|
|
// point is within aR, and now we'll modify aR to output a rect that has point
|
|
// on one edge. But which edge?
|
|
if (aFlushToOriginEdge) {
|
|
// The output rect should be flush to the edge of aR that contains the
|
|
// origin.
|
|
if (isVertical) {
|
|
aR->SetHeight(point.y - aR->y);
|
|
} else {
|
|
aR->SetWidth(point.x - aR->x);
|
|
}
|
|
} else {
|
|
// The output rect should be flush to the edge of aR opposite the origin.
|
|
if (isVertical) {
|
|
aR->SetHeight(aR->YMost() - point.y);
|
|
aR->y = point.y;
|
|
} else {
|
|
aR->SetWidth(aR->XMost() - point.x);
|
|
aR->x = point.x;
|
|
}
|
|
}
|
|
}
|
|
|
|
static nsTextFrame* GetTextFrameForContent(nsIContent* aContent) {
|
|
return do_QueryFrame(aContent->GetPrimaryFrame());
|
|
}
|
|
|
|
static void GetPartialTextRect(RectCallback* aCallback,
|
|
Sequence<nsString>* aTextList,
|
|
nsIContent* aContent, int32_t aStartOffset,
|
|
int32_t aEndOffset, bool aClampToEdge) {
|
|
nsTextFrame* textFrame = GetTextFrameForContent(aContent);
|
|
if (!textFrame) {
|
|
return;
|
|
}
|
|
nsIFrame* relativeTo =
|
|
nsLayoutUtils::GetContainingBlockForClientRect(textFrame);
|
|
|
|
for (nsTextFrame* f = textFrame->FindContinuationForOffset(aStartOffset); f;
|
|
f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
|
|
int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd();
|
|
if (fend <= aStartOffset) {
|
|
continue;
|
|
}
|
|
if (fstart >= aEndOffset) {
|
|
break;
|
|
}
|
|
|
|
// Calculate the text content offsets we'll need if text is requested.
|
|
int32_t textContentStart = fstart;
|
|
int32_t textContentEnd = fend;
|
|
|
|
// overlapping with the offset we want
|
|
f->EnsureTextRun(nsTextFrame::eInflated);
|
|
gfxTextRun* run = f->GetTextRun(nsTextFrame::eInflated);
|
|
if (NS_WARN_IF(!run)) {
|
|
continue;
|
|
}
|
|
bool topLeftToBottomRight = !run->IsInlineReversed();
|
|
nsRect r = f->GetRectRelativeToSelf();
|
|
if (fstart < aStartOffset) {
|
|
// aStartOffset is within this frame
|
|
ExtractRectFromOffset(f, aStartOffset, &r, !topLeftToBottomRight,
|
|
aClampToEdge);
|
|
textContentStart = aStartOffset;
|
|
}
|
|
if (fend > aEndOffset) {
|
|
// aEndOffset is in the middle of this frame
|
|
ExtractRectFromOffset(f, aEndOffset, &r, topLeftToBottomRight,
|
|
aClampToEdge);
|
|
textContentEnd = aEndOffset;
|
|
}
|
|
r = nsLayoutUtils::TransformFrameRectToAncestor(f, r, relativeTo);
|
|
aCallback->AddRect(r);
|
|
|
|
// Finally capture the text, if requested.
|
|
if (aTextList) {
|
|
nsIFrame::RenderedText renderedText =
|
|
f->GetRenderedText(textContentStart, textContentEnd,
|
|
nsIFrame::TextOffsetType::OffsetsInContentText,
|
|
nsIFrame::TrailingWhitespace::DontTrim);
|
|
|
|
if (!aTextList->AppendElement(renderedText.mString, fallible)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CollectClientRectsForSubtree(
|
|
nsINode* aNode, RectCallback* aCollector, Sequence<nsString>* aTextList,
|
|
nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
|
|
uint32_t aEndOffset, bool aClampToEdge, bool aTextOnly) {
|
|
auto* content = nsIContent::FromNode(aNode);
|
|
if (!content) {
|
|
return;
|
|
}
|
|
|
|
const bool isText = content->IsText();
|
|
if (isText) {
|
|
if (aNode == aStartContainer) {
|
|
int32_t offset = aStartContainer == aEndContainer
|
|
? static_cast<int32_t>(aEndOffset)
|
|
: content->AsText()->TextDataLength();
|
|
GetPartialTextRect(aCollector, aTextList, content,
|
|
static_cast<int32_t>(aStartOffset), offset,
|
|
aClampToEdge);
|
|
return;
|
|
}
|
|
|
|
if (aNode == aEndContainer) {
|
|
GetPartialTextRect(aCollector, aTextList, content, 0,
|
|
static_cast<int32_t>(aEndOffset), aClampToEdge);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (nsIFrame* frame = content->GetPrimaryFrame()) {
|
|
if (!aTextOnly || isText) {
|
|
nsLayoutUtils::GetAllInFlowRectsAndTexts(
|
|
frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
|
|
aCollector, aTextList,
|
|
nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
|
|
if (isText) {
|
|
return;
|
|
}
|
|
aTextOnly = true;
|
|
// We just get the text when calling GetAllInFlowRectsAndTexts, so we
|
|
// don't need to call it again when visiting the children.
|
|
aTextList = nullptr;
|
|
}
|
|
} else if (!content->IsElement() ||
|
|
!content->AsElement()->IsDisplayContents()) {
|
|
return;
|
|
}
|
|
|
|
FlattenedChildIterator childIter(content);
|
|
for (nsIContent* child = childIter.GetNextChild(); child;
|
|
child = childIter.GetNextChild()) {
|
|
CollectClientRectsForSubtree(child, aCollector, aTextList, aStartContainer,
|
|
aStartOffset, aEndContainer, aEndOffset,
|
|
aClampToEdge, aTextOnly);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void AbstractRange::CollectClientRectsAndText(
|
|
RectCallback* aCollector, Sequence<nsString>* aTextList,
|
|
AbstractRange* aRange, nsINode* aStartContainer, uint32_t aStartOffset,
|
|
nsINode* aEndContainer, uint32_t aEndOffset, bool aClampToEdge,
|
|
bool aFlushLayout) {
|
|
// Currently, this method is called with start of end offset of AbstractRange.
|
|
// So, they must be between 0 - INT32_MAX.
|
|
MOZ_ASSERT(RangeUtils::IsValidOffset(aStartOffset));
|
|
MOZ_ASSERT(RangeUtils::IsValidOffset(aEndOffset));
|
|
|
|
// Hold strong pointers across the flush
|
|
nsCOMPtr<nsINode> startContainer = aStartContainer;
|
|
nsCOMPtr<nsINode> endContainer = aEndContainer;
|
|
|
|
// Flush out layout so our frames are up to date.
|
|
if (!aStartContainer->IsInComposedDoc()) {
|
|
return;
|
|
}
|
|
|
|
if (aFlushLayout) {
|
|
if (auto* content = nsIContent::FromNode(aStartContainer)) {
|
|
content->GetPrimaryFrame(FlushType::Layout);
|
|
} else {
|
|
aStartContainer->OwnerDoc()->FlushPendingNotifications(FlushType::Layout);
|
|
}
|
|
// Recheck whether we're still in the document
|
|
if (!aStartContainer->IsInComposedDoc()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
RangeSubtreeIterator iter;
|
|
|
|
if (NS_FAILED(iter.Init(aRange))) {
|
|
return;
|
|
}
|
|
|
|
if (iter.IsDone()) {
|
|
// the range is collapsed, only continue if the cursor is in a text node
|
|
if (!aStartContainer->IsText()) {
|
|
return;
|
|
}
|
|
nsTextFrame* textFrame = GetTextFrameForContent(aStartContainer->AsText());
|
|
if (!textFrame) {
|
|
return;
|
|
}
|
|
int32_t outOffset = 0;
|
|
nsIFrame* outFrame = nullptr;
|
|
textFrame->GetChildFrameContainingOffset(static_cast<int32_t>(aStartOffset),
|
|
false, &outOffset, &outFrame);
|
|
if (!outFrame) {
|
|
return;
|
|
}
|
|
nsIFrame* relativeTo =
|
|
nsLayoutUtils::GetContainingBlockForClientRect(outFrame);
|
|
nsRect r = outFrame->GetRectRelativeToSelf();
|
|
ExtractRectFromOffset(outFrame, static_cast<int32_t>(aStartOffset), &r,
|
|
false, aClampToEdge);
|
|
r.SetWidth(0);
|
|
r = nsLayoutUtils::TransformFrameRectToAncestor(outFrame, r, relativeTo);
|
|
aCollector->AddRect(r);
|
|
return;
|
|
}
|
|
|
|
do {
|
|
nsCOMPtr<nsINode> node = iter.GetCurrentNode();
|
|
iter.Next();
|
|
|
|
CollectClientRectsForSubtree(node, aCollector, aTextList, aStartContainer,
|
|
aStartOffset, aEndContainer, aEndOffset,
|
|
aClampToEdge, false);
|
|
} while (!iter.IsDone());
|
|
}
|
|
|
|
already_AddRefed<DOMRect> AbstractRange::GetBoundingClientRect(
|
|
bool aClampToEdge, bool aFlushLayout) {
|
|
RefPtr<DOMRect> rect = new DOMRect(ToSupports(mOwner));
|
|
if (!mIsPositioned) {
|
|
return rect.forget();
|
|
}
|
|
|
|
nsLayoutUtils::RectAccumulator accumulator;
|
|
CollectClientRectsAndText(
|
|
&accumulator, nullptr, this, mStart.GetContainer(),
|
|
*mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
|
|
mEnd.GetContainer(),
|
|
*mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
|
|
aFlushLayout);
|
|
|
|
nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
|
|
: accumulator.mResultRect;
|
|
rect->SetLayoutRect(r);
|
|
return rect.forget();
|
|
}
|
|
|
|
already_AddRefed<DOMRectList> AbstractRange::GetClientRects(bool aClampToEdge,
|
|
bool aFlushLayout) {
|
|
return GetClientRectsInner(AllowRangeCrossShadowBoundary::No, aClampToEdge,
|
|
aFlushLayout);
|
|
}
|
|
|
|
already_AddRefed<DOMRectList>
|
|
AbstractRange::GetAllowCrossShadowBoundaryClientRects(bool aClampToEdge,
|
|
bool aFlushLayout) {
|
|
return GetClientRectsInner(AllowRangeCrossShadowBoundary::Yes, aClampToEdge,
|
|
aFlushLayout);
|
|
}
|
|
|
|
void AbstractRange::CollectClientRects(RectCallback& aCallback,
|
|
bool aClampToEdge) const {
|
|
if (!mIsPositioned) {
|
|
return;
|
|
}
|
|
CollectClientRectsAndText(
|
|
&aCallback, nullptr, const_cast<AbstractRange*>(this),
|
|
mStart.GetContainer(),
|
|
*mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
|
|
mEnd.GetContainer(),
|
|
*mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
|
|
/* aFlushLayout */ false);
|
|
}
|
|
|
|
already_AddRefed<DOMRectList> AbstractRange::GetClientRectsInner(
|
|
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundaryRange,
|
|
bool aClampToEdge, bool aFlushLayout) {
|
|
if (!mIsPositioned) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<DOMRectList> rectList = new DOMRectList(ToSupports(mOwner));
|
|
|
|
nsLayoutUtils::RectListBuilder builder(rectList);
|
|
|
|
const auto& startRef =
|
|
aAllowCrossShadowBoundaryRange == AllowRangeCrossShadowBoundary::Yes
|
|
? MayCrossShadowBoundaryStartRef()
|
|
: mStart;
|
|
const auto& endRef =
|
|
aAllowCrossShadowBoundaryRange == AllowRangeCrossShadowBoundary::Yes
|
|
? MayCrossShadowBoundaryEndRef()
|
|
: mEnd;
|
|
|
|
CollectClientRectsAndText(
|
|
&builder, nullptr, this, startRef.GetContainer(),
|
|
*startRef.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
|
|
endRef.GetContainer(),
|
|
*endRef.Offset(RangeBoundary::OffsetFilter::kValidOffsets), aClampToEdge,
|
|
aFlushLayout);
|
|
return rectList.forget();
|
|
}
|
|
|
|
void AbstractRange::GetClientRectsAndTexts(
|
|
mozilla::dom::ClientRectsAndTexts& aResult, ErrorResult& aErr) {
|
|
if (!mIsPositioned) {
|
|
return;
|
|
}
|
|
|
|
aResult.mRectList = new DOMRectList(ToSupports(mOwner));
|
|
|
|
nsLayoutUtils::RectListBuilder builder(aResult.mRectList);
|
|
|
|
CollectClientRectsAndText(
|
|
&builder, &aResult.mTextList, this, mStart.GetContainer(),
|
|
*mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets),
|
|
mEnd.GetContainer(),
|
|
*mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets), true, true);
|
|
}
|
|
|
|
} // namespace mozilla::dom
|