Bug 1867058 - Part 8: Update Selection to support across shadow dom selection r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D195310
This commit is contained in:
Sean Feng 2024-03-25 13:40:59 +00:00
parent b411788f91
commit 2026f2e0ce
9 changed files with 192 additions and 76 deletions

View file

@ -378,7 +378,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);
}

View file

@ -147,9 +147,10 @@ nsresult RangeUtils::CompareNodeToRange(nsINode* aNode,
NS_WARN_IF(!aAbstractRange->IsPositioned())) {
return NS_ERROR_INVALID_ARG;
}
return CompareNodeToRangeBoundaries(aNode, aAbstractRange->StartRef(),
aAbstractRange->EndRef(),
aNodeIsBeforeRange, aNodeIsAfterRange);
return CompareNodeToRangeBoundaries(
aNode, aAbstractRange->MayCrossShadowBoundaryStartRef(),
aAbstractRange->MayCrossShadowBoundaryEndRef(), aNodeIsBeforeRange,
aNodeIsAfterRange);
}
template <typename SPT, typename SRT, typename EPT, typename ERT>
nsresult RangeUtils::CompareNodeToRangeBoundaries(

View file

@ -22,6 +22,7 @@
#include "mozilla/CaretAssociationHint.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/SelectionBinding.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/StaticRange.h"
@ -778,30 +779,39 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection)
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Selection, Disconnect())
const RangeBoundary& Selection::AnchorRef() const {
const RangeBoundary& Selection::AnchorRef(
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
if (!mAnchorFocusRange) {
static RangeBoundary sEmpty;
return sEmpty;
}
if (GetDirection() == eDirNext) {
return mAnchorFocusRange->StartRef();
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
? mAnchorFocusRange->MayCrossShadowBoundaryStartRef()
: mAnchorFocusRange->StartRef();
}
return mAnchorFocusRange->EndRef();
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
? mAnchorFocusRange->MayCrossShadowBoundaryEndRef()
: mAnchorFocusRange->EndRef();
}
const RangeBoundary& Selection::FocusRef() const {
const RangeBoundary& Selection::FocusRef(
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
if (!mAnchorFocusRange) {
static RangeBoundary sEmpty;
return sEmpty;
}
if (GetDirection() == eDirNext) {
return mAnchorFocusRange->EndRef();
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
? mAnchorFocusRange->MayCrossShadowBoundaryEndRef()
: mAnchorFocusRange->EndRef();
}
return mAnchorFocusRange->StartRef();
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
? mAnchorFocusRange->MayCrossShadowBoundaryStartRef()
: mAnchorFocusRange->StartRef();
}
void Selection::SetAnchorFocusRange(size_t aIndex) {
@ -818,8 +828,8 @@ static int32_t CompareToRangeStart(const nsINode& aCompareNode,
uint32_t aCompareOffset,
const AbstractRange& aRange,
nsContentUtils::NodeIndexCache* aCache) {
MOZ_ASSERT(aRange.GetStartContainer());
nsINode* start = aRange.GetStartContainer();
MOZ_ASSERT(aRange.GetMayCrossShadowBoundaryStartContainer());
nsINode* start = aRange.GetMayCrossShadowBoundaryStartContainer();
// If the nodes that we're comparing are not in the same document, assume that
// aCompareNode will fall at the end of the ranges.
if (aCompareNode.GetComposedDoc() != start->GetComposedDoc() ||
@ -830,8 +840,9 @@ static int32_t CompareToRangeStart(const nsINode& aCompareNode,
}
// The points are in the same subtree, hence there has to be an order.
return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, start,
aRange.StartOffset(), aCache);
return *nsContentUtils::ComparePoints(
&aCompareNode, aCompareOffset, start,
aRange.MayCrossShadowBoundaryStartOffset(), aCache);
}
static int32_t CompareToRangeStart(const nsINode& aCompareNode,
@ -844,7 +855,7 @@ static int32_t CompareToRangeEnd(const nsINode& aCompareNode,
uint32_t aCompareOffset,
const AbstractRange& aRange) {
MOZ_ASSERT(aRange.IsPositioned());
nsINode* end = aRange.GetEndContainer();
nsINode* end = aRange.GetMayCrossShadowBoundaryEndContainer();
// If the nodes that we're comparing are not in the same document or in the
// same subtree, assume that aCompareNode will fall at the end of the ranges.
if (aCompareNode.GetComposedDoc() != end->GetComposedDoc() ||
@ -855,8 +866,9 @@ static int32_t CompareToRangeEnd(const nsINode& aCompareNode,
}
// The points are in the same subtree, hence there has to be an order.
return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, end,
aRange.EndOffset());
return *nsContentUtils::ComparePoints(
&aCompareNode, aCompareOffset, end,
aRange.MayCrossShadowBoundaryEndOffset());
}
// static
@ -1616,7 +1628,8 @@ nsresult Selection::StyledRanges::GetIndicesForInterval(
// the given interval's start point, but that range isn't collapsed (a
// collapsed range should be included in the returned results).
const AbstractRange* beginRange = mRanges[beginsAfterIndex].mRange;
if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset) &&
if (beginRange->MayCrossShadowBoundaryEndRef().Equals(aBeginNode,
aBeginOffset) &&
!beginRange->Collapsed()) {
beginsAfterIndex++;
}
@ -1627,7 +1640,8 @@ nsresult Selection::StyledRanges::GetIndicesForInterval(
// included
if (endsBeforeIndex < mRanges.Length()) {
const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
if (endRange->StartRef().Equals(aEndNode, aEndOffset) &&
if (endRange->MayCrossShadowBoundaryStartRef().Equals(aEndNode,
aEndOffset) &&
endRange->Collapsed()) {
endsBeforeIndex++;
}
@ -1748,16 +1762,22 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
if (mFrameSelection->IsInTableSelectionMode()) {
const nsIContent* const commonAncestorContent =
nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor());
nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor(
StaticPrefs::dom_select_events_textcontrols_selectstart_enabled()
? AllowRangeCrossShadowBoundary::Yes
: AllowRangeCrossShadowBoundary::No));
nsIFrame* const frame = commonAncestorContent
? commonAncestorContent->GetPrimaryFrame()
: aPresContext->PresShell()->GetRootFrame();
if (frame) {
if (frame->IsTextFrame()) {
MOZ_ASSERT(commonAncestorContent == aRange.GetStartContainer());
MOZ_ASSERT(commonAncestorContent == aRange.GetEndContainer());
MOZ_ASSERT(commonAncestorContent ==
aRange.GetMayCrossShadowBoundaryStartContainer());
MOZ_ASSERT(commonAncestorContent ==
aRange.GetMayCrossShadowBoundaryEndContainer());
static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
aRange.StartOffset(), aRange.EndOffset(), aSelect, mSelectionType);
aRange.MayCrossShadowBoundaryStartOffset(),
aRange.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType);
} else {
frame->SelectionStateChanged();
}
@ -1768,8 +1788,8 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
// Loop through the content iterator for each content node; for each text
// node, call SetSelected on it:
nsIContent* const startContent =
nsIContent::FromNodeOrNull(aRange.GetStartContainer());
nsIContent* const startContent = nsIContent::FromNodeOrNull(
aRange.GetMayCrossShadowBoundaryStartContainer());
if (MOZ_UNLIKELY(!startContent)) {
// Don't warn, bug 1055722
// XXX The range can start from a document node and such range can be
@ -1780,7 +1800,7 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
MOZ_DIAGNOSTIC_ASSERT(startContent->IsInComposedDoc());
// We must call first one explicitly
nsINode* const endNode = aRange.GetEndContainer();
nsINode* const endNode = aRange.GetMayCrossShadowBoundaryEndContainer();
if (NS_WARN_IF(!endNode)) {
// We null-checked start node above, therefore, end node should also be
// non-null here.
@ -1792,10 +1812,10 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
// The frame could be an SVG text frame, in which case we don't treat it
// as a text frame.
if (frame->IsTextFrame()) {
const uint32_t startOffset = aRange.StartOffset();
const uint32_t endOffset = endNode == startContent
? aRange.EndOffset()
: startContent->Length();
const uint32_t startOffset = aRange.MayCrossShadowBoundaryStartOffset();
const uint32_t endOffset =
endNode == startContent ? aRange.MayCrossShadowBoundaryEndOffset()
: startContent->Length();
static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
startOffset, endOffset, aSelect, mSelectionType);
} else {
@ -1806,7 +1826,7 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
// If the range is in a node and the node is a leaf node, we don't need to
// walk the subtree.
if (aRange.Collapsed() ||
if ((aRange.Collapsed() && !aRange.MayCrossShadowBoundary()) ||
(startContent == endNode && !startContent->HasChildren())) {
if (!isFirstContentTextNode) {
SelectFramesOf(startContent, aSelect);
@ -1815,7 +1835,7 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
}
ContentSubtreeIterator subtreeIter;
subtreeIter.Init(&aRange);
subtreeIter.InitWithAllowCrossShadowBoundary(&aRange);
if (isFirstContentTextNode && !subtreeIter.IsDone() &&
subtreeIter.GetCurrentNode() == startContent) {
subtreeIter.Next(); // first content has already been handled.
@ -1839,7 +1859,7 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
// The frame could be an SVG text frame, in which case we'll ignore it.
if (frame->IsTextFrame()) {
static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
0, aRange.EndOffset(), aSelect, mSelectionType);
0, aRange.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType);
}
}
return NS_OK;
@ -1901,10 +1921,11 @@ UniquePtr<SelectionDetails> Selection::LookUpSelection(
if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) {
continue;
}
nsINode* startNode = range->GetStartContainer();
nsINode* endNode = range->GetEndContainer();
uint32_t startOffset = range->StartOffset();
uint32_t endOffset = range->EndOffset();
nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer();
nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer();
uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset();
uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset();
Maybe<uint32_t> start, end;
if (startNode == aContent && endNode == aContent) {
@ -2914,17 +2935,17 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
#ifdef DEBUG_SELECTION
nsDirection oldDirection = GetDirection();
#endif
nsINode* anchorNode = GetAnchorNode();
nsINode* focusNode = GetFocusNode();
const uint32_t anchorOffset = AnchorOffset();
const uint32_t focusOffset = FocusOffset();
nsINode* anchorNode = GetMayCrossShadowBoundaryAnchorNode();
nsINode* focusNode = GetMayCrossShadowBoundaryFocusNode();
const uint32_t anchorOffset = MayCrossShadowBoundaryAnchorOffset();
const uint32_t focusOffset = MayCrossShadowBoundaryFocusOffset();
RefPtr<nsRange> range = mAnchorFocusRange->CloneRange();
nsINode* startNode = range->GetStartContainer();
nsINode* endNode = range->GetEndContainer();
const uint32_t startOffset = range->StartOffset();
const uint32_t endOffset = range->EndOffset();
nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer();
nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer();
const uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset();
const uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset();
bool shouldClearRange = false;
const Maybe<int32_t> anchorOldFocusOrder = nsContentUtils::ComparePoints(
@ -2960,7 +2981,8 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
(*anchorOldFocusOrder <= 0 &&
*oldFocusNewFocusOrder < 0)) { // a1,2 a,1,2
// select from 1 to 2 unless they are collapsed
range->SetEnd(aContainer, aOffset, aRv);
range->SetEnd(aContainer, aOffset, aRv,
AllowRangeCrossShadowBoundary::Yes);
if (aRv.Failed()) {
return;
}
@ -2981,7 +3003,8 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
*anchorNewFocusOrder > 0) { // 2, a1
// select from 2 to 1a
SetDirection(eDirPrevious);
range->SetStart(aContainer, aOffset, aRv);
range->SetStart(aContainer, aOffset, aRv,
AllowRangeCrossShadowBoundary::Yes);
if (aRv.Failed()) {
return;
}
@ -3001,7 +3024,8 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
return;
}
range->SetEnd(aContainer, aOffset, aRv);
range->SetEnd(aContainer, aOffset, aRv,
AllowRangeCrossShadowBoundary::Yes);
if (aRv.Failed()) {
return;
}
@ -3011,27 +3035,33 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
return;
}
SelectFrames(presContext, *difRange, false); // deselect now
difRange->SetEnd(range->GetEndContainer(), range->EndOffset());
difRange->SetEnd(range->GetMayCrossShadowBoundaryEndContainer(),
range->MayCrossShadowBoundaryEndOffset(),
AllowRangeCrossShadowBoundary::Yes);
SelectFrames(presContext, *difRange, true); // must reselect last node
// maybe more
} else if (*anchorOldFocusOrder >= 0 &&
*anchorNewFocusOrder <= 0) { // 1,a,2 or 1a,2 or 1,a2 or 1a2
if (GetDirection() == eDirPrevious) {
res = range->SetStart(endNode, endOffset);
res = range->SetStart(endNode, endOffset,
AllowRangeCrossShadowBoundary::Yes);
if (NS_FAILED(res)) {
aRv.Throw(res);
return;
}
}
SetDirection(eDirNext);
range->SetEnd(aContainer, aOffset, aRv);
range->SetEnd(aContainer, aOffset, aRv,
AllowRangeCrossShadowBoundary::Yes);
if (aRv.Failed()) {
return;
}
if (focusNode != anchorNode ||
focusOffset != anchorOffset) { // if collapsed diff dont do anything
res = difRange->SetStart(focusNode, focusOffset);
nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset);
res = difRange->SetStart(focusNode, focusOffset,
AllowRangeCrossShadowBoundary::Yes);
nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset,
AllowRangeCrossShadowBoundary::Yes);
if (NS_FAILED(tmp)) {
res = tmp;
}
@ -3065,7 +3095,8 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
return;
}
SetDirection(eDirPrevious);
range->SetStart(aContainer, aOffset, aRv);
range->SetStart(aContainer, aOffset, aRv,
AllowRangeCrossShadowBoundary::Yes);
if (aRv.Failed()) {
return;
}
@ -3076,15 +3107,19 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
return;
}
SelectFrames(presContext, *difRange, false);
difRange->SetStart(range->GetStartContainer(), range->StartOffset());
difRange->SetStart(range->GetMayCrossShadowBoundaryStartContainer(),
range->MayCrossShadowBoundaryStartOffset(),
AllowRangeCrossShadowBoundary::Yes);
SelectFrames(presContext, *difRange, true); // must reselect last node
} else if (*anchorNewFocusOrder >= 0 &&
*anchorOldFocusOrder <= 0) { // 2,a,1 or 2a,1 or 2,a1 or 2a1
if (GetDirection() == eDirNext) {
range->SetEnd(startNode, startOffset);
range->SetEnd(startNode, startOffset,
AllowRangeCrossShadowBoundary::Yes);
}
SetDirection(eDirPrevious);
range->SetStart(aContainer, aOffset, aRv);
range->SetStart(aContainer, aOffset, aRv,
AllowRangeCrossShadowBoundary::Yes);
if (aRv.Failed()) {
return;
}
@ -3114,7 +3149,8 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
} else if (*oldFocusNewFocusOrder >= 0 &&
*anchorOldFocusOrder >= 0) { // 2,1,a or 21,a or 2,1a or 21a
// select from 2 to 1
range->SetStart(aContainer, aOffset, aRv);
range->SetStart(aContainer, aOffset, aRv,
AllowRangeCrossShadowBoundary::Yes);
if (aRv.Failed()) {
return;
}

View file

@ -64,6 +64,9 @@ namespace dom {
class Selection final : public nsSupportsWeakReference,
public nsWrapperCache,
public SupportsWeakPtr {
using AllowRangeCrossShadowBoundary =
mozilla::dom::AllowRangeCrossShadowBoundary;
protected:
virtual ~Selection();
@ -327,6 +330,30 @@ class Selection final : public nsSupportsWeakReference,
return offset ? *offset : 0;
}
nsINode* GetMayCrossShadowBoundaryAnchorNode() const {
const RangeBoundary& anchor = AnchorRef(AllowRangeCrossShadowBoundary::Yes);
return anchor.IsSet() ? anchor.Container() : nullptr;
}
uint32_t MayCrossShadowBoundaryAnchorOffset() const {
const RangeBoundary& anchor = AnchorRef(AllowRangeCrossShadowBoundary::Yes);
const Maybe<uint32_t> offset =
anchor.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
return offset ? *offset : 0;
}
nsINode* GetMayCrossShadowBoundaryFocusNode() const {
const RangeBoundary& focus = FocusRef(AllowRangeCrossShadowBoundary::Yes);
return focus.IsSet() ? focus.Container() : nullptr;
}
uint32_t MayCrossShadowBoundaryFocusOffset() const {
const RangeBoundary& focus = FocusRef(AllowRangeCrossShadowBoundary::Yes);
const Maybe<uint32_t> offset =
focus.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
return offset ? *offset : 0;
}
nsIContent* GetChildAtAnchorOffset() {
const RangeBoundary& anchor = AnchorRef();
return anchor.IsSet() ? anchor.GetChildAtOffset() : nullptr;
@ -336,8 +363,12 @@ class Selection final : public nsSupportsWeakReference,
return focus.IsSet() ? focus.GetChildAtOffset() : nullptr;
}
const RangeBoundary& AnchorRef() const;
const RangeBoundary& FocusRef() const;
const RangeBoundary& AnchorRef(
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
AllowRangeCrossShadowBoundary::No) const;
const RangeBoundary& FocusRef(
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
AllowRangeCrossShadowBoundary::No) const;
/*
* IsCollapsed -- is the whole selection just one point, or unset?

View file

@ -287,11 +287,17 @@ static const nsINode* GetClosestCommonInclusiveAncestorForRangeInSelection(
const nsINode* aNode) {
while (aNode &&
!aNode->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
const bool isNodeInShadowTree =
StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
aNode->IsInShadowTree();
if (!aNode
->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection() &&
!isNodeInShadowTree) {
return nullptr;
}
aNode = aNode->GetParentNode();
aNode = StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
? aNode->GetParentOrShadowHostNode()
: aNode->GetParentNode();
}
return aNode;
}
@ -315,12 +321,12 @@ class IsItemInRangeComparator {
int operator()(const AbstractRange* const aRange) const {
int32_t cmp = nsContentUtils::ComparePoints_Deprecated(
&mNode, mEndOffset, aRange->GetStartContainer(), aRange->StartOffset(),
nullptr, mCache);
&mNode, mEndOffset, aRange->GetMayCrossShadowBoundaryStartContainer(),
aRange->MayCrossShadowBoundaryStartOffset(), nullptr, mCache);
if (cmp == 1) {
cmp = nsContentUtils::ComparePoints_Deprecated(
&mNode, mStartOffset, aRange->GetEndContainer(), aRange->EndOffset(),
nullptr, mCache);
&mNode, mStartOffset, aRange->GetMayCrossShadowBoundaryEndContainer(),
aRange->MayCrossShadowBoundaryEndOffset(), nullptr, mCache);
if (cmp == -1) {
return 0;
}
@ -386,6 +392,18 @@ bool nsINode::IsSelected(const uint32_t aStartOffset,
return true;
}
if (range->MayCrossShadowBoundary()) {
MOZ_ASSERT(range->IsDynamicRange(),
"range->MayCrossShadowBoundary() can only return true for "
"dynamic range");
StaticRange* crossBoundaryRange =
range->AsDynamicRange()->GetCrossShadowBoundaryRange();
MOZ_ASSERT(crossBoundaryRange);
if (!crossBoundaryRange->Collapsed()) {
return true;
}
}
const AbstractRange* middlePlus1;
const AbstractRange* middleMinus1;
// if node end > start of middle+1, result = 1
@ -552,7 +570,8 @@ static nsIContent* GetRootForContentSubtree(nsIContent* aContent) {
return nsIContent::FromNode(aContent->SubtreeRoot());
}
nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) {
nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell,
bool aAllowCrossShadowBoundary) {
NS_ENSURE_TRUE(aPresShell, nullptr);
if (IsDocument()) return AsDocument()->GetRootElement();
@ -596,7 +615,7 @@ nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) {
}
RefPtr<nsFrameSelection> fs = aPresShell->FrameSelection();
nsIContent* content = fs->GetLimiter();
nsCOMPtr<nsIContent> content = fs->GetLimiter();
if (!content) {
content = fs->GetAncestorLimiter();
if (!content) {
@ -616,6 +635,10 @@ nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) {
// Use the host as the root.
if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(content)) {
content = shadowRoot->GetHost();
if (content && aAllowCrossShadowBoundary) {
content = content->GetSelectionRootContent(aPresShell,
aAllowCrossShadowBoundary);
}
}
}

View file

@ -1629,7 +1629,7 @@ class nsINode : public mozilla::dom::EventTarget {
* not in same subtree, this returns the root content of the closeset subtree.
*/
MOZ_CAN_RUN_SCRIPT nsIContent* GetSelectionRootContent(
mozilla::PresShell* aPresShell);
mozilla::PresShell* aPresShell, bool aAllowCrossShadowBoundary = false);
nsINodeList* ChildNodes();

View file

@ -3188,7 +3188,7 @@ void nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges) {
// This is the initial range and all its nodes until now are
// non-selectable so just trim them from the start.
IgnoredErrorResult err;
range->SetStartBefore(*node, err);
range->SetStartBefore(*node, err, AllowRangeCrossShadowBoundary::Yes);
if (err.Failed()) {
return;
}
@ -3202,7 +3202,8 @@ void nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges) {
// Truncate the current range before the first non-selectable node.
IgnoredErrorResult err;
range->SetEndBefore(*firstNonSelectableContent, err);
range->SetEndBefore(*firstNonSelectableContent, err,
AllowRangeCrossShadowBoundary::Yes);
// Store it in the result (strong ref) - do this before creating
// a new range in |newRange| below so we don't drop the last ref

View file

@ -539,7 +539,11 @@ nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
NS_ENSURE_STATE(mPresShell);
RefPtr<PresShell> presShell = mPresShell;
nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(presShell);
nsIContent* anchorRoot =
anchorContent
->GetSelectionRootContent(
presShell,
StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() /* aAllowCrossShadowBoundary */);
NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED);
//
@ -549,7 +553,10 @@ nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
nsCOMPtr<nsIContent> content = aFrame->GetContent();
if (content) {
nsIContent* contentRoot = content->GetSelectionRootContent(presShell);
nsIContent* contentRoot =
content->GetSelectionRootContent(
presShell, StaticPrefs::
dom_shadowdom_selection_across_boundary_enabled() /* aAllowCrossShadowBoundary */);
NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
if (anchorRoot == contentRoot) {
@ -573,8 +580,9 @@ nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
if (cursorFrame && cursorFrame->PresShell() == presShell) {
nsCOMPtr<nsIContent> cursorContent = cursorFrame->GetContent();
NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE);
nsIContent* cursorContentRoot =
cursorContent->GetSelectionRootContent(presShell);
nsIContent* cursorContentRoot = cursorContent->GetSelectionRootContent(
presShell, StaticPrefs::
dom_shadowdom_selection_across_boundary_enabled() /* aAllowCrossShadowBoundary */);
NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED);
if (cursorContentRoot == anchorRoot) {
*aRetFrame = cursorFrame;

View file

@ -8568,6 +8568,12 @@ const nsFrameSelection* nsIFrame::GetConstFrameSelection() const {
bool nsIFrame::IsFrameSelected() const {
NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
"use the public IsSelected() instead");
if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
if (const ShadowRoot* shadowRoot =
GetContent()->GetShadowRootForSelection()) {
return shadowRoot->IsSelected(0, shadowRoot->GetChildCount());
}
}
return GetContent()->IsSelected(0, GetContent()->GetChildCount());
}
@ -8991,6 +8997,13 @@ nsresult nsIFrame::PeekOffsetForParagraph(PeekOffsetStruct* aPos) {
if (reachedLimit) { // no "stop frame" found
aPos->mResultContent = frame->GetContent();
if (ShadowRoot* shadowRoot =
aPos->mResultContent->GetShadowRootForSelection()) {
// Even if there's no children for this node,
// the elements inside the shadow root is still
// selectable
aPos->mResultContent = shadowRoot;
}
if (aPos->mDirection == eDirPrevious) {
aPos->mContentOffset = 0;
} else if (aPos->mResultContent) {