Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "nsGkAtoms.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
static void UpdateDescendantsInSameTree(const nsINode& aNode,
bool aMarkDesendants) {
MOZ_ASSERT(!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled());
// don't set the Descendant bit on |aNode| itself
nsINode* node = aNode.GetNextNode(&aNode);
while (node) {
if (aMarkDesendants) {
node->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
} else {
node->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
}
if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
node = node->GetNextNode(&aNode);
} else {
// We found an ancestor of an overlapping range, skip its descendants.
node = node->GetNextNonChildNode(&aNode);
}
}
}
void AbstractRange::UpdateDescendantsInFlattenedTree(nsINode& aNode,
bool aMarkDescendants) {
MOZ_ASSERT(StaticPrefs::dom_shadowdom_selection_across_boundary_enabled());
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.
if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
UpdateDescendantsInFlattenedTree(aNode, true /* aMarkDescendants */);
} else {
UpdateDescendantsInSameTree(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()) {
if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
UpdateDescendantsInFlattenedTree(aNode, false /* aMarkDescendants */);
} else {
UpdateDescendantsInSameTree(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 &&
StaticPrefs::dom_shadowdom_selection_across_boundary_enabled();
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(
StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
? AllowRangeCrossShadowBoundary::Yes
: AllowRangeCrossShadowBoundary::No);
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