summaryrefslogtreecommitdiffstats
path: root/dom/base/AbstractRange.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/AbstractRange.cpp')
-rw-r--r--dom/base/AbstractRange.cpp204
1 files changed, 194 insertions, 10 deletions
diff --git a/dom/base/AbstractRange.cpp b/dom/base/AbstractRange.cpp
index 91234bf0a7..c9138a19d2 100644
--- a/dom/base/AbstractRange.cpp
+++ b/dom/base/AbstractRange.cpp
@@ -10,6 +10,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/RangeUtils.h"
+#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/StaticRange.h"
#include "mozilla/dom/Selection.h"
@@ -87,6 +88,29 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractRange)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegisteredClosestCommonInclusiveAncestor)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+// When aMarkDesendants is true, Set
+// DescendantOfClosestCommonInclusiveAncestorForRangeInSelection flag for the
+// flattened children of aNode. When aMarkDesendants is false, unset that flag
+// for the flattened children of aNode.
+void UpdateDescendantsInFlattenedTree(const nsIContent& aNode,
+ bool aMarkDesendants) {
+ if (!aNode.IsElement() || aNode.IsHTMLElement(nsGkAtoms::slot)) {
+ return;
+ }
+
+ FlattenedChildIterator iter(&aNode);
+ for (nsIContent* child = iter.GetNextChild(); child;
+ child = iter.GetNextChild()) {
+ if (aMarkDesendants) {
+ child->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ } else {
+ child
+ ->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
+ }
+ UpdateDescendantsInFlattenedTree(*child, aMarkDesendants);
+ }
+}
+
void AbstractRange::MarkDescendants(const nsINode& aNode) {
// Set NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection on
// aNode's descendants unless aNode is already marked as a range common
@@ -95,10 +119,22 @@ void AbstractRange::MarkDescendants(const nsINode& aNode) {
if (!aNode.IsMaybeSelected()) {
// don't set the Descendant bit on |aNode| itself
nsINode* node = aNode.GetNextNode(&aNode);
+ if (!node) {
+ if (aNode.GetShadowRootForSelection()) {
+ UpdateDescendantsInFlattenedTree(*aNode.AsContent(), true);
+ }
+ return;
+ }
while (node) {
node->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
- node = node->GetNextNode(&aNode);
+ if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
+ UpdateDescendantsInFlattenedTree(*node->AsContent(), true);
+ // sub-tree of node has been marked already
+ node = node->GetNextNonChildNode(&aNode);
+ } else {
+ node = node->GetNextNode(&aNode);
+ }
} else {
// optimize: skip this sub-tree since it's marked already.
node = node->GetNextNonChildNode(&aNode);
@@ -116,10 +152,22 @@ void AbstractRange::UnmarkDescendants(const nsINode& aNode) {
.IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
// we know |aNode| doesn't have any bit set
nsINode* node = aNode.GetNextNode(&aNode);
+ if (!node) {
+ if (aNode.GetShadowRootForSelection()) {
+ UpdateDescendantsInFlattenedTree(*aNode.AsContent(), false);
+ }
+ return;
+ }
while (node) {
node->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
- node = node->GetNextNode(&aNode);
+ if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
+ UpdateDescendantsInFlattenedTree(*node->AsContent(), false);
+ // sub-tree has been marked already
+ node = node->GetNextNonChildNode(&aNode);
+ } else {
+ node = node->GetNextNode(&aNode);
+ }
} else {
// We found an ancestor of an overlapping range, skip its descendants.
node = node->GetNextNonChildNode(&aNode);
@@ -185,10 +233,62 @@ bool AbstractRange::MaybeCacheToReuse(RangeType& aInstance) {
return true;
}
-nsINode* AbstractRange::GetClosestCommonInclusiveAncestor() const {
- return mIsPositioned ? nsContentUtils::GetClosestCommonInclusiveAncestor(
- mStart.Container(), mEnd.Container())
- : nullptr;
+nsINode* AbstractRange::GetClosestCommonInclusiveAncestor(
+ AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
+ if (!mIsPositioned) {
+ return nullptr;
+ }
+ nsINode* startContainer =
+ aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
+ ? GetMayCrossShadowBoundaryStartContainer()
+ : GetStartContainer();
+ nsINode* endContainer =
+ aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
+ ? GetMayCrossShadowBoundaryEndContainer()
+ : GetEndContainer();
+
+ if (MayCrossShadowBoundary() &&
+ aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
+ // 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
@@ -237,9 +337,28 @@ nsresult AbstractRange::SetStartAndEndInternal(
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
- // If they have different root, this should be collapsed at the end point.
+ // Different root
if (newStartRoot != newEndRoot) {
- aRange->DoSetRange(aEndBoundary, aEndBoundary, 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 (!IsRootUAWidget(newStartRoot) && !IsRootUAWidget(newEndRoot)) {
+ aRange->AsDynamicRange()
+ ->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(aStartBoundary,
+ aEndBoundary);
+ }
+ }
return NS_OK;
}
@@ -274,7 +393,10 @@ void AbstractRange::RegisterSelection(Selection& aSelection) {
bool isFirstSelection = mSelections.IsEmpty();
mSelections.AppendElement(&aSelection);
if (isFirstSelection && !mRegisteredClosestCommonInclusiveAncestor) {
- nsINode* commonAncestor = GetClosestCommonInclusiveAncestor();
+ nsINode* commonAncestor = GetClosestCommonInclusiveAncestor(
+ StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
+ ? AllowRangeCrossShadowBoundary::Yes
+ : AllowRangeCrossShadowBoundary::No);
MOZ_ASSERT(commonAncestor, "unexpected disconnected nodes");
RegisterClosestCommonInclusiveAncestor(commonAncestor);
}
@@ -358,7 +480,8 @@ void AbstractRange::UnregisterClosestCommonInclusiveAncestor(
void AbstractRange::UpdateCommonAncestorIfNecessary() {
nsINode* oldCommonAncestor = mRegisteredClosestCommonInclusiveAncestor;
- nsINode* newCommonAncestor = GetClosestCommonInclusiveAncestor();
+ nsINode* newCommonAncestor =
+ GetClosestCommonInclusiveAncestor(AllowRangeCrossShadowBoundary::Yes);
if (newCommonAncestor != oldCommonAncestor) {
if (oldCommonAncestor) {
UnregisterClosestCommonInclusiveAncestor(oldCommonAncestor, false);
@@ -379,6 +502,59 @@ void AbstractRange::UpdateCommonAncestorIfNecessary() {
}
}
+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.Container();
+}
+
+nsINode* AbstractRange::GetMayCrossShadowBoundaryEndContainer() const {
+ return IsDynamicRange()
+ ? AsDynamicRange()->GetMayCrossShadowBoundaryEndContainer()
+ : mEnd.Container();
+}
+
+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,
@@ -395,4 +571,12 @@ void AbstractRange::ClearForReuse() {
mCalledByJS = false;
}
+/*static*/
+bool AbstractRange::IsRootUAWidget(const nsINode* aRoot) {
+ MOZ_ASSERT(aRoot);
+ if (const ShadowRoot* shadowRoot = ShadowRoot::FromNode(aRoot)) {
+ return shadowRoot->IsUAWidget();
+ }
+ return false;
+}
} // namespace mozilla::dom