forked from mirrors/gecko-dev
Bug 1891783 - Fix two more bugs in ShadowDOM Selection r=jjaschke,smaug,dom-core
Bug #1: AbstractRange::(Mark|Unmark)Descendants should always use the shadow tree of web-exposed shadow root, instead of using light DOM elements of the host. Bug #2: aRange could possibly create mCrossShadowBoundaryRange first (due to boundaries are in different tree), and later moves the boundaries to the same tree. When this happens, we should remove mCrossShadowBoundaryRange and use the default range to represent it. Differential Revision: https://phabricator.services.mozilla.com/D207608
This commit is contained in:
parent
3b631b9d44
commit
25b4330bcc
7 changed files with 243 additions and 60 deletions
|
|
@ -96,7 +96,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|||
// for the flattened children of aNode.
|
||||
void UpdateDescendantsInFlattenedTree(const nsIContent& aNode,
|
||||
bool aMarkDesendants) {
|
||||
if (!aNode.IsElement() || aNode.IsHTMLElement(nsGkAtoms::slot)) {
|
||||
if (!aNode.IsElement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -119,14 +119,14 @@ void AbstractRange::MarkDescendants(const nsINode& aNode) {
|
|||
// ancestor or a descendant of one, in which case all of our descendants have
|
||||
// the bit set already.
|
||||
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);
|
||||
}
|
||||
// If aNode has a web-exposed shadow root, use this shadow tree and ignore
|
||||
// the children of aNode.
|
||||
if (aNode.GetShadowRootForSelection()) {
|
||||
UpdateDescendantsInFlattenedTree(*aNode.AsContent(), true);
|
||||
return;
|
||||
}
|
||||
// don't set the Descendant bit on |aNode| itself
|
||||
nsINode* node = aNode.GetNextNode(&aNode);
|
||||
while (node) {
|
||||
node->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
|
||||
if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
|
||||
|
|
@ -152,14 +152,14 @@ void AbstractRange::UnmarkDescendants(const nsINode& aNode) {
|
|||
// common ancestor itself).
|
||||
if (!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);
|
||||
}
|
||||
// If aNode has a web-exposed shadow root, use this shadow tree and ignore
|
||||
// the children of aNode.
|
||||
if (aNode.GetShadowRootForSelection()) {
|
||||
UpdateDescendantsInFlattenedTree(*aNode.AsContent(), false);
|
||||
return;
|
||||
}
|
||||
// we know |aNode| doesn't have any bit set
|
||||
nsINode* node = aNode.GetNextNode(&aNode);
|
||||
while (node) {
|
||||
node->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
|
||||
if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
|
||||
|
|
|
|||
|
|
@ -106,19 +106,19 @@ template nsresult nsRange::SetStartAndEnd(
|
|||
template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary,
|
||||
const RangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
RangeBehaviour aRangeBehaviour);
|
||||
template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary,
|
||||
const RawRangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
RangeBehaviour aRangeBehaviour);
|
||||
template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
|
||||
const RangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
RangeBehaviour aRangeBehaviour);
|
||||
template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
|
||||
const RawRangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
RangeBehaviour aRangeBehaviour);
|
||||
|
||||
template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
|
||||
|
|
@ -222,25 +222,26 @@ already_AddRefed<nsRange> nsRange::Create(
|
|||
* aRange: The nsRange that aNewBoundary is being set to.
|
||||
* aNewRoot: The shadow-including root of the container of aNewBoundary
|
||||
* aNewBoundary: The new boundary
|
||||
* aIsSetStart: true if ShouldCollapseBoundary is called by nsRange::SetStart,
|
||||
* aIsSetStart: true if GetRangeBehaviour is called by nsRange::SetStart,
|
||||
* false otherwise
|
||||
* aAllowCrossShadowBoundary: Indicates whether the boundaries allowed to cross
|
||||
* shadow boundary or not
|
||||
*/
|
||||
static CollapsePolicy ShouldCollapseBoundary(
|
||||
static RangeBehaviour GetRangeBehaviour(
|
||||
const nsRange* aRange, const nsINode* aNewRoot,
|
||||
const RawRangeBoundary& aNewBoundary, const bool aIsSetStart,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
if (!aRange->IsPositioned()) {
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aRange->GetRoot());
|
||||
|
||||
if (aNewRoot != aRange->GetRoot()) {
|
||||
// Boundaries are in different document (or not connected), so collapse
|
||||
// the both the default range and the crossBoundaryRange range.
|
||||
if (aNewRoot->GetComposedDoc() != aRange->GetRoot()->GetComposedDoc()) {
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
// Always collapse both ranges if the one of the roots is an UA widget
|
||||
|
|
@ -248,14 +249,32 @@ static CollapsePolicy ShouldCollapseBoundary(
|
|||
// or not.
|
||||
if (AbstractRange::IsRootUAWidget(aNewRoot) ||
|
||||
AbstractRange::IsRootUAWidget(aRange->GetRoot())) {
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
if (const CrossShadowBoundaryRange* crossShadowBoundaryRange =
|
||||
aRange->GetCrossShadowBoundaryRange()) {
|
||||
// Check if the existing-other-side boundary in
|
||||
// aRange::mCrossShadowBoundaryRange has the same root
|
||||
// as aNewRoot. If this is the case, it means default range
|
||||
// is good enough to represent this range, so that we can
|
||||
// merge the cross-shadow-boundary range and the default range.
|
||||
const RangeBoundary& otherSideExistingBoundary =
|
||||
aIsSetStart ? crossShadowBoundaryRange->EndRef()
|
||||
: crossShadowBoundaryRange->StartRef();
|
||||
const nsINode* otherSideRoot =
|
||||
RangeUtils::ComputeRootNode(otherSideExistingBoundary.Container());
|
||||
if (aNewRoot == otherSideRoot) {
|
||||
return RangeBehaviour::MergeDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
}
|
||||
|
||||
// Different root, but same document. So we only collapse the
|
||||
// default range if boundaries are allowed to cross shadow boundary.
|
||||
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
|
||||
? CollapsePolicy::DefaultRange
|
||||
: CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
? RangeBehaviour::CollapseDefaultRange
|
||||
: RangeBehaviour::
|
||||
CollapseDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
const RangeBoundary& otherSideExistingBoundary =
|
||||
|
|
@ -281,12 +300,12 @@ static CollapsePolicy ShouldCollapseBoundary(
|
|||
// aNewBoundary intends to be the end.
|
||||
//
|
||||
// So no collapse for above cases.
|
||||
return CollapsePolicy::No;
|
||||
return RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
if (!aRange->MayCrossShadowBoundary() ||
|
||||
aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::No) {
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
const RangeBoundary& otherSideExistingCrossShadowBoundaryBoundary =
|
||||
|
|
@ -308,15 +327,15 @@ static CollapsePolicy ShouldCollapseBoundary(
|
|||
|
||||
// Valid to the cross boundary boundary.
|
||||
if (withCrossShadowBoundaryOrder && *withCrossShadowBoundaryOrder != 1) {
|
||||
return CollapsePolicy::DefaultRange;
|
||||
return RangeBehaviour::CollapseDefaultRange;
|
||||
}
|
||||
|
||||
// Not valid to both existing boundaries.
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
/******************************************************
|
||||
* nsISupports
|
||||
|
|
@ -1041,12 +1060,11 @@ void nsRange::AssertIfMismatchRootAndRangeBoundaries(
|
|||
// Calling DoSetRange with either parent argument null will collapse
|
||||
// the range to have both endpoints point to the other node
|
||||
template <typename SPT, typename SRT, typename EPT, typename ERT>
|
||||
void nsRange::DoSetRange(
|
||||
const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
|
||||
const RangeBoundaryBase<EPT, ERT>& aEndBoundary, nsINode* aRootNode,
|
||||
bool aNotInsertedYet /* = false */,
|
||||
CollapsePolicy
|
||||
aCollapsePolicy /* = DEFAULT_RANGE_AND_CROSS_BOUNDARY_RANGES */) {
|
||||
void nsRange::
|
||||
DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
|
||||
const RangeBoundaryBase<EPT, ERT>& aEndBoundary,
|
||||
nsINode* aRootNode,
|
||||
bool aNotInsertedYet /* = false */, RangeBehaviour aRangeBehaviour /* = CollapseDefaultRangeAndCrossShadowBoundaryRanges */) {
|
||||
mIsPositioned = aStartBoundary.IsSetAndValid() &&
|
||||
aEndBoundary.IsSetAndValid() && aRootNode;
|
||||
MOZ_ASSERT_IF(!mIsPositioned, !aStartBoundary.IsSet());
|
||||
|
|
@ -1075,8 +1093,8 @@ void nsRange::DoSetRange(
|
|||
mStart.CopyFrom(aStartBoundary, RangeBoundaryIsMutationObserved::Yes);
|
||||
mEnd.CopyFrom(aEndBoundary, RangeBoundaryIsMutationObserved::Yes);
|
||||
|
||||
if (aCollapsePolicy ==
|
||||
CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges) {
|
||||
if (aRangeBehaviour ==
|
||||
RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges) {
|
||||
ResetCrossShadowBoundaryRange();
|
||||
}
|
||||
|
||||
|
|
@ -1158,12 +1176,12 @@ void nsRange::SetStart(
|
|||
return;
|
||||
}
|
||||
|
||||
CollapsePolicy policy =
|
||||
ShouldCollapseBoundary(this, newRoot, aPoint, true /* aIsSetStart= */,
|
||||
aAllowCrossShadowBoundary);
|
||||
RangeBehaviour behaviour =
|
||||
GetRangeBehaviour(this, newRoot, aPoint, true /* aIsSetStart= */,
|
||||
aAllowCrossShadowBoundary);
|
||||
|
||||
switch (policy) {
|
||||
case CollapsePolicy::No:
|
||||
switch (behaviour) {
|
||||
case RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges:
|
||||
// EndRef(..) may be same as mStart or not, depends on
|
||||
// the value of mCrossShadowBoundaryRange->mEnd, We need to update
|
||||
// mCrossShadowBoundaryRange and the default boundaries separately
|
||||
|
|
@ -1176,17 +1194,22 @@ void nsRange::SetStart(
|
|||
ResetCrossShadowBoundaryRange();
|
||||
}
|
||||
}
|
||||
DoSetRange(aPoint, mEnd, mRoot, false, policy);
|
||||
DoSetRange(aPoint, mEnd, mRoot, false, behaviour);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges:
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
case RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges:
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, behaviour);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRange:
|
||||
case RangeBehaviour::CollapseDefaultRange:
|
||||
MOZ_ASSERT(aAllowCrossShadowBoundary ==
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
aPoint, MayCrossShadowBoundaryEndRef());
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, behaviour);
|
||||
break;
|
||||
case RangeBehaviour::MergeDefaultRangeAndCrossShadowBoundaryRanges:
|
||||
DoSetRange(aPoint, MayCrossShadowBoundaryEndRef(), newRoot, false,
|
||||
behaviour);
|
||||
ResetCrossShadowBoundaryRange();
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
|
|
@ -1270,12 +1293,12 @@ void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv,
|
|||
return;
|
||||
}
|
||||
|
||||
CollapsePolicy policy =
|
||||
ShouldCollapseBoundary(this, newRoot, aPoint, false /* aIsStartStart */,
|
||||
aAllowCrossShadowBoundary);
|
||||
RangeBehaviour policy =
|
||||
GetRangeBehaviour(this, newRoot, aPoint, false /* aIsStartStart */,
|
||||
aAllowCrossShadowBoundary);
|
||||
|
||||
switch (policy) {
|
||||
case CollapsePolicy::No:
|
||||
case RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges:
|
||||
// StartRef(..) may be same as mStart or not, depends on
|
||||
// the value of mCrossShadowBoundaryRange->mStart, so we need to update
|
||||
// mCrossShadowBoundaryRange and the default boundaries separately
|
||||
|
|
@ -1290,16 +1313,21 @@ void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv,
|
|||
}
|
||||
DoSetRange(mStart, aPoint, mRoot, false, policy);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges:
|
||||
case RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges:
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRange:
|
||||
case RangeBehaviour::CollapseDefaultRange:
|
||||
MOZ_ASSERT(aAllowCrossShadowBoundary ==
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
MayCrossShadowBoundaryStartRef(), aPoint);
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
break;
|
||||
case RangeBehaviour::MergeDefaultRangeAndCrossShadowBoundaryRanges:
|
||||
DoSetRange(MayCrossShadowBoundaryStartRef(), aPoint, newRoot, false,
|
||||
policy);
|
||||
ResetCrossShadowBoundaryRange();
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,11 +34,19 @@ class DOMRectList;
|
|||
class InspectorFontFace;
|
||||
class Selection;
|
||||
|
||||
enum class CollapsePolicy : uint8_t {
|
||||
No, // Don't need to collapse
|
||||
DefaultRange, // Collapse the default range
|
||||
DefaultRangeAndCrossShadowBoundaryRanges // Collapse both the default range
|
||||
// and the cross boundary range
|
||||
enum class RangeBehaviour : uint8_t {
|
||||
// Keep both ranges
|
||||
KeepDefaultRangeAndCrossShadowBoundaryRanges,
|
||||
// Merge both ranges; This is the case where the range boundaries was in
|
||||
// different roots initially, and becoming in the same roots now. Since
|
||||
// they start to be in the same root, using normal range is good enough
|
||||
// to represent it
|
||||
MergeDefaultRangeAndCrossShadowBoundaryRanges,
|
||||
// Collapse the default range
|
||||
CollapseDefaultRange,
|
||||
// Collapse both the default range and the cross-shadow-boundary range
|
||||
CollapseDefaultRangeAndCrossShadowBoundaryRanges
|
||||
|
||||
};
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
@ -491,8 +499,8 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
|||
const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
|
||||
const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet = false,
|
||||
mozilla::dom::CollapsePolicy aCollapsePolicy = mozilla::dom::
|
||||
CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges);
|
||||
mozilla::dom::RangeBehaviour aRangeBehaviour = mozilla::dom::
|
||||
RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges);
|
||||
|
||||
// Assume that this is guaranteed that this is held by the caller when
|
||||
// this is used. (Note that we cannot use AutoRestore for mCalledByJS
|
||||
|
|
|
|||
|
|
@ -302,6 +302,12 @@ skip-if = ["release_or_beta"] # requires Selection.getComposedRanges to be enabl
|
|||
["test_selection_cross_shadow_boundary_multi_ranges_backward_click.html"]
|
||||
skip-if = ["release_or_beta"] # requires Selection.getComposedRanges to be enabled (Nightly only)
|
||||
|
||||
["test_selection_cross_shadow_boundary_forward_and_backward.html"]
|
||||
skip-if = ["release_or_beta"] # requires Selection.getComposedRanges to be enabled (Nightly only)
|
||||
|
||||
["test_selection_cross_shadow_boundary_backward_nested_click.html"]
|
||||
skip-if = ["release_or_beta"] # requires Selection.getComposedRanges to be enabled (Nightly only)
|
||||
|
||||
["test_selection_doubleclick.html"]
|
||||
|
||||
["test_selection_expanding.html"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
function run() {
|
||||
const host = document.getElementById("host");
|
||||
const inner = host.shadowRoot.getElementById("inner");
|
||||
const innerRect = inner.getBoundingClientRect();
|
||||
|
||||
const innerHost = host.shadowRoot.getElementById("innerHost");
|
||||
const nested = innerHost.shadowRoot.getElementById("nested");
|
||||
const nestedRect = nested.getBoundingClientRect();
|
||||
|
||||
// Click the center of "NestedText"
|
||||
synthesizeMouse(nested, nestedRect.width / 2, nestedRect.height / 2, { type: "mousedown" });
|
||||
synthesizeMouse(nested, nestedRect.width / 2, nestedRect.height / 2, { type: "mouseup" });
|
||||
|
||||
// Click the center of "InnerText"
|
||||
synthesizeMouse(inner, innerRect.width / 2, innerRect.height / 2, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(inner, innerRect.width / 2, innerRect.height / 2, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
// Above two clicks should select half of the content in "InnerText" and half of the content in "NestedText"
|
||||
let sel = document.getSelection().getComposedRanges(host.shadowRoot, innerHost.shadowRoot)[0];
|
||||
|
||||
// forward selection
|
||||
is(sel.startContainer, inner.firstChild, "startContainer is the InnerText");
|
||||
is(sel.endContainer, nested.firstChild, "endContainer is the NestedText");
|
||||
|
||||
const collapsedRange = document.getSelection().getRangeAt(0);
|
||||
is(collapsedRange.startContainer, inner.firstChild, "normal range's startContainer get collapsed to InnerText");
|
||||
is(collapsedRange.endContainer, inner.firstChild, "normal range's endContainer get collapsed to InnerText");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<div id="host">
|
||||
<template shadowrootmode="open">
|
||||
<span id="inner">InnerText</span>
|
||||
<div id="innerHost">
|
||||
<template shadowrootmode="open">
|
||||
<span id="nested">NestedText</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Test to ensure that things still work correctly when the range boundaries
|
||||
// was in different roots initially and moves to the same root afterwards.
|
||||
function run() {
|
||||
const outer = document.getElementById("outer");
|
||||
|
||||
const inner = document.getElementById("host").shadowRoot.getElementById("inner");
|
||||
const innerRect = inner.getBoundingClientRect();
|
||||
|
||||
// Click the bottom right of "InnerText"
|
||||
synthesizeMouse(inner, innerRect.width, innerRect.height, { type: "mousedown" });
|
||||
synthesizeMouse(inner, innerRect.width, innerRect.height, { type: "mouseup" });
|
||||
|
||||
// Click the top left of "OuterText"
|
||||
synthesizeMouse(outer, 0, 0, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(outer, 0, 0, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
// Above two clicks should select both "OuterText" and "InnerText"
|
||||
let sel = document.getSelection().getComposedRanges(host.shadowRoot)[0];
|
||||
|
||||
// forward selection
|
||||
is(sel.startContainer, outer.firstChild, "startContainer is the OuterText");
|
||||
is(sel.startOffset, 0, "startOffset starts at the first character");
|
||||
is(sel.endContainer, inner.firstChild, "endContainer is the InnerText");
|
||||
is(sel.endOffset, 9, "endOffset ends at the last character");
|
||||
|
||||
let normalRange = document.getSelection().getRangeAt(0);
|
||||
is(normalRange.startContainer, outer.firstChild, "normal range's startContainer gets collapsed to OuterText");
|
||||
is(normalRange.endContainer, outer.firstChild, "normal range's endContainer gets collapsed the OuterText");
|
||||
|
||||
// Click the center of "InnerText"
|
||||
synthesizeMouse(inner, innerRect.width / 2, innerRect.height / 2, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(inner, innerRect.width / 2, innerRect.height / 2, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
sel = document.getSelection().getComposedRanges(host.shadowRoot)[0];
|
||||
is(sel.startContainer, inner.firstChild, "both startContainer and endContainer are InnerText");
|
||||
is(sel.endContainer, inner.firstChild, "both startContainer and endContainer are InnerText");
|
||||
|
||||
normalRange = document.getSelection().getRangeAt(0);
|
||||
is(normalRange.startContainer, inner.firstChild, "normal range's startContainer gets collapsed to InnerText");
|
||||
is(normalRange.endContainer, inner.firstChild, "normal range's endContainer gets collapsed the InnerText");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer">OuterText</span>
|
||||
<div id="host">
|
||||
<template shadowrootmode="open">
|
||||
<span id="inner">InnerText</span>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<div id="host">
|
||||
<span id="slotted">slotted</span>
|
||||
</div>
|
||||
<span id="outer">outer</span>
|
||||
<script>
|
||||
test(function(t) {
|
||||
const sel = window.getSelection();
|
||||
sel.setBaseAndExtent(slotted.firstChild, 3, outer.firstChild, 2);
|
||||
host.attachShadow({mode: "open"}).innerHTML = "<slot></slot><span>inner</span>";
|
||||
|
||||
assert_equals(sel.anchorNode, slotted.firstChild);
|
||||
assert_equals(sel.anchorOffset, 3);
|
||||
assert_equals(sel.focusNode, outer.firstChild);
|
||||
assert_equals(sel.focusOffset, 2);
|
||||
|
||||
const composedRange = sel.getComposedRanges(host.shadowRoot)[0];
|
||||
assert_equals(composedRange.startContainer, slotted.firstChild);
|
||||
assert_equals(composedRange.startOffset, 3);
|
||||
assert_equals(composedRange.endContainer, outer.firstChild);
|
||||
assert_equals(composedRange.endOffset, 2);
|
||||
|
||||
sel.empty();
|
||||
|
||||
assert_equals(sel.anchorNode, null);
|
||||
assert_equals(sel.anchorOffset, 0);
|
||||
assert_equals(sel.focusNode, null);
|
||||
assert_equals(sel.focusOffset, 0);
|
||||
}, "test to select a light DOM element and it becomes a slotted content after the selection");
|
||||
</script>
|
||||
Loading…
Reference in a new issue