mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
Backed out 12 changesets (bug 1867058) for causing bustages on AbstractRange.cpp
Backed out changeset 6254c9c51033 (bug 1867058) Backed out changeset 2ad556d56736 (bug 1867058) Backed out changeset a8bc41291ab3 (bug 1867058) Backed out changeset c30869c03a70 (bug 1867058) Backed out changeset 39c5816dff6b (bug 1867058) Backed out changeset 42e226158dc9 (bug 1867058) Backed out changeset 67bb7158a09f (bug 1867058) Backed out changeset 7b5a689dc7fd (bug 1867058) Backed out changeset 3cf108eb13a6 (bug 1867058) Backed out changeset 8efda4cce80c (bug 1867058) Backed out changeset 0837e15babab (bug 1867058) Backed out changeset 8ce7972ea4df (bug 1867058)
This commit is contained in:
parent
45152fc3cb
commit
715a8e1d09
@ -10,7 +10,6 @@
|
||||
#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"
|
||||
@ -88,29 +87,6 @@ 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
|
||||
@ -119,22 +95,10 @@ 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 (const ShadowRoot* shadowRoot = aNode.GetShadowRootForSelection()) {
|
||||
UpdateDescendantsInFlattenedTree(*aNode.AsContent(), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
while (node) {
|
||||
node->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
|
||||
if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
|
||||
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);
|
||||
}
|
||||
node = node->GetNextNode(&aNode);
|
||||
} else {
|
||||
// optimize: skip this sub-tree since it's marked already.
|
||||
node = node->GetNextNonChildNode(&aNode);
|
||||
@ -152,22 +116,10 @@ void AbstractRange::UnmarkDescendants(const nsINode& aNode) {
|
||||
.IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
|
||||
// we know |aNode| doesn't have any bit set
|
||||
nsINode* node = aNode.GetNextNode(&aNode);
|
||||
if (!node) {
|
||||
if (const ShadowRoot* shadowRoot = aNode.GetShadowRootForSelection()) {
|
||||
UpdateDescendantsInFlattenedTree(*aNode.AsContent(), false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
while (node) {
|
||||
node->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
|
||||
if (!node->IsClosestCommonInclusiveAncestorForRangeInSelection()) {
|
||||
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);
|
||||
}
|
||||
node = node->GetNextNode(&aNode);
|
||||
} else {
|
||||
// We found an ancestor of an overlapping range, skip its descendants.
|
||||
node = node->GetNextNonChildNode(&aNode);
|
||||
@ -233,54 +185,10 @@ bool AbstractRange::MaybeCacheToReuse(RangeType& aInstance) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
// RangeBoundary allows the container to be shadow roots; When
|
||||
// this happens, we should use the shadow host here.
|
||||
if (startContainer->IsShadowRoot()) {
|
||||
startContainer = startContainer->GetContainingShadowHost();
|
||||
}
|
||||
if (endContainer->IsShadowRoot()) {
|
||||
endContainer = endContainer->GetContainingShadowHost();
|
||||
}
|
||||
return nsContentUtils::GetCommonFlattenedTreeAncestor(
|
||||
startContainer ? startContainer->AsContent() : nullptr,
|
||||
endContainer ? endContainer->AsContent() : nullptr);
|
||||
}
|
||||
return nsContentUtils::GetClosestCommonInclusiveAncestor(startContainer,
|
||||
endContainer);
|
||||
nsINode* AbstractRange::GetClosestCommonInclusiveAncestor() const {
|
||||
return mIsPositioned ? nsContentUtils::GetClosestCommonInclusiveAncestor(
|
||||
mStart.Container(), mEnd.Container())
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
@ -329,21 +237,9 @@ nsresult AbstractRange::SetStartAndEndInternal(
|
||||
return NS_ERROR_DOM_INDEX_SIZE_ERR;
|
||||
}
|
||||
|
||||
// Different root
|
||||
// If they have different root, this should be collapsed at the end point.
|
||||
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);
|
||||
aRange->AsDynamicRange()->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
aStartBoundary, aEndBoundary);
|
||||
}
|
||||
aRange->DoSetRange(aEndBoundary, aEndBoundary, newEndRoot);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -378,10 +274,7 @@ void AbstractRange::RegisterSelection(Selection& aSelection) {
|
||||
bool isFirstSelection = mSelections.IsEmpty();
|
||||
mSelections.AppendElement(&aSelection);
|
||||
if (isFirstSelection && !mRegisteredClosestCommonInclusiveAncestor) {
|
||||
nsINode* commonAncestor = GetClosestCommonInclusiveAncestor(
|
||||
StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
|
||||
? AllowRangeCrossShadowBoundary::Yes
|
||||
: AllowRangeCrossShadowBoundary::No);
|
||||
nsINode* commonAncestor = GetClosestCommonInclusiveAncestor();
|
||||
MOZ_ASSERT(commonAncestor, "unexpected disconnected nodes");
|
||||
RegisterClosestCommonInclusiveAncestor(commonAncestor);
|
||||
}
|
||||
@ -465,8 +358,7 @@ void AbstractRange::UnregisterClosestCommonInclusiveAncestor(
|
||||
|
||||
void AbstractRange::UpdateCommonAncestorIfNecessary() {
|
||||
nsINode* oldCommonAncestor = mRegisteredClosestCommonInclusiveAncestor;
|
||||
nsINode* newCommonAncestor =
|
||||
GetClosestCommonInclusiveAncestor(AllowRangeCrossShadowBoundary::Yes);
|
||||
nsINode* newCommonAncestor = GetClosestCommonInclusiveAncestor();
|
||||
if (newCommonAncestor != oldCommonAncestor) {
|
||||
if (oldCommonAncestor) {
|
||||
UnregisterClosestCommonInclusiveAncestor(oldCommonAncestor, false);
|
||||
@ -487,59 +379,6 @@ 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,
|
||||
|
@ -31,15 +31,10 @@ class Document;
|
||||
class Selection;
|
||||
class StaticRange;
|
||||
|
||||
enum class AllowRangeCrossShadowBoundary : bool { No, Yes };
|
||||
|
||||
class AbstractRange : public nsISupports,
|
||||
public nsWrapperCache,
|
||||
// For linking together selection-associated ranges.
|
||||
public mozilla::LinkedListElement<AbstractRange> {
|
||||
using AllowRangeCrossShadowBoundary =
|
||||
mozilla::dom::AllowRangeCrossShadowBoundary;
|
||||
|
||||
protected:
|
||||
explicit AbstractRange(nsINode* aNode, bool aIsDynamicRange);
|
||||
virtual ~AbstractRange();
|
||||
@ -56,33 +51,18 @@ class AbstractRange : public nsISupports,
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AbstractRange)
|
||||
|
||||
/**
|
||||
* All of the MayCrossShadowBoundary* methods are used to get the boundary
|
||||
* endpoints that cross shadow boundaries. They would return
|
||||
* the same value as the non-MayCrossShadowBoundary* methods if the range
|
||||
* boundaries don't cross shadow boundaries.
|
||||
*/
|
||||
const RangeBoundary& StartRef() const { return mStart; }
|
||||
const RangeBoundary& MayCrossShadowBoundaryStartRef() const;
|
||||
|
||||
const RangeBoundary& EndRef() const { return mEnd; }
|
||||
const RangeBoundary& MayCrossShadowBoundaryEndRef() const;
|
||||
|
||||
nsIContent* GetChildAtStartOffset() const {
|
||||
return mStart.GetChildAtOffset();
|
||||
}
|
||||
nsIContent* GetMayCrossShadowBoundaryChildAtStartOffset() const;
|
||||
|
||||
nsIContent* GetChildAtEndOffset() const { return mEnd.GetChildAtOffset(); }
|
||||
nsIContent* GetMayCrossShadowBoundaryChildAtEndOffset() const;
|
||||
|
||||
bool IsPositioned() const { return mIsPositioned; }
|
||||
/**
|
||||
* https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
|
||||
*/
|
||||
nsINode* GetClosestCommonInclusiveAncestor(
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No) const;
|
||||
nsINode* GetClosestCommonInclusiveAncestor() const;
|
||||
|
||||
// WebIDL
|
||||
|
||||
@ -95,12 +75,7 @@ class AbstractRange : public nsISupports,
|
||||
// `IsPositioned()` directly.
|
||||
|
||||
nsINode* GetStartContainer() const { return mStart.Container(); }
|
||||
nsINode* GetMayCrossShadowBoundaryStartContainer() const;
|
||||
|
||||
nsINode* GetEndContainer() const { return mEnd.Container(); }
|
||||
nsINode* GetMayCrossShadowBoundaryEndContainer() const;
|
||||
|
||||
bool MayCrossShadowBoundary() const;
|
||||
|
||||
Document* GetComposedDocOfContainers() const {
|
||||
return mStart.Container() ? mStart.Container()->GetComposedDoc() : nullptr;
|
||||
@ -111,15 +86,12 @@ class AbstractRange : public nsISupports,
|
||||
return static_cast<uint32_t>(
|
||||
*mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
|
||||
}
|
||||
uint32_t MayCrossShadowBoundaryStartOffset() const;
|
||||
|
||||
// FYI: Returns 0 if it's not positioned.
|
||||
uint32_t EndOffset() const {
|
||||
return static_cast<uint32_t>(
|
||||
*mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets));
|
||||
}
|
||||
uint32_t MayCrossShadowBoundaryEndOffset() const;
|
||||
|
||||
bool Collapsed() const {
|
||||
return !mIsPositioned || (mStart.Container() == mEnd.Container() &&
|
||||
StartOffset() == EndOffset());
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "ContentIterator.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/RangeBoundary.h"
|
||||
#include "mozilla/RangeUtils.h"
|
||||
@ -27,69 +26,6 @@ using namespace dom;
|
||||
__VA_ARGS__); \
|
||||
template aResultType ContentIteratorBase<nsINode*>::aMethodName(__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* IteratorHelpers contains the static methods to help extra values
|
||||
* based on whether or not the iterator allows to iterate nodes cross the shadow
|
||||
* boundary.
|
||||
*/
|
||||
struct IteratorHelpers {
|
||||
IteratorHelpers() = delete;
|
||||
|
||||
static nsINode* GetStartContainer(AbstractRange* aRange,
|
||||
bool aAllowCrossShadowBoundary) {
|
||||
MOZ_ASSERT(aRange);
|
||||
return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
|
||||
aAllowCrossShadowBoundary)
|
||||
? aRange->GetMayCrossShadowBoundaryStartContainer()
|
||||
: aRange->GetStartContainer();
|
||||
}
|
||||
|
||||
static int32_t StartOffset(AbstractRange* aRange,
|
||||
bool aAllowCrossShadowBoundary) {
|
||||
MOZ_ASSERT(aRange);
|
||||
return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
|
||||
aAllowCrossShadowBoundary)
|
||||
? aRange->MayCrossShadowBoundaryStartOffset()
|
||||
: aRange->StartOffset();
|
||||
}
|
||||
|
||||
static nsINode* GetEndContainer(AbstractRange* aRange,
|
||||
bool aAllowCrossShadowBoundary) {
|
||||
MOZ_ASSERT(aRange);
|
||||
return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
|
||||
aAllowCrossShadowBoundary)
|
||||
? aRange->GetMayCrossShadowBoundaryEndContainer()
|
||||
: aRange->GetEndContainer();
|
||||
}
|
||||
|
||||
static int32_t EndOffset(AbstractRange* aRange,
|
||||
bool aAllowCrossShadowBoundary) {
|
||||
MOZ_ASSERT(aRange);
|
||||
return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
|
||||
aAllowCrossShadowBoundary)
|
||||
? aRange->MayCrossShadowBoundaryEndOffset()
|
||||
: aRange->EndOffset();
|
||||
}
|
||||
|
||||
// FIXME(sefeng): This doesn't work with slots / flattened tree.
|
||||
static nsINode* GetParentNode(nsINode& aNode,
|
||||
bool aAllowCrossShadowBoundary) {
|
||||
return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
|
||||
aAllowCrossShadowBoundary)
|
||||
? aNode.GetParentOrShadowHostNode()
|
||||
: aNode.GetParentNode();
|
||||
}
|
||||
|
||||
static ShadowRoot* GetShadowRoot(const nsINode* aNode,
|
||||
bool aAllowCrossShadowBoundary) {
|
||||
MOZ_ASSERT(aNode);
|
||||
return (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
|
||||
aAllowCrossShadowBoundary)
|
||||
? aNode->GetShadowRootForSelection()
|
||||
: nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
static bool ComparePostMode(const RawRangeBoundary& aStart,
|
||||
const RawRangeBoundary& aEnd, nsINode& aNode) {
|
||||
nsINode* parent = aNode.GetParentNode();
|
||||
@ -605,7 +541,7 @@ nsINode* ContentIteratorBase<NodeType>::GetDeepFirstChild(nsINode* aRoot) {
|
||||
// static
|
||||
template <typename NodeType>
|
||||
nsIContent* ContentIteratorBase<NodeType>::GetDeepFirstChild(
|
||||
nsIContent* aRoot, bool aAllowCrossShadowBoundary) {
|
||||
nsIContent* aRoot) {
|
||||
if (NS_WARN_IF(!aRoot)) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -613,25 +549,9 @@ nsIContent* ContentIteratorBase<NodeType>::GetDeepFirstChild(
|
||||
nsIContent* node = aRoot;
|
||||
nsIContent* child = node->GetFirstChild();
|
||||
|
||||
if (!child) {
|
||||
if (ShadowRoot* shadowRoot =
|
||||
IteratorHelpers::GetShadowRoot(node, aAllowCrossShadowBoundary)) {
|
||||
// If this node doesn't have a child, but it's also a shadow host
|
||||
// that can be selected, we go into this shadow tree.
|
||||
child = shadowRoot->GetFirstChild();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(sefeng): This is problematic for slotted contents
|
||||
while (child) {
|
||||
node = child;
|
||||
child = node->GetFirstChild();
|
||||
if (!child) {
|
||||
if (ShadowRoot* shadowRoot =
|
||||
IteratorHelpers::GetShadowRoot(node, aAllowCrossShadowBoundary)) {
|
||||
child = shadowRoot->GetFirstChild();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
@ -649,41 +569,23 @@ nsINode* ContentIteratorBase<NodeType>::GetDeepLastChild(nsINode* aRoot) {
|
||||
|
||||
// static
|
||||
template <typename NodeType>
|
||||
nsIContent* ContentIteratorBase<NodeType>::GetDeepLastChild(
|
||||
nsIContent* aRoot, bool aAllowCrossShadowBoundary) {
|
||||
nsIContent* ContentIteratorBase<NodeType>::GetDeepLastChild(nsIContent* aRoot) {
|
||||
if (NS_WARN_IF(!aRoot)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsIContent* node = aRoot;
|
||||
|
||||
ShadowRoot* shadowRoot =
|
||||
IteratorHelpers::GetShadowRoot(node, aAllowCrossShadowBoundary);
|
||||
// FIXME(sefeng): This doesn't work with slots / flattened tree.
|
||||
while (node->HasChildren() || (shadowRoot && shadowRoot->HasChildren())) {
|
||||
if (node->HasChildren()) {
|
||||
node = node->GetLastChild();
|
||||
} else {
|
||||
MOZ_ASSERT(shadowRoot);
|
||||
// If this node doesn't have a child, but it's also a shadow host
|
||||
// that can be selected, we go into this shadow tree.
|
||||
node = shadowRoot->GetLastChild();
|
||||
}
|
||||
shadowRoot =
|
||||
IteratorHelpers::GetShadowRoot(node, aAllowCrossShadowBoundary);
|
||||
while (node->HasChildren()) {
|
||||
nsIContent* child = node->GetLastChild();
|
||||
node = child;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// Get the next sibling, or parent's next sibling, or shadow host's next
|
||||
// sibling (when aAllowCrossShadowBoundary is true), or grandpa's next
|
||||
// sibling...
|
||||
//
|
||||
// Get the next sibling, or parent's next sibling, or grandpa's next sibling...
|
||||
// static
|
||||
//
|
||||
template <typename NodeType>
|
||||
nsIContent* ContentIteratorBase<NodeType>::GetNextSibling(
|
||||
nsINode* aNode, bool aAllowCrossShadowBoundary) {
|
||||
nsIContent* ContentIteratorBase<NodeType>::GetNextSibling(nsINode* aNode) {
|
||||
if (NS_WARN_IF(!aNode)) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -692,32 +594,18 @@ nsIContent* ContentIteratorBase<NodeType>::GetNextSibling(
|
||||
return next;
|
||||
}
|
||||
|
||||
nsINode* parent =
|
||||
IteratorHelpers::GetParentNode(*aNode, aAllowCrossShadowBoundary);
|
||||
nsINode* parent = aNode->GetParentNode();
|
||||
if (NS_WARN_IF(!parent)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aAllowCrossShadowBoundary) {
|
||||
// This is temporary solution.
|
||||
// For shadow root, instead of getting to the sibling of the parent
|
||||
// directly, we need to get into the light tree of the parent to handle
|
||||
// slotted contents.
|
||||
if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(aNode)) {
|
||||
if (nsIContent* child = parent->GetFirstChild()) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ContentIteratorBase::GetNextSibling(parent, aAllowCrossShadowBoundary);
|
||||
return ContentIteratorBase::GetNextSibling(parent);
|
||||
}
|
||||
|
||||
// Get the prev sibling, or parent's prev sibling, or shadow host's prev sibling
|
||||
// (when aAllowCrossShadowBoundary is true), or grandpa's prev sibling... static
|
||||
// Get the prev sibling, or parent's prev sibling, or grandpa's prev sibling...
|
||||
// static
|
||||
template <typename NodeType>
|
||||
nsIContent* ContentIteratorBase<NodeType>::GetPrevSibling(
|
||||
nsINode* aNode, bool aAllowCrossShadowBoundary) {
|
||||
nsIContent* ContentIteratorBase<NodeType>::GetPrevSibling(nsINode* aNode) {
|
||||
if (NS_WARN_IF(!aNode)) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -726,13 +614,12 @@ nsIContent* ContentIteratorBase<NodeType>::GetPrevSibling(
|
||||
return prev;
|
||||
}
|
||||
|
||||
nsINode* parent =
|
||||
IteratorHelpers::GetParentNode(*aNode, aAllowCrossShadowBoundary);
|
||||
nsINode* parent = aNode->GetParentNode();
|
||||
if (NS_WARN_IF(!parent)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ContentIteratorBase::GetPrevSibling(parent, aAllowCrossShadowBoundary);
|
||||
return ContentIteratorBase::GetPrevSibling(parent);
|
||||
}
|
||||
|
||||
template <typename NodeType>
|
||||
@ -973,41 +860,19 @@ nsresult ContentSubtreeIterator::Init(const RawRangeBoundary& aStartBoundary,
|
||||
return InitWithRange();
|
||||
}
|
||||
|
||||
nsresult ContentSubtreeIterator::InitWithAllowCrossShadowBoundary(
|
||||
AbstractRange* aRange) {
|
||||
MOZ_ASSERT(aRange);
|
||||
|
||||
if (NS_WARN_IF(!aRange->IsPositioned())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
mRange = aRange;
|
||||
|
||||
mAllowCrossShadowBoundary = AllowRangeCrossShadowBoundary::Yes;
|
||||
return InitWithRange();
|
||||
}
|
||||
|
||||
void ContentSubtreeIterator::CacheInclusiveAncestorsOfEndContainer() {
|
||||
mInclusiveAncestorsOfEndContainer.Clear();
|
||||
nsINode* const endContainer =
|
||||
IteratorHelpers::GetEndContainer(mRange, IterAllowCrossShadowBoundary());
|
||||
nsINode* const endContainer = mRange->GetEndContainer();
|
||||
nsIContent* endNode =
|
||||
endContainer->IsContent() ? endContainer->AsContent() : nullptr;
|
||||
while (endNode) {
|
||||
mInclusiveAncestorsOfEndContainer.AppendElement(endNode);
|
||||
// Cross the boundary for contents in shadow tree.
|
||||
nsINode* parent = IteratorHelpers::GetParentNode(
|
||||
*endNode, IterAllowCrossShadowBoundary());
|
||||
if (!parent || !parent->IsContent()) {
|
||||
break;
|
||||
}
|
||||
endNode = parent->AsContent();
|
||||
endNode = endNode->GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
nsIContent* ContentSubtreeIterator::DetermineCandidateForFirstContent() const {
|
||||
nsINode* startContainer = IteratorHelpers::GetStartContainer(
|
||||
mRange, IterAllowCrossShadowBoundary());
|
||||
nsINode* startContainer = mRange->GetStartContainer();
|
||||
nsIContent* firstCandidate = nullptr;
|
||||
// find first node in range
|
||||
nsINode* node = nullptr;
|
||||
@ -1015,14 +880,9 @@ nsIContent* ContentSubtreeIterator::DetermineCandidateForFirstContent() const {
|
||||
// no children, start at the node itself
|
||||
node = startContainer;
|
||||
} else {
|
||||
nsIContent* child =
|
||||
IterAllowCrossShadowBoundary()
|
||||
? mRange->GetMayCrossShadowBoundaryChildAtStartOffset()
|
||||
: mRange->GetChildAtStartOffset();
|
||||
|
||||
MOZ_ASSERT(child == startContainer->GetChildAt_Deprecated(
|
||||
IteratorHelpers::StartOffset(
|
||||
mRange, IterAllowCrossShadowBoundary())));
|
||||
nsIContent* child = mRange->GetChildAtStartOffset();
|
||||
MOZ_ASSERT(child ==
|
||||
startContainer->GetChildAt_Deprecated(mRange->StartOffset()));
|
||||
if (!child) {
|
||||
// offset after last child
|
||||
node = startContainer;
|
||||
@ -1033,13 +893,11 @@ nsIContent* ContentSubtreeIterator::DetermineCandidateForFirstContent() const {
|
||||
|
||||
if (!firstCandidate) {
|
||||
// then firstCandidate is next node after node
|
||||
firstCandidate = ContentIteratorBase::GetNextSibling(
|
||||
node, IterAllowCrossShadowBoundary());
|
||||
firstCandidate = ContentIteratorBase::GetNextSibling(node);
|
||||
}
|
||||
|
||||
if (firstCandidate) {
|
||||
firstCandidate = ContentIteratorBase::GetDeepFirstChild(
|
||||
firstCandidate, IterAllowCrossShadowBoundary());
|
||||
firstCandidate = ContentIteratorBase::GetDeepFirstChild(firstCandidate);
|
||||
}
|
||||
|
||||
return firstCandidate;
|
||||
@ -1068,12 +926,9 @@ nsIContent* ContentSubtreeIterator::DetermineFirstContent() const {
|
||||
|
||||
nsIContent* ContentSubtreeIterator::DetermineCandidateForLastContent() const {
|
||||
nsIContent* lastCandidate{nullptr};
|
||||
nsINode* endContainer =
|
||||
IteratorHelpers::GetEndContainer(mRange, IterAllowCrossShadowBoundary());
|
||||
nsINode* endContainer = mRange->GetEndContainer();
|
||||
// now to find the last node
|
||||
int32_t offset =
|
||||
IteratorHelpers::EndOffset(mRange, IterAllowCrossShadowBoundary());
|
||||
|
||||
int32_t offset = mRange->EndOffset();
|
||||
int32_t numChildren = endContainer->GetChildCount();
|
||||
|
||||
nsINode* node = nullptr;
|
||||
@ -1084,9 +939,7 @@ nsIContent* ContentSubtreeIterator::DetermineCandidateForLastContent() const {
|
||||
if (!offset || !numChildren) {
|
||||
node = endContainer;
|
||||
} else {
|
||||
lastCandidate = IterAllowCrossShadowBoundary()
|
||||
? mRange->MayCrossShadowBoundaryEndRef().Ref()
|
||||
: mRange->EndRef().Ref();
|
||||
lastCandidate = mRange->EndRef().Ref();
|
||||
MOZ_ASSERT(lastCandidate == endContainer->GetChildAt_Deprecated(--offset));
|
||||
NS_ASSERTION(lastCandidate,
|
||||
"tree traversal trouble in ContentSubtreeIterator::Init");
|
||||
@ -1094,13 +947,11 @@ nsIContent* ContentSubtreeIterator::DetermineCandidateForLastContent() const {
|
||||
|
||||
if (!lastCandidate) {
|
||||
// then lastCandidate is prev node before node
|
||||
lastCandidate = ContentIteratorBase::GetPrevSibling(
|
||||
node, IterAllowCrossShadowBoundary());
|
||||
lastCandidate = ContentIteratorBase::GetPrevSibling(node);
|
||||
}
|
||||
|
||||
if (lastCandidate) {
|
||||
lastCandidate = ContentIteratorBase::GetDeepLastChild(
|
||||
lastCandidate, IterAllowCrossShadowBoundary());
|
||||
lastCandidate = ContentIteratorBase::GetDeepLastChild(lastCandidate);
|
||||
}
|
||||
|
||||
return lastCandidate;
|
||||
@ -1111,17 +962,11 @@ nsresult ContentSubtreeIterator::InitWithRange() {
|
||||
MOZ_ASSERT(mRange->IsPositioned());
|
||||
|
||||
// get the start node and offset, convert to nsINode
|
||||
mClosestCommonInclusiveAncestor =
|
||||
mRange->GetClosestCommonInclusiveAncestor(mAllowCrossShadowBoundary);
|
||||
|
||||
nsINode* startContainer = IteratorHelpers::GetStartContainer(
|
||||
mRange, IterAllowCrossShadowBoundary());
|
||||
const int32_t startOffset =
|
||||
IteratorHelpers::StartOffset(mRange, IterAllowCrossShadowBoundary());
|
||||
nsINode* endContainer =
|
||||
IteratorHelpers::GetEndContainer(mRange, IterAllowCrossShadowBoundary());
|
||||
const int32_t endOffset =
|
||||
IteratorHelpers::EndOffset(mRange, IterAllowCrossShadowBoundary());
|
||||
mClosestCommonInclusiveAncestor = mRange->GetClosestCommonInclusiveAncestor();
|
||||
nsINode* startContainer = mRange->GetStartContainer();
|
||||
const int32_t startOffset = mRange->StartOffset();
|
||||
nsINode* endContainer = mRange->GetEndContainer();
|
||||
const int32_t endOffset = mRange->EndOffset();
|
||||
MOZ_ASSERT(mClosestCommonInclusiveAncestor && startContainer && endContainer);
|
||||
// Bug 767169
|
||||
MOZ_ASSERT(uint32_t(startOffset) <= startContainer->Length() &&
|
||||
@ -1199,24 +1044,15 @@ void ContentSubtreeIterator::Next() {
|
||||
return;
|
||||
}
|
||||
|
||||
nsINode* nextNode = ContentIteratorBase::GetNextSibling(
|
||||
mCurNode, IterAllowCrossShadowBoundary());
|
||||
|
||||
MOZ_ASSERT(nextNode, "No next sibling!?! This could mean deadlock!");
|
||||
nsINode* nextNode = ContentIteratorBase::GetNextSibling(mCurNode);
|
||||
NS_ASSERTION(nextNode, "No next sibling!?! This could mean deadlock!");
|
||||
|
||||
int32_t i = mInclusiveAncestorsOfEndContainer.IndexOf(nextNode);
|
||||
while (i != -1) {
|
||||
// as long as we are finding ancestors of the endpoint of the range,
|
||||
// dive down into their children
|
||||
ShadowRoot* root = IteratorHelpers::GetShadowRoot(
|
||||
Element::FromNode(nextNode), IterAllowCrossShadowBoundary());
|
||||
if (!root) {
|
||||
nextNode = nextNode->GetFirstChild();
|
||||
} else {
|
||||
nextNode = mRange->MayCrossShadowBoundary() ? root->GetFirstChild()
|
||||
: nextNode->GetFirstChild();
|
||||
}
|
||||
MOZ_ASSERT(nextNode, "Iterator error, expected a child node!");
|
||||
nextNode = nextNode->GetFirstChild();
|
||||
NS_ASSERTION(nextNode, "Iterator error, expected a child node!");
|
||||
|
||||
// should be impossible to get a null pointer. If we went all the way
|
||||
// down the child chain to the bottom without finding an interior node,
|
||||
@ -1262,8 +1098,7 @@ nsresult ContentSubtreeIterator::PositionAt(nsINode* aCurNode) {
|
||||
|
||||
nsIContent* ContentSubtreeIterator::GetTopAncestorInRange(
|
||||
nsINode* aNode) const {
|
||||
if (!aNode ||
|
||||
!IteratorHelpers::GetParentNode(*aNode, IterAllowCrossShadowBoundary())) {
|
||||
if (!aNode || !aNode->GetParentNode()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -1279,23 +1114,15 @@ nsIContent* ContentSubtreeIterator::GetTopAncestorInRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsIContent* lastContentInShadowTree = nullptr;
|
||||
while (content) {
|
||||
nsINode* parent = IteratorHelpers::GetParentNode(
|
||||
*content, IterAllowCrossShadowBoundary());
|
||||
|
||||
nsIContent* parent = content->GetParent();
|
||||
// content always has a parent. If its parent is the root, however --
|
||||
// i.e., either it's not content, or it is content but its own parent is
|
||||
// null -- then we're finished, since we don't go up to the root.
|
||||
//
|
||||
// Caveat: If iteration crossing shadow boundary is allowed
|
||||
// and the root is a shadow root, we keep going up to the
|
||||
// shadow host and continue.
|
||||
//
|
||||
// We have to special-case this because CompareNodeToRange treats the root
|
||||
// node differently -- see bug 765205.
|
||||
if (!parent || !IteratorHelpers::GetParentNode(
|
||||
*parent, IterAllowCrossShadowBoundary())) {
|
||||
if (!parent || !parent->GetParentNode()) {
|
||||
return content;
|
||||
}
|
||||
|
||||
@ -1303,28 +1130,10 @@ nsIContent* ContentSubtreeIterator::GetTopAncestorInRange(
|
||||
RangeUtils::IsNodeContainedInRange(*parent, mRange);
|
||||
MOZ_ALWAYS_TRUE(isNodeContainedInRange);
|
||||
if (!isNodeContainedInRange.value()) {
|
||||
if (IterAllowCrossShadowBoundary() && content->IsShadowRoot()) {
|
||||
MOZ_ASSERT(parent->GetShadowRoot() == content);
|
||||
// host element is not in range, the last content in tree
|
||||
// should be the ancestor.
|
||||
MOZ_ASSERT(lastContentInShadowTree);
|
||||
return lastContentInShadowTree;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
// When we cross the boundary, we keep a reference to the
|
||||
// last content that is in tree, because if we later
|
||||
// find the shadow host element is not in the range, that means
|
||||
// the last content in the tree should be top ancestor in range.
|
||||
//
|
||||
// Using shadow root doesn't make sense here because it doesn't
|
||||
// represent a actual content.
|
||||
if (IterAllowCrossShadowBoundary() && parent->IsShadowRoot()) {
|
||||
lastContentInShadowTree = content;
|
||||
}
|
||||
|
||||
content = parent->AsContent();
|
||||
content = parent;
|
||||
}
|
||||
|
||||
MOZ_CRASH("This should only be possible if aNode was null");
|
||||
|
@ -82,26 +82,15 @@ class ContentIteratorBase {
|
||||
// Recursively get the deepest first/last child of aRoot. This will return
|
||||
// aRoot itself if it has no children.
|
||||
static nsINode* GetDeepFirstChild(nsINode* aRoot);
|
||||
// If aAllowCrossShadowBoundary is true, it'll continue with the shadow tree
|
||||
// when it reaches to a shadow host.
|
||||
static nsIContent* GetDeepFirstChild(nsIContent* aRoot,
|
||||
bool aAllowCrossShadowBoundary);
|
||||
static nsIContent* GetDeepFirstChild(nsIContent* aRoot);
|
||||
static nsINode* GetDeepLastChild(nsINode* aRoot);
|
||||
// If aAllowCrossShadowBoundary is true, it'll continue with the shadow tree
|
||||
// when it reaches to a shadow host.
|
||||
static nsIContent* GetDeepLastChild(nsIContent* aRoot,
|
||||
bool aAllowCrossShadowBoundary);
|
||||
static nsIContent* GetDeepLastChild(nsIContent* aRoot);
|
||||
|
||||
// Get the next/previous sibling of aNode, or its parent's, or grandparent's,
|
||||
// etc. Returns null if aNode and all its ancestors have no next/previous
|
||||
// sibling.
|
||||
//
|
||||
// If aAllowCrossShadowBoundary is true, it'll continue with the shadow host
|
||||
// when it reaches to a shadow root.
|
||||
static nsIContent* GetNextSibling(nsINode* aNode,
|
||||
bool aAllowCrossShadowBoundary = false);
|
||||
static nsIContent* GetPrevSibling(nsINode* aNode,
|
||||
bool aAllowCrossShadowBoundary = false);
|
||||
static nsIContent* GetNextSibling(nsINode* aNode);
|
||||
static nsIContent* GetPrevSibling(nsINode* aNode);
|
||||
|
||||
nsINode* NextNode(nsINode* aNode);
|
||||
nsINode* PrevNode(nsINode* aNode);
|
||||
@ -230,29 +219,6 @@ class ContentSubtreeIterator final : public SafeContentIteratorBase {
|
||||
virtual nsresult Init(nsINode* aRoot) override;
|
||||
|
||||
virtual nsresult Init(dom::AbstractRange* aRange) override;
|
||||
|
||||
/**
|
||||
* Initialize the iterator with aRange that does correct things
|
||||
* when the aRange's start and/or the end containers are
|
||||
* in shadow dom.
|
||||
*
|
||||
* If both start and end containers are in light dom, the iterator
|
||||
* won't do anything special.
|
||||
*
|
||||
* When the start container is in shadow dom, the iterator can
|
||||
* find the correct start node by crossing the shadow
|
||||
* boundary when needed.
|
||||
*
|
||||
* When the end container is in shadow dom, the iterator can find
|
||||
* the correct end node by crossing the shadow boundary when
|
||||
* needed. Also when the next node is an ancestor of
|
||||
* the end node, it can correctly iterate into the
|
||||
* subtree of it by crossing the shadow boundary.
|
||||
*
|
||||
* Examples of what nodes will be returned can be found
|
||||
* at test_content_iterator_subtree_shadow_tree.html.
|
||||
*/
|
||||
nsresult InitWithAllowCrossShadowBoundary(dom::AbstractRange* aRange);
|
||||
virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
|
||||
nsINode* aEndContainer, uint32_t aEndOffset) override;
|
||||
virtual nsresult Init(const RawRangeBoundary& aStartBoundary,
|
||||
@ -310,18 +276,10 @@ class ContentSubtreeIterator final : public SafeContentIteratorBase {
|
||||
// the range's start and end nodes will never be considered "in" it.
|
||||
nsIContent* GetTopAncestorInRange(nsINode* aNode) const;
|
||||
|
||||
bool IterAllowCrossShadowBoundary() const {
|
||||
return mAllowCrossShadowBoundary == dom::AllowRangeCrossShadowBoundary::Yes;
|
||||
}
|
||||
|
||||
RefPtr<dom::AbstractRange> mRange;
|
||||
|
||||
// See <https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor>.
|
||||
AutoTArray<nsIContent*, 8> mInclusiveAncestorsOfEndContainer;
|
||||
|
||||
// Whether this iterator allows to iterate nodes across shadow boundary.
|
||||
dom::AllowRangeCrossShadowBoundary mAllowCrossShadowBoundary =
|
||||
dom::AllowRangeCrossShadowBoundary::No;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -1347,22 +1347,6 @@ already_AddRefed<ShadowRoot> Element::AttachShadowWithoutNameChecks(
|
||||
dispatcher->PostDOMEvent();
|
||||
}
|
||||
|
||||
const LinkedList<AbstractRange>* ranges =
|
||||
GetExistingClosestCommonInclusiveAncestorRanges();
|
||||
if (ranges) {
|
||||
for (const AbstractRange* range : *ranges) {
|
||||
if (range->MayCrossShadowBoundary()) {
|
||||
MOZ_ASSERT(range->IsDynamicRange());
|
||||
StaticRange* crossBoundaryRange =
|
||||
range->AsDynamicRange()->GetCrossShadowBoundaryRange();
|
||||
MOZ_ASSERT(crossBoundaryRange);
|
||||
// We may have previously selected this node before it
|
||||
// becomes a shadow host, so we need to reset the values
|
||||
// in RangeBoundaries to accommodate the change.
|
||||
crossBoundaryRange->NotifyNodeBecomesShadowHost(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 10. Return shadow.
|
||||
*/
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIContent.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
@ -352,34 +351,6 @@ class RangeBoundaryBase {
|
||||
}
|
||||
|
||||
public:
|
||||
void NotifyParentBecomesShadowHost() {
|
||||
MOZ_ASSERT(mParent);
|
||||
MOZ_ASSERT(mParent->IsContainerNode(),
|
||||
"Range is positioned on a text node!");
|
||||
if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mIsMutationObserved) {
|
||||
// RangeBoundaries that are not used in the context of a
|
||||
// `MutationObserver` use the offset as main source of truth to compute
|
||||
// `mRef`. Therefore, it must not be updated or invalidated.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mRef) {
|
||||
MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0,
|
||||
"Invalidating offset of invalid RangeBoundary?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (dom::ShadowRoot* shadowRoot = mParent->GetShadowRootForSelection()) {
|
||||
mParent = shadowRoot;
|
||||
}
|
||||
|
||||
mOffset = Some(0);
|
||||
}
|
||||
|
||||
bool IsSet() const { return mParent && (mRef || mOffset.isSome()); }
|
||||
|
||||
bool IsSetAndValid() const {
|
||||
|
@ -147,10 +147,9 @@ nsresult RangeUtils::CompareNodeToRange(nsINode* aNode,
|
||||
NS_WARN_IF(!aAbstractRange->IsPositioned())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
return CompareNodeToRangeBoundaries(
|
||||
aNode, aAbstractRange->MayCrossShadowBoundaryStartRef(),
|
||||
aAbstractRange->MayCrossShadowBoundaryEndRef(), aNodeIsBeforeRange,
|
||||
aNodeIsAfterRange);
|
||||
return CompareNodeToRangeBoundaries(aNode, aAbstractRange->StartRef(),
|
||||
aAbstractRange->EndRef(),
|
||||
aNodeIsBeforeRange, aNodeIsAfterRange);
|
||||
}
|
||||
template <typename SPT, typename SRT, typename EPT, typename ERT>
|
||||
nsresult RangeUtils::CompareNodeToRangeBoundaries(
|
||||
|
@ -108,22 +108,6 @@ ScriptableContentIterator::InitWithRange(IteratorType aType, nsRange* aRange) {
|
||||
return mContentIterator->Init(aRange);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::InitWithRangeAllowCrossShadowBoundary(
|
||||
IteratorType aType, nsRange* aRange) {
|
||||
if (aType == NOT_INITIALIZED ||
|
||||
(mIteratorType != NOT_INITIALIZED && aType != mIteratorType) ||
|
||||
aType != SUBTREE_ITERATOR) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
mIteratorType = aType;
|
||||
MOZ_ASSERT(mIteratorType == SUBTREE_ITERATOR);
|
||||
EnsureContentIterator();
|
||||
return static_cast<ContentSubtreeIterator*>(mContentIterator.get())
|
||||
->InitWithAllowCrossShadowBoundary(aRange);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::InitWithPositions(IteratorType aType,
|
||||
nsINode* aStartContainer,
|
||||
|
@ -22,11 +22,9 @@
|
||||
#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"
|
||||
#include "mozilla/dom/ShadowIncludingTreeIterator.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/HTMLEditor.h"
|
||||
#include "mozilla/IntegerRange.h"
|
||||
@ -780,39 +778,30 @@ NS_INTERFACE_MAP_END
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Selection)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Selection, Disconnect())
|
||||
|
||||
const RangeBoundary& Selection::AnchorRef(
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
|
||||
const RangeBoundary& Selection::AnchorRef() const {
|
||||
if (!mAnchorFocusRange) {
|
||||
static RangeBoundary sEmpty;
|
||||
return sEmpty;
|
||||
}
|
||||
|
||||
if (GetDirection() == eDirNext) {
|
||||
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
|
||||
? mAnchorFocusRange->MayCrossShadowBoundaryStartRef()
|
||||
: mAnchorFocusRange->StartRef();
|
||||
return mAnchorFocusRange->StartRef();
|
||||
}
|
||||
|
||||
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
|
||||
? mAnchorFocusRange->MayCrossShadowBoundaryEndRef()
|
||||
: mAnchorFocusRange->EndRef();
|
||||
return mAnchorFocusRange->EndRef();
|
||||
}
|
||||
|
||||
const RangeBoundary& Selection::FocusRef(
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) const {
|
||||
const RangeBoundary& Selection::FocusRef() const {
|
||||
if (!mAnchorFocusRange) {
|
||||
static RangeBoundary sEmpty;
|
||||
return sEmpty;
|
||||
}
|
||||
|
||||
if (GetDirection() == eDirNext) {
|
||||
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
|
||||
? mAnchorFocusRange->MayCrossShadowBoundaryEndRef()
|
||||
: mAnchorFocusRange->EndRef();
|
||||
return mAnchorFocusRange->EndRef();
|
||||
}
|
||||
return aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
|
||||
? mAnchorFocusRange->MayCrossShadowBoundaryStartRef()
|
||||
: mAnchorFocusRange->StartRef();
|
||||
|
||||
return mAnchorFocusRange->StartRef();
|
||||
}
|
||||
|
||||
void Selection::SetAnchorFocusRange(size_t aIndex) {
|
||||
@ -829,8 +818,8 @@ static int32_t CompareToRangeStart(const nsINode& aCompareNode,
|
||||
uint32_t aCompareOffset,
|
||||
const AbstractRange& aRange,
|
||||
nsContentUtils::NodeIndexCache* aCache) {
|
||||
MOZ_ASSERT(aRange.GetMayCrossShadowBoundaryStartContainer());
|
||||
nsINode* start = aRange.GetMayCrossShadowBoundaryStartContainer();
|
||||
MOZ_ASSERT(aRange.GetStartContainer());
|
||||
nsINode* start = aRange.GetStartContainer();
|
||||
// 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() ||
|
||||
@ -841,9 +830,8 @@ 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.MayCrossShadowBoundaryStartOffset(), aCache);
|
||||
return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, start,
|
||||
aRange.StartOffset(), aCache);
|
||||
}
|
||||
|
||||
static int32_t CompareToRangeStart(const nsINode& aCompareNode,
|
||||
@ -856,7 +844,7 @@ static int32_t CompareToRangeEnd(const nsINode& aCompareNode,
|
||||
uint32_t aCompareOffset,
|
||||
const AbstractRange& aRange) {
|
||||
MOZ_ASSERT(aRange.IsPositioned());
|
||||
nsINode* end = aRange.GetMayCrossShadowBoundaryEndContainer();
|
||||
nsINode* end = aRange.GetEndContainer();
|
||||
// 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() ||
|
||||
@ -867,9 +855,8 @@ 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.MayCrossShadowBoundaryEndOffset());
|
||||
return *nsContentUtils::ComparePoints(&aCompareNode, aCompareOffset, end,
|
||||
aRange.EndOffset());
|
||||
}
|
||||
|
||||
// static
|
||||
@ -1336,18 +1323,7 @@ nsresult Selection::RemoveCollapsedRanges() {
|
||||
nsresult Selection::StyledRanges::RemoveCollapsedRanges() {
|
||||
uint32_t i = 0;
|
||||
while (i < mRanges.Length()) {
|
||||
const AbstractRange* range = mRanges[i].mRange;
|
||||
// If nsRange::mCrossShadowBoundaryRange exists, it means
|
||||
// there's a cross boundary selection, so obviously
|
||||
// we shouldn't remove this range.
|
||||
const bool collapsed =
|
||||
range->Collapsed() && !range->MayCrossShadowBoundary();
|
||||
// Cross boundary range should always be uncollapsed.
|
||||
MOZ_ASSERT_IF(
|
||||
range->MayCrossShadowBoundary(),
|
||||
!range->AsDynamicRange()->CrossShadowBoundaryRangeCollapsed());
|
||||
|
||||
if (collapsed) {
|
||||
if (mRanges[i].mRange->Collapsed()) {
|
||||
nsresult rv = RemoveRangeAndUnregisterSelection(*mRanges[i].mRange);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
@ -1640,8 +1616,7 @@ 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->MayCrossShadowBoundaryEndRef().Equals(aBeginNode,
|
||||
aBeginOffset) &&
|
||||
if (beginRange->EndRef().Equals(aBeginNode, aBeginOffset) &&
|
||||
!beginRange->Collapsed()) {
|
||||
beginsAfterIndex++;
|
||||
}
|
||||
@ -1652,8 +1627,7 @@ nsresult Selection::StyledRanges::GetIndicesForInterval(
|
||||
// included
|
||||
if (endsBeforeIndex < mRanges.Length()) {
|
||||
const AbstractRange* endRange = mRanges[endsBeforeIndex].mRange;
|
||||
if (endRange->MayCrossShadowBoundaryStartRef().Equals(aEndNode,
|
||||
aEndOffset) &&
|
||||
if (endRange->StartRef().Equals(aEndNode, aEndOffset) &&
|
||||
endRange->Collapsed()) {
|
||||
endsBeforeIndex++;
|
||||
}
|
||||
@ -1736,16 +1710,6 @@ nsresult Selection::SelectFramesOfInclusiveDescendantsOfContent(
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Selection::SelectFramesOfShadowIncludingDescendantsOfContent(
|
||||
nsIContent* aContent, bool aSelected) const {
|
||||
MOZ_ASSERT(aContent);
|
||||
MOZ_ASSERT(StaticPrefs::dom_shadowdom_selection_across_boundary_enabled());
|
||||
for (nsINode* node : ShadowIncludingTreeIterator(*aContent)) {
|
||||
nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr;
|
||||
SelectFramesOf(innercontent, aSelected);
|
||||
}
|
||||
}
|
||||
|
||||
void Selection::SelectFramesInAllRanges(nsPresContext* aPresContext) {
|
||||
// this method is currently only called in a user-initiated context.
|
||||
// therefore it is safe to assume that we are not in a Highlight selection
|
||||
@ -1784,22 +1748,16 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
|
||||
|
||||
if (mFrameSelection->IsInTableSelectionMode()) {
|
||||
const nsIContent* const commonAncestorContent =
|
||||
nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor(
|
||||
StaticPrefs::dom_select_events_textcontrols_selectstart_enabled()
|
||||
? AllowRangeCrossShadowBoundary::Yes
|
||||
: AllowRangeCrossShadowBoundary::No));
|
||||
nsIContent::FromNodeOrNull(aRange.GetClosestCommonInclusiveAncestor());
|
||||
nsIFrame* const frame = commonAncestorContent
|
||||
? commonAncestorContent->GetPrimaryFrame()
|
||||
: aPresContext->PresShell()->GetRootFrame();
|
||||
if (frame) {
|
||||
if (frame->IsTextFrame()) {
|
||||
MOZ_ASSERT(commonAncestorContent ==
|
||||
aRange.GetMayCrossShadowBoundaryStartContainer());
|
||||
MOZ_ASSERT(commonAncestorContent ==
|
||||
aRange.GetMayCrossShadowBoundaryEndContainer());
|
||||
MOZ_ASSERT(commonAncestorContent == aRange.GetStartContainer());
|
||||
MOZ_ASSERT(commonAncestorContent == aRange.GetEndContainer());
|
||||
static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
|
||||
aRange.MayCrossShadowBoundaryStartOffset(),
|
||||
aRange.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType);
|
||||
aRange.StartOffset(), aRange.EndOffset(), aSelect, mSelectionType);
|
||||
} else {
|
||||
frame->SelectionStateChanged();
|
||||
}
|
||||
@ -1810,8 +1768,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.GetMayCrossShadowBoundaryStartContainer());
|
||||
nsIContent* const startContent =
|
||||
nsIContent::FromNodeOrNull(aRange.GetStartContainer());
|
||||
if (MOZ_UNLIKELY(!startContent)) {
|
||||
// Don't warn, bug 1055722
|
||||
// XXX The range can start from a document node and such range can be
|
||||
@ -1822,7 +1780,7 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
|
||||
MOZ_DIAGNOSTIC_ASSERT(startContent->IsInComposedDoc());
|
||||
|
||||
// We must call first one explicitly
|
||||
nsINode* const endNode = aRange.GetMayCrossShadowBoundaryEndContainer();
|
||||
nsINode* const endNode = aRange.GetEndContainer();
|
||||
if (NS_WARN_IF(!endNode)) {
|
||||
// We null-checked start node above, therefore, end node should also be
|
||||
// non-null here.
|
||||
@ -1834,10 +1792,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.MayCrossShadowBoundaryStartOffset();
|
||||
const uint32_t endOffset =
|
||||
endNode == startContent ? aRange.MayCrossShadowBoundaryEndOffset()
|
||||
: startContent->Length();
|
||||
const uint32_t startOffset = aRange.StartOffset();
|
||||
const uint32_t endOffset = endNode == startContent
|
||||
? aRange.EndOffset()
|
||||
: startContent->Length();
|
||||
static_cast<nsTextFrame*>(frame)->SelectionStateChanged(
|
||||
startOffset, endOffset, aSelect, mSelectionType);
|
||||
} else {
|
||||
@ -1848,7 +1806,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() && !aRange.MayCrossShadowBoundary()) ||
|
||||
if (aRange.Collapsed() ||
|
||||
(startContent == endNode && !startContent->HasChildren())) {
|
||||
if (!isFirstContentTextNode) {
|
||||
SelectFramesOf(startContent, aSelect);
|
||||
@ -1857,7 +1815,7 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
|
||||
}
|
||||
|
||||
ContentSubtreeIterator subtreeIter;
|
||||
subtreeIter.InitWithAllowCrossShadowBoundary(&aRange);
|
||||
subtreeIter.Init(&aRange);
|
||||
if (isFirstContentTextNode && !subtreeIter.IsDone() &&
|
||||
subtreeIter.GetCurrentNode() == startContent) {
|
||||
subtreeIter.Next(); // first content has already been handled.
|
||||
@ -1867,12 +1825,8 @@ nsresult Selection::SelectFrames(nsPresContext* aPresContext,
|
||||
MOZ_DIAGNOSTIC_ASSERT(subtreeIter.GetCurrentNode());
|
||||
if (nsIContent* const content =
|
||||
nsIContent::FromNodeOrNull(subtreeIter.GetCurrentNode())) {
|
||||
if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
|
||||
SelectFramesOfShadowIncludingDescendantsOfContent(content, aSelect);
|
||||
} else {
|
||||
SelectFramesOfInclusiveDescendantsOfContent(postOrderIter, content,
|
||||
aSelect);
|
||||
}
|
||||
SelectFramesOfInclusiveDescendantsOfContent(postOrderIter, content,
|
||||
aSelect);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1885,7 +1839,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.MayCrossShadowBoundaryEndOffset(), aSelect, mSelectionType);
|
||||
0, aRange.EndOffset(), aSelect, mSelectionType);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
@ -1947,11 +1901,10 @@ UniquePtr<SelectionDetails> Selection::LookUpSelection(
|
||||
if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer();
|
||||
nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer();
|
||||
uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset();
|
||||
uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset();
|
||||
nsINode* startNode = range->GetStartContainer();
|
||||
nsINode* endNode = range->GetEndContainer();
|
||||
uint32_t startOffset = range->StartOffset();
|
||||
uint32_t endOffset = range->EndOffset();
|
||||
|
||||
Maybe<uint32_t> start, end;
|
||||
if (startNode == aContent && endNode == aContent) {
|
||||
@ -2231,67 +2184,6 @@ void Selection::RemoveAllRanges(ErrorResult& aRv) {
|
||||
RemoveAllRangesInternal(aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<StaticRange> Selection::GetComposedRange(
|
||||
const AbstractRange* aRange,
|
||||
const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots) const {
|
||||
// If aIsEndNode is true, this method does the Step 5.1 and 5.2
|
||||
// in https://www.w3.org/TR/selection-api/#dom-selection-getcomposedranges,
|
||||
// otherwise it does the Step 3.1 and 3.2.
|
||||
auto reScope = [&aShadowRoots](nsINode*& aNode, uint32_t& aOffset,
|
||||
bool aIsEndNode) {
|
||||
MOZ_ASSERT(aNode);
|
||||
while (aNode) {
|
||||
const ShadowRoot* shadowRootOfNode = aNode->GetContainingShadow();
|
||||
if (!shadowRootOfNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const OwningNonNull<ShadowRoot>& shadowRoot : aShadowRoots) {
|
||||
if (shadowRoot->IsShadowIncludingInclusiveDescendantOf(
|
||||
shadowRootOfNode)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const nsIContent* host = aNode->GetContainingShadowHost();
|
||||
const Maybe<uint32_t> maybeIndex = host->ComputeIndexInParentContent();
|
||||
MOZ_ASSERT(maybeIndex.isSome(), "not parent or anonymous child?");
|
||||
if (MOZ_UNLIKELY(maybeIndex.isNothing())) {
|
||||
// Unlikely to happen, but still set aNode to nullptr to avoid
|
||||
// leaking information about the shadow tree.
|
||||
aNode = nullptr;
|
||||
return;
|
||||
}
|
||||
aOffset = maybeIndex.value();
|
||||
if (aIsEndNode) {
|
||||
aOffset += 1;
|
||||
}
|
||||
aNode = host->GetParentNode();
|
||||
}
|
||||
};
|
||||
|
||||
nsINode* startNode = aRange->GetMayCrossShadowBoundaryStartContainer();
|
||||
uint32_t startOffset = aRange->MayCrossShadowBoundaryStartOffset();
|
||||
nsINode* endNode = aRange->GetMayCrossShadowBoundaryEndContainer();
|
||||
uint32_t endOffset = aRange->MayCrossShadowBoundaryEndOffset();
|
||||
|
||||
reScope(startNode, startOffset, false /* aIsEndNode */);
|
||||
reScope(endNode, endOffset, true /* aIsEndNode */);
|
||||
|
||||
RefPtr<StaticRange> composedRange = StaticRange::Create(
|
||||
startNode, startOffset, endNode, endOffset, IgnoreErrors());
|
||||
return composedRange.forget();
|
||||
}
|
||||
|
||||
void Selection::GetComposedRanges(
|
||||
const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots,
|
||||
nsTArray<RefPtr<StaticRange>>& aComposedRanges) {
|
||||
aComposedRanges.SetCapacity(mStyledRanges.mRanges.Length());
|
||||
for (const auto& range : mStyledRanges.mRanges) {
|
||||
aComposedRanges.AppendElement(GetComposedRange(range.mRange, aShadowRoots));
|
||||
}
|
||||
}
|
||||
|
||||
void Selection::RemoveAllRangesInternal(ErrorResult& aRv) {
|
||||
if (!mFrameSelection) {
|
||||
aRv.Throw(NS_ERROR_NOT_INITIALIZED);
|
||||
@ -2784,19 +2676,6 @@ AbstractRange* Selection::GetAbstractRangeAt(uint32_t aIndex) const {
|
||||
return mStyledRanges.mRanges.SafeElementAt(aIndex, empty).mRange;
|
||||
}
|
||||
|
||||
void Selection::GetDirection(nsAString& aDirection) const {
|
||||
if (mStyledRanges.mRanges.IsEmpty() ||
|
||||
(mFrameSelection && (mFrameSelection->IsDoubleClickSelection() ||
|
||||
mFrameSelection->IsTripleClickSelection()))) {
|
||||
// Empty range and double/triple clicks result a directionless selection.
|
||||
aDirection.AssignLiteral("none");
|
||||
} else if (mDirection == nsDirection::eDirPrevious) {
|
||||
aDirection.AssignLiteral("backward");
|
||||
} else {
|
||||
aDirection.AssignLiteral("forward");
|
||||
}
|
||||
}
|
||||
|
||||
nsRange* Selection::GetRangeAt(uint32_t aIndex) const {
|
||||
// This method per IDL spec returns a dynamic range.
|
||||
// Therefore, it must be ensured that it is only called
|
||||
@ -2961,17 +2840,17 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
|
||||
#ifdef DEBUG_SELECTION
|
||||
nsDirection oldDirection = GetDirection();
|
||||
#endif
|
||||
nsINode* anchorNode = GetMayCrossShadowBoundaryAnchorNode();
|
||||
nsINode* focusNode = GetMayCrossShadowBoundaryFocusNode();
|
||||
const uint32_t anchorOffset = MayCrossShadowBoundaryAnchorOffset();
|
||||
const uint32_t focusOffset = MayCrossShadowBoundaryFocusOffset();
|
||||
nsINode* anchorNode = GetAnchorNode();
|
||||
nsINode* focusNode = GetFocusNode();
|
||||
const uint32_t anchorOffset = AnchorOffset();
|
||||
const uint32_t focusOffset = FocusOffset();
|
||||
|
||||
RefPtr<nsRange> range = mAnchorFocusRange->CloneRange();
|
||||
|
||||
nsINode* startNode = range->GetMayCrossShadowBoundaryStartContainer();
|
||||
nsINode* endNode = range->GetMayCrossShadowBoundaryEndContainer();
|
||||
const uint32_t startOffset = range->MayCrossShadowBoundaryStartOffset();
|
||||
const uint32_t endOffset = range->MayCrossShadowBoundaryEndOffset();
|
||||
nsINode* startNode = range->GetStartContainer();
|
||||
nsINode* endNode = range->GetEndContainer();
|
||||
const uint32_t startOffset = range->StartOffset();
|
||||
const uint32_t endOffset = range->EndOffset();
|
||||
|
||||
bool shouldClearRange = false;
|
||||
const Maybe<int32_t> anchorOldFocusOrder = nsContentUtils::ComparePoints(
|
||||
@ -3007,8 +2886,7 @@ 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,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetEnd(aContainer, aOffset, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
@ -3029,8 +2907,7 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
|
||||
*anchorNewFocusOrder > 0) { // 2, a1
|
||||
// select from 2 to 1a
|
||||
SetDirection(eDirPrevious);
|
||||
range->SetStart(aContainer, aOffset, aRv,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetStart(aContainer, aOffset, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
@ -3050,8 +2927,7 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
|
||||
return;
|
||||
}
|
||||
|
||||
range->SetEnd(aContainer, aOffset, aRv,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetEnd(aContainer, aOffset, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
@ -3061,33 +2937,27 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
|
||||
return;
|
||||
}
|
||||
SelectFrames(presContext, *difRange, false); // deselect now
|
||||
difRange->SetEnd(range->GetMayCrossShadowBoundaryEndContainer(),
|
||||
range->MayCrossShadowBoundaryEndOffset(),
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
difRange->SetEnd(range->GetEndContainer(), range->EndOffset());
|
||||
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,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
res = range->SetStart(endNode, endOffset);
|
||||
if (NS_FAILED(res)) {
|
||||
aRv.Throw(res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
SetDirection(eDirNext);
|
||||
range->SetEnd(aContainer, aOffset, aRv,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetEnd(aContainer, aOffset, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
if (focusNode != anchorNode ||
|
||||
focusOffset != anchorOffset) { // if collapsed diff dont do anything
|
||||
res = difRange->SetStart(focusNode, focusOffset,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
res = difRange->SetStart(focusNode, focusOffset);
|
||||
nsresult tmp = difRange->SetEnd(anchorNode, anchorOffset);
|
||||
if (NS_FAILED(tmp)) {
|
||||
res = tmp;
|
||||
}
|
||||
@ -3121,8 +2991,7 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
|
||||
return;
|
||||
}
|
||||
SetDirection(eDirPrevious);
|
||||
range->SetStart(aContainer, aOffset, aRv,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetStart(aContainer, aOffset, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
@ -3133,19 +3002,15 @@ void Selection::Extend(nsINode& aContainer, uint32_t aOffset,
|
||||
return;
|
||||
}
|
||||
SelectFrames(presContext, *difRange, false);
|
||||
difRange->SetStart(range->GetMayCrossShadowBoundaryStartContainer(),
|
||||
range->MayCrossShadowBoundaryStartOffset(),
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
difRange->SetStart(range->GetStartContainer(), range->StartOffset());
|
||||
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,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetEnd(startNode, startOffset);
|
||||
}
|
||||
SetDirection(eDirPrevious);
|
||||
range->SetStart(aContainer, aOffset, aRv,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetStart(aContainer, aOffset, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
@ -3175,8 +3040,7 @@ 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,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetStart(aContainer, aOffset, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
@ -3744,10 +3608,9 @@ void Selection::NotifySelectionListeners() {
|
||||
|
||||
RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
|
||||
|
||||
// This flag will be set to Double or Triple if a selection by double click or
|
||||
// triple click is detected. As soon as the selection is modified, it needs to
|
||||
// be reset to NotApplicable.
|
||||
frameSelection->SetClickSelectionType(ClickSelectionType::NotApplicable);
|
||||
// This flag will be set to true if a selection by double click is detected.
|
||||
// As soon as the selection is modified, it needs to be set to false.
|
||||
frameSelection->SetIsDoubleClickSelection(false);
|
||||
|
||||
if (frameSelection->IsBatching()) {
|
||||
frameSelection->SetChangesDuringBatchingFlag();
|
||||
|
@ -64,9 +64,6 @@ namespace dom {
|
||||
class Selection final : public nsSupportsWeakReference,
|
||||
public nsWrapperCache,
|
||||
public SupportsWeakPtr {
|
||||
using AllowRangeCrossShadowBoundary =
|
||||
mozilla::dom::AllowRangeCrossShadowBoundary;
|
||||
|
||||
protected:
|
||||
virtual ~Selection();
|
||||
|
||||
@ -208,10 +205,6 @@ class Selection final : public nsSupportsWeakReference,
|
||||
nsRange* aRange, Maybe<size_t>* aOutIndex,
|
||||
DispatchSelectstartEvent aDispatchSelectstartEvent);
|
||||
|
||||
already_AddRefed<StaticRange> GetComposedRange(
|
||||
const AbstractRange* aRange,
|
||||
const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots) const;
|
||||
|
||||
public:
|
||||
nsresult RemoveCollapsedRanges();
|
||||
void Clear(nsPresContext* aPresContext);
|
||||
@ -253,8 +246,6 @@ class Selection final : public nsSupportsWeakReference,
|
||||
// anchor and which end is focus.
|
||||
const nsRange* GetAnchorFocusRange() const { return mAnchorFocusRange; }
|
||||
|
||||
void GetDirection(nsAString& aDirection) const;
|
||||
|
||||
nsDirection GetDirection() const { return mDirection; }
|
||||
|
||||
void SetDirection(nsDirection aDir) { mDirection = aDir; }
|
||||
@ -330,30 +321,6 @@ 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;
|
||||
@ -363,12 +330,8 @@ class Selection final : public nsSupportsWeakReference,
|
||||
return focus.IsSet() ? focus.GetChildAtOffset() : nullptr;
|
||||
}
|
||||
|
||||
const RangeBoundary& AnchorRef(
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No) const;
|
||||
const RangeBoundary& FocusRef(
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No) const;
|
||||
const RangeBoundary& AnchorRef() const;
|
||||
const RangeBoundary& FocusRef() const;
|
||||
|
||||
/*
|
||||
* IsCollapsed -- is the whole selection just one point, or unset?
|
||||
@ -422,10 +385,6 @@ class Selection final : public nsSupportsWeakReference,
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT void RemoveAllRanges(mozilla::ErrorResult& aRv);
|
||||
|
||||
void GetComposedRanges(
|
||||
const Sequence<OwningNonNull<ShadowRoot>>& aShadowRoots,
|
||||
nsTArray<RefPtr<StaticRange>>& aComposedRanges);
|
||||
|
||||
/**
|
||||
* Whether Stringify should flush layout or not.
|
||||
*/
|
||||
@ -851,12 +810,6 @@ class Selection final : public nsSupportsWeakReference,
|
||||
PostContentIterator& aPostOrderIter, nsIContent* aContent,
|
||||
bool aSelected) const;
|
||||
|
||||
/**
|
||||
* https://dom.spec.whatwg.org/#concept-shadow-including-descendant
|
||||
*/
|
||||
void SelectFramesOfShadowIncludingDescendantsOfContent(nsIContent* aContent,
|
||||
bool aSelected) const;
|
||||
|
||||
nsresult SelectFrames(nsPresContext* aPresContext, AbstractRange& aRange,
|
||||
bool aSelect) const;
|
||||
|
||||
|
@ -100,13 +100,6 @@ bool StaticRange::IsValid() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mAreStartAndEndInSameTree ==
|
||||
(RangeUtils::ComputeRootNode(mStart.Container()) ==
|
||||
RangeUtils::ComputeRootNode(mEnd.Container())));
|
||||
if (!mAreStartAndEndInSameTree) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Maybe<int32_t> pointOrder = nsContentUtils::ComparePoints(mStart, mEnd);
|
||||
return pointOrder.isSome() && *pointOrder <= 0;
|
||||
}
|
||||
@ -126,9 +119,6 @@ void StaticRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
|
||||
if (checkCommonAncestor) {
|
||||
UpdateCommonAncestorIfNecessary();
|
||||
}
|
||||
|
||||
mAreStartAndEndInSameTree = RangeUtils::ComputeRootNode(mStart.Container()) ==
|
||||
RangeUtils::ComputeRootNode(mEnd.Container());
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
@ -8,7 +8,6 @@
|
||||
#define mozilla_dom_StaticRange_h
|
||||
|
||||
#include "mozilla/RangeBoundary.h"
|
||||
#include "mozilla/RangeUtils.h"
|
||||
#include "mozilla/dom/AbstractRange.h"
|
||||
#include "mozilla/dom/StaticRangeBinding.h"
|
||||
#include "nsTArray.h"
|
||||
@ -71,21 +70,6 @@ class StaticRange final : public AbstractRange {
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
void NotifyNodeBecomesShadowHost(nsINode* aNode) {
|
||||
if (aNode == mStart.Container()) {
|
||||
mStart.NotifyParentBecomesShadowHost();
|
||||
}
|
||||
|
||||
if (aNode == mEnd.Container()) {
|
||||
mEnd.NotifyParentBecomesShadowHost();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Whether the start and end points are in the same tree.
|
||||
// They could be in different trees, i.e, cross shadow boundaries.
|
||||
bool mAreStartAndEndInSameTree = false;
|
||||
|
||||
protected:
|
||||
explicit StaticRange(nsINode* aNode)
|
||||
: AbstractRange(aNode, /* aIsDynamicRange = */ false) {}
|
||||
|
@ -287,17 +287,11 @@ 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() &&
|
||||
!isNodeInShadowTree) {
|
||||
->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
|
||||
return nullptr;
|
||||
}
|
||||
aNode = StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
|
||||
? aNode->GetParentOrShadowHostNode()
|
||||
: aNode->GetParentNode();
|
||||
aNode = aNode->GetParentNode();
|
||||
}
|
||||
return aNode;
|
||||
}
|
||||
@ -321,12 +315,12 @@ class IsItemInRangeComparator {
|
||||
|
||||
int operator()(const AbstractRange* const aRange) const {
|
||||
int32_t cmp = nsContentUtils::ComparePoints_Deprecated(
|
||||
&mNode, mEndOffset, aRange->GetMayCrossShadowBoundaryStartContainer(),
|
||||
aRange->MayCrossShadowBoundaryStartOffset(), nullptr, mCache);
|
||||
&mNode, mEndOffset, aRange->GetStartContainer(), aRange->StartOffset(),
|
||||
nullptr, mCache);
|
||||
if (cmp == 1) {
|
||||
cmp = nsContentUtils::ComparePoints_Deprecated(
|
||||
&mNode, mStartOffset, aRange->GetMayCrossShadowBoundaryEndContainer(),
|
||||
aRange->MayCrossShadowBoundaryEndOffset(), nullptr, mCache);
|
||||
&mNode, mStartOffset, aRange->GetEndContainer(), aRange->EndOffset(),
|
||||
nullptr, mCache);
|
||||
if (cmp == -1) {
|
||||
return 0;
|
||||
}
|
||||
@ -392,18 +386,6 @@ 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
|
||||
@ -570,8 +552,7 @@ static nsIContent* GetRootForContentSubtree(nsIContent* aContent) {
|
||||
return nsIContent::FromNode(aContent->SubtreeRoot());
|
||||
}
|
||||
|
||||
nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell,
|
||||
bool aAllowCrossShadowBoundary) {
|
||||
nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) {
|
||||
NS_ENSURE_TRUE(aPresShell, nullptr);
|
||||
|
||||
if (IsDocument()) return AsDocument()->GetRootElement();
|
||||
@ -615,7 +596,7 @@ nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell,
|
||||
}
|
||||
|
||||
RefPtr<nsFrameSelection> fs = aPresShell->FrameSelection();
|
||||
nsCOMPtr<nsIContent> content = fs->GetLimiter();
|
||||
nsIContent* content = fs->GetLimiter();
|
||||
if (!content) {
|
||||
content = fs->GetAncestorLimiter();
|
||||
if (!content) {
|
||||
@ -635,10 +616,6 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3849,33 +3826,6 @@ void nsINode::FireNodeRemovedForChildren() {
|
||||
}
|
||||
}
|
||||
|
||||
ShadowRoot* nsINode::GetShadowRoot() const {
|
||||
return IsContent() ? AsContent()->GetShadowRoot() : nullptr;
|
||||
}
|
||||
|
||||
ShadowRoot* nsINode::GetShadowRootForSelection() const {
|
||||
if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShadowRoot* shadowRoot = GetShadowRoot();
|
||||
if (!shadowRoot) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ie. <details> and <video>
|
||||
if (shadowRoot->IsUAWidget()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ie. <use> element
|
||||
if (IsElement() && !AsElement()->CanAttachShadowDOM()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return shadowRoot;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsNodeWeakReference, nsIWeakReference)
|
||||
|
||||
nsNodeWeakReference::nsNodeWeakReference(nsINode* aNode)
|
||||
|
@ -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, bool aAllowCrossShadowBoundary = false);
|
||||
mozilla::PresShell* aPresShell);
|
||||
|
||||
nsINodeList* ChildNodes();
|
||||
|
||||
@ -2074,14 +2074,6 @@ class nsINode : public mozilla::dom::EventTarget {
|
||||
ClearBoolFlag(ElementCreatedFromPrototypeAndHasUnmodifiedL10n);
|
||||
}
|
||||
|
||||
mozilla::dom::ShadowRoot* GetShadowRoot() const;
|
||||
|
||||
// Return the shadow root of the node if it is a shadow host and
|
||||
// it meets the requirements for being a shadow host of a selection.
|
||||
// For example, <details>, <video> and <use> elements are not valid
|
||||
// shadow host for selection.
|
||||
mozilla::dom::ShadowRoot* GetShadowRootForSelection() const;
|
||||
|
||||
protected:
|
||||
void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
|
||||
void SetIsInDocument() { SetBoolFlag(IsInDocument); }
|
||||
|
@ -38,11 +38,6 @@ interface nsIScriptableContentIterator : nsISupports
|
||||
void initWithRange(in nsIScriptableContentIterator_IteratorType aType,
|
||||
in Range aRange);
|
||||
|
||||
// See ContentSubtreeIterator::InitWithAllowCrossShadowBoundary(nsRange*)
|
||||
void initWithRangeAllowCrossShadowBoundary(
|
||||
in nsIScriptableContentIterator_IteratorType aType,
|
||||
in Range aRange);
|
||||
|
||||
// See ContentIteratorBase::Init(nsINode*, uint32_t, nsINode*, uint32_t)
|
||||
void initWithPositions(in nsIScriptableContentIterator_IteratorType aType,
|
||||
in Node aStartContainer, in unsigned long aStartOffset,
|
||||
|
@ -105,30 +105,16 @@ template nsresult nsRange::SetStartAndEnd(
|
||||
|
||||
template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary,
|
||||
const RangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
nsINode* aRootNode, bool aNotInsertedYet);
|
||||
template void nsRange::DoSetRange(const RangeBoundary& aStartBoundary,
|
||||
const RawRangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
nsINode* aRootNode, bool aNotInsertedYet);
|
||||
template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
|
||||
const RangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
nsINode* aRootNode, bool aNotInsertedYet);
|
||||
template void nsRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
|
||||
const RawRangeBoundary& aEndBoundary,
|
||||
nsINode* aRootNode, bool aNotInsertedYet,
|
||||
CollapsePolicy aCollapsePolicy);
|
||||
|
||||
template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
|
||||
template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary);
|
||||
template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary);
|
||||
template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
const RawRangeBoundary& aStartBoundary,
|
||||
const RawRangeBoundary& aEndBoundary);
|
||||
nsINode* aRootNode, bool aNotInsertedYet);
|
||||
|
||||
JSObject* nsRange::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
@ -185,7 +171,7 @@ nsRange::nsRange(nsINode* aNode)
|
||||
mNextEndRef(nullptr) {
|
||||
// printf("Size of nsRange: %zu\n", sizeof(nsRange));
|
||||
|
||||
static_assert(sizeof(nsRange) <= 248,
|
||||
static_assert(sizeof(nsRange) <= 240,
|
||||
"nsRange size shouldn't be increased as far as possible");
|
||||
}
|
||||
|
||||
@ -215,101 +201,6 @@ already_AddRefed<nsRange> nsRange::Create(
|
||||
return range.forget();
|
||||
}
|
||||
|
||||
/*
|
||||
* When a new boundary is given to a nsRange, compare its position with other
|
||||
* existing boundaries to see if we need to collapse the end points.
|
||||
*
|
||||
* 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,
|
||||
* false otherwise
|
||||
* aAllowCrossShadowBoundary: Indicates whether the boundaries allowed to cross
|
||||
* shadow boundary or not
|
||||
*/
|
||||
static CollapsePolicy ShouldCollapseBoundary(
|
||||
const nsRange* aRange, const nsINode* aNewRoot,
|
||||
const RawRangeBoundary& aNewBoundary, const bool aIsSetStart,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
if (!aRange->IsPositioned()) {
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const RangeBoundary& otherSideExistingBoundary =
|
||||
aIsSetStart ? aRange->EndRef() : aRange->StartRef();
|
||||
|
||||
// Both bondaries are in the same root, now check for their position
|
||||
const Maybe<int32_t> order =
|
||||
aIsSetStart ? nsContentUtils::ComparePoints(aNewBoundary,
|
||||
otherSideExistingBoundary)
|
||||
: nsContentUtils::ComparePoints(otherSideExistingBoundary,
|
||||
aNewBoundary);
|
||||
|
||||
if (order) {
|
||||
if (*order != 1) {
|
||||
// aNewBoundary is at a valid position.
|
||||
//
|
||||
// If aIsSetStart is true, this means
|
||||
// aNewBoundary <= otherSideExistingBoundary which is
|
||||
// good because aNewBoundary intends to be the start.
|
||||
//
|
||||
// If aIsSetStart is false, this means
|
||||
// otherSideExistingBoundary <= aNewBoundary which is good because
|
||||
// aNewBoundary intends to be the end.
|
||||
//
|
||||
// So no collapse for above cases.
|
||||
return CollapsePolicy::No;
|
||||
}
|
||||
|
||||
if (!aRange->MayCrossShadowBoundary() ||
|
||||
aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::No) {
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
const RangeBoundary& otherSideExistingCrossShadowBoundaryBoundary =
|
||||
aIsSetStart ? aRange->MayCrossShadowBoundaryEndRef()
|
||||
: aRange->MayCrossShadowBoundaryStartRef();
|
||||
|
||||
// Please see the comment for (*order != 1) to see what "valid" means.
|
||||
//
|
||||
// We reach to this line when (*order == 1), it means aNewBoundary is
|
||||
// at an invalid position, so we need to collapse aNewBoundary with
|
||||
// otherSideExistingBoundary. However, it's possible that aNewBoundary
|
||||
// is valid with the otherSideExistingCrossShadowBoundaryBoundary.
|
||||
const Maybe<int32_t> withCrossShadowBoundaryOrder =
|
||||
aIsSetStart
|
||||
? nsContentUtils::ComparePoints(
|
||||
aNewBoundary, otherSideExistingCrossShadowBoundaryBoundary)
|
||||
: nsContentUtils::ComparePoints(
|
||||
otherSideExistingCrossShadowBoundaryBoundary, aNewBoundary);
|
||||
|
||||
// Valid to the cross boundary boundary.
|
||||
if (withCrossShadowBoundaryOrder && *withCrossShadowBoundaryOrder != 1) {
|
||||
return CollapsePolicy::DefaultRange;
|
||||
}
|
||||
|
||||
// Not valid to both existing boundaries.
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
return CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges;
|
||||
}
|
||||
/******************************************************
|
||||
* nsISupports
|
||||
******************************************************/
|
||||
@ -328,13 +219,11 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsRange, AbstractRange)
|
||||
// `Reset()` unlinks `mStart`, `mEnd` and `mRoot`.
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrossShadowBoundaryRange);
|
||||
tmp->Reset();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsRange, AbstractRange)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrossShadowBoundaryRange);
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsRange, AbstractRange)
|
||||
@ -342,7 +231,6 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
bool nsRange::MaybeInterruptLastRelease() {
|
||||
bool interrupt = AbstractRange::MaybeCacheToReuse(*this);
|
||||
ResetCrossShadowBoundaryRange();
|
||||
MOZ_ASSERT(!interrupt || IsCleared());
|
||||
return interrupt;
|
||||
}
|
||||
@ -685,15 +573,6 @@ void nsRange::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) {
|
||||
nsINode* startContainer = mStart.Container();
|
||||
nsINode* endContainer = mEnd.Container();
|
||||
|
||||
// FIXME(sefeng): Temporary Solution for ContentRemoved
|
||||
// editing/crashtests/removeformat-from-DOMNodeRemoved.html can be used to
|
||||
// verify this.
|
||||
if (mCrossShadowBoundaryRange &&
|
||||
(mCrossShadowBoundaryRange->GetStartContainer() == aChild ||
|
||||
mCrossShadowBoundaryRange->GetEndContainer() == aChild)) {
|
||||
ResetCrossShadowBoundaryRange();
|
||||
}
|
||||
|
||||
RawRangeBoundary newStart;
|
||||
RawRangeBoundary newEnd;
|
||||
Maybe<bool> gravitateStart;
|
||||
@ -1002,12 +881,10 @@ 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 */) {
|
||||
mIsPositioned = aStartBoundary.IsSetAndValid() &&
|
||||
aEndBoundary.IsSetAndValid() && aRootNode;
|
||||
MOZ_ASSERT_IF(!mIsPositioned, !aStartBoundary.IsSet());
|
||||
@ -1036,11 +913,6 @@ void nsRange::DoSetRange(
|
||||
mStart.CopyFrom(aStartBoundary, RangeBoundaryIsMutationObserved::Yes);
|
||||
mEnd.CopyFrom(aEndBoundary, RangeBoundaryIsMutationObserved::Yes);
|
||||
|
||||
if (aCollapsePolicy ==
|
||||
CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges) {
|
||||
ResetCrossShadowBoundaryRange();
|
||||
}
|
||||
|
||||
if (checkCommonAncestor) {
|
||||
UpdateCommonAncestorIfNecessary();
|
||||
}
|
||||
@ -1093,21 +965,17 @@ bool nsRange::CanAccess(const nsINode& aNode) const {
|
||||
return nsContentUtils::CanCallerAccess(&aNode);
|
||||
}
|
||||
|
||||
void nsRange::SetStart(
|
||||
nsINode& aNode, uint32_t aOffset, ErrorResult& aRv,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
void nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) {
|
||||
if (!CanAccess(aNode)) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
AutoInvalidateSelection atEndOfBlock(this);
|
||||
SetStart(RawRangeBoundary(&aNode, aOffset), aRv, aAllowCrossShadowBoundary);
|
||||
SetStart(RawRangeBoundary(&aNode, aOffset), aRv);
|
||||
}
|
||||
|
||||
void nsRange::SetStart(
|
||||
const RawRangeBoundary& aPoint, ErrorResult& aRv,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
void nsRange::SetStart(const RawRangeBoundary& aPoint, ErrorResult& aRv) {
|
||||
nsINode* newRoot = RangeUtils::ComputeRootNode(aPoint.Container());
|
||||
if (!newRoot) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
|
||||
@ -1119,46 +987,28 @@ void nsRange::SetStart(
|
||||
return;
|
||||
}
|
||||
|
||||
CollapsePolicy policy =
|
||||
ShouldCollapseBoundary(this, newRoot, aPoint, true /* aIsSetStart= */,
|
||||
aAllowCrossShadowBoundary);
|
||||
// Collapse if not positioned yet, if positioned in another doc or
|
||||
// if the new start is after end.
|
||||
const bool collapse = [&]() {
|
||||
if (!mIsPositioned || (newRoot != mRoot)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (policy) {
|
||||
case CollapsePolicy::No:
|
||||
// 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
|
||||
if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
|
||||
if (MayCrossShadowBoundaryEndRef() != mEnd) {
|
||||
CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
aPoint, MayCrossShadowBoundaryEndRef());
|
||||
} else {
|
||||
// The normal range is good enough for this case, just use that.
|
||||
ResetCrossShadowBoundaryRange();
|
||||
}
|
||||
}
|
||||
DoSetRange(aPoint, mEnd, mRoot, false, policy);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges:
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRange:
|
||||
MOZ_ASSERT(aAllowCrossShadowBoundary ==
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
aPoint, MayCrossShadowBoundaryEndRef());
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
const Maybe<int32_t> order = nsContentUtils::ComparePoints(aPoint, mEnd);
|
||||
if (order) {
|
||||
return *order == 1;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (collapse) {
|
||||
DoSetRange(aPoint, aPoint, newRoot);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void nsRange::SetStartAllowCrossShadowBoundary(nsINode& aNode, uint32_t aOffset,
|
||||
ErrorResult& aErr) {
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetStart(aNode, aOffset, aErr, AllowRangeCrossShadowBoundary::Yes);
|
||||
DoSetRange(aPoint, mEnd, mRoot);
|
||||
}
|
||||
|
||||
void nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr) {
|
||||
@ -1167,9 +1017,7 @@ void nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr) {
|
||||
SetStartBefore(aNode, aErr);
|
||||
}
|
||||
|
||||
void nsRange::SetStartBefore(
|
||||
nsINode& aNode, ErrorResult& aRv,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
void nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv) {
|
||||
if (!CanAccess(aNode)) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
@ -1179,8 +1027,7 @@ void nsRange::SetStartBefore(
|
||||
// If the node is being removed from its parent, GetRawRangeBoundaryBefore()
|
||||
// returns unset instance. Then, SetStart() will throw
|
||||
// NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
|
||||
SetStart(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv,
|
||||
aAllowCrossShadowBoundary);
|
||||
SetStart(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv);
|
||||
}
|
||||
|
||||
void nsRange::SetStartAfterJS(nsINode& aNode, ErrorResult& aErr) {
|
||||
@ -1208,18 +1055,16 @@ void nsRange::SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr) {
|
||||
SetEnd(aNode, aOffset, aErr);
|
||||
}
|
||||
|
||||
void nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
void nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) {
|
||||
if (!CanAccess(aNode)) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
}
|
||||
AutoInvalidateSelection atEndOfBlock(this);
|
||||
SetEnd(RawRangeBoundary(&aNode, aOffset), aRv, aAllowCrossShadowBoundary);
|
||||
SetEnd(RawRangeBoundary(&aNode, aOffset), aRv);
|
||||
}
|
||||
|
||||
void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv) {
|
||||
nsINode* newRoot = RangeUtils::ComputeRootNode(aPoint.Container());
|
||||
if (!newRoot) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
|
||||
@ -1231,47 +1076,28 @@ void nsRange::SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aRv,
|
||||
return;
|
||||
}
|
||||
|
||||
CollapsePolicy policy =
|
||||
ShouldCollapseBoundary(this, newRoot, aPoint, false /* aIsStartStart */,
|
||||
aAllowCrossShadowBoundary);
|
||||
// Collapse if not positioned yet, if positioned in another doc or
|
||||
// if the new end is before start.
|
||||
const bool collapse = [&]() {
|
||||
if (!mIsPositioned || (newRoot != mRoot)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (policy) {
|
||||
case CollapsePolicy::No:
|
||||
// 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
|
||||
if (aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
|
||||
if (MayCrossShadowBoundaryStartRef() != mStart) {
|
||||
CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
MayCrossShadowBoundaryStartRef(), aPoint);
|
||||
} else {
|
||||
// The normal range is good enough for this case, just use that.
|
||||
ResetCrossShadowBoundaryRange();
|
||||
}
|
||||
}
|
||||
DoSetRange(mStart, aPoint, mRoot, false, policy);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRangeAndCrossShadowBoundaryRanges:
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
break;
|
||||
case CollapsePolicy::DefaultRange:
|
||||
MOZ_ASSERT(aAllowCrossShadowBoundary ==
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
MayCrossShadowBoundaryStartRef(), aPoint);
|
||||
DoSetRange(aPoint, aPoint, newRoot, false, policy);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
const Maybe<int32_t> order = nsContentUtils::ComparePoints(mStart, aPoint);
|
||||
if (order) {
|
||||
return *order == 1;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (collapse) {
|
||||
DoSetRange(aPoint, aPoint, newRoot);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void nsRange::SetEndAllowCrossShadowBoundary(nsINode& aNode, uint32_t aOffset,
|
||||
ErrorResult& aErr) {
|
||||
AutoCalledByJSRestore calledByJSRestorer(*this);
|
||||
mCalledByJS = true;
|
||||
SetEnd(aNode, aOffset, aErr,
|
||||
AllowRangeCrossShadowBoundary::Yes /* aAllowCrossShadowBoundary */);
|
||||
DoSetRange(mStart, aPoint, mRoot);
|
||||
}
|
||||
|
||||
void nsRange::SelectNodesInContainer(nsINode* aContainer,
|
||||
@ -1301,9 +1127,7 @@ void nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr) {
|
||||
SetEndBefore(aNode, aErr);
|
||||
}
|
||||
|
||||
void nsRange::SetEndBefore(
|
||||
nsINode& aNode, ErrorResult& aRv,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
|
||||
void nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv) {
|
||||
if (!CanAccess(aNode)) {
|
||||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
@ -1313,8 +1137,7 @@ void nsRange::SetEndBefore(
|
||||
// If the node is being removed from its parent, GetRawRangeBoundaryBefore()
|
||||
// returns unset instance. Then, SetEnd() will throw
|
||||
// NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
|
||||
SetEnd(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv,
|
||||
aAllowCrossShadowBoundary);
|
||||
SetEnd(RangeUtils::GetRawRangeBoundaryBefore(&aNode), aRv);
|
||||
}
|
||||
|
||||
void nsRange::SetEndAfterJS(nsINode& aNode, ErrorResult& aErr) {
|
||||
@ -2378,11 +2201,6 @@ already_AddRefed<DocumentFragment> nsRange::CloneContents(ErrorResult& aRv) {
|
||||
already_AddRefed<nsRange> nsRange::CloneRange() const {
|
||||
RefPtr<nsRange> range = nsRange::Create(mOwner);
|
||||
range->DoSetRange(mStart, mEnd, mRoot);
|
||||
if (mCrossShadowBoundaryRange) {
|
||||
range->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
mCrossShadowBoundaryRange->StartRef(),
|
||||
mCrossShadowBoundaryRange->EndRef());
|
||||
}
|
||||
return range.forget();
|
||||
}
|
||||
|
||||
@ -3188,7 +3006,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, AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetStartBefore(*node, err);
|
||||
if (err.Failed()) {
|
||||
return;
|
||||
}
|
||||
@ -3202,8 +3020,7 @@ void nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges) {
|
||||
|
||||
// Truncate the current range before the first non-selectable node.
|
||||
IgnoredErrorResult err;
|
||||
range->SetEndBefore(*firstNonSelectableContent, err,
|
||||
AllowRangeCrossShadowBoundary::Yes);
|
||||
range->SetEndBefore(*firstNonSelectableContent, err);
|
||||
|
||||
// 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
|
||||
@ -3447,52 +3264,3 @@ void nsRange::GetInnerTextNoFlush(DOMString& aValue, ErrorResult& aError,
|
||||
// Do not flush trailing line breaks! Required breaks at the end of the text
|
||||
// are suppressed.
|
||||
}
|
||||
|
||||
template <typename SPT, typename SRT, typename EPT, typename ERT>
|
||||
void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
|
||||
const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
|
||||
if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aStartBoundary.IsSetAndValid() && aEndBoundary.IsSetAndValid());
|
||||
|
||||
nsINode* startNode = aStartBoundary.Container();
|
||||
nsINode* endNode = aEndBoundary.Container();
|
||||
|
||||
if (!startNode && !endNode) {
|
||||
ResetCrossShadowBoundaryRange();
|
||||
return;
|
||||
}
|
||||
|
||||
auto CanBecomeCrossShadowBoundaryPoint = [](nsINode* aContainer) -> bool {
|
||||
if (!aContainer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unlike normal ranges, shadow cross ranges don't work
|
||||
// when the nodes aren't in document.
|
||||
if (!aContainer->IsInComposedDoc()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// AbstractRange::GetClosestCommonInclusiveAncestor only supports
|
||||
// Document and Content nodes.
|
||||
return aContainer->IsDocument() || aContainer->IsContent();
|
||||
};
|
||||
|
||||
if (!CanBecomeCrossShadowBoundaryPoint(startNode) ||
|
||||
!CanBecomeCrossShadowBoundaryPoint(endNode)) {
|
||||
ResetCrossShadowBoundaryRange();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mCrossShadowBoundaryRange) {
|
||||
mCrossShadowBoundaryRange =
|
||||
StaticRange::Create(aStartBoundary, aEndBoundary, IgnoreErrors());
|
||||
return;
|
||||
}
|
||||
|
||||
mCrossShadowBoundaryRange->SetStartAndEnd(aStartBoundary, aEndBoundary);
|
||||
}
|
||||
|
@ -13,7 +13,6 @@
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "mozilla/dom/AbstractRange.h"
|
||||
#include "mozilla/dom/StaticRange.h"
|
||||
#include "prmon.h"
|
||||
#include "nsStubMutationObserver.h"
|
||||
#include "nsWrapperCache.h"
|
||||
@ -32,13 +31,6 @@ class DOMRect;
|
||||
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
|
||||
};
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
@ -51,8 +43,6 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
||||
using DOMRectList = mozilla::dom::DOMRectList;
|
||||
using RangeBoundary = mozilla::RangeBoundary;
|
||||
using RawRangeBoundary = mozilla::RawRangeBoundary;
|
||||
using AllowRangeCrossShadowBoundary =
|
||||
mozilla::dom::AllowRangeCrossShadowBoundary;
|
||||
|
||||
virtual ~nsRange();
|
||||
explicit nsRange(nsINode* aNode);
|
||||
@ -121,20 +111,14 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
||||
* When you set both start and end of a range, you should use
|
||||
* SetStartAndEnd() instead.
|
||||
*/
|
||||
nsresult SetStart(nsINode* aContainer, uint32_t aOffset,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No) {
|
||||
nsresult SetStart(nsINode* aContainer, uint32_t aOffset) {
|
||||
ErrorResult error;
|
||||
SetStart(RawRangeBoundary(aContainer, aOffset), error,
|
||||
aAllowCrossShadowBoundary);
|
||||
SetStart(RawRangeBoundary(aContainer, aOffset), error);
|
||||
return error.StealNSResult();
|
||||
}
|
||||
nsresult SetEnd(nsINode* aContainer, uint32_t aOffset,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No) {
|
||||
nsresult SetEnd(nsINode* aContainer, uint32_t aOffset) {
|
||||
ErrorResult error;
|
||||
SetEnd(RawRangeBoundary(aContainer, aOffset), error,
|
||||
aAllowCrossShadowBoundary);
|
||||
SetEnd(RawRangeBoundary(aContainer, aOffset), error);
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
@ -240,11 +224,6 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
||||
void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr);
|
||||
void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr);
|
||||
|
||||
void SetStartAllowCrossShadowBoundary(nsINode& aNode, uint32_t aOffset,
|
||||
ErrorResult& aErr);
|
||||
void SetEndAllowCrossShadowBoundary(nsINode& aNode, uint32_t aOffset,
|
||||
ErrorResult& aErr);
|
||||
|
||||
void SurroundContents(nsINode& aNode, ErrorResult& aErr);
|
||||
already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
|
||||
bool aFlushLayout = true);
|
||||
@ -256,26 +235,14 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
||||
// Following methods should be used for internal use instead of *JS().
|
||||
void SelectNode(nsINode& aNode, ErrorResult& aErr);
|
||||
void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
|
||||
void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No);
|
||||
void SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aErr,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No);
|
||||
void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
|
||||
void SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aErr);
|
||||
void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
|
||||
void SetEndBefore(nsINode& aNode, ErrorResult& aErr,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No);
|
||||
void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No);
|
||||
void SetStart(const RawRangeBoundary& aPoint, ErrorResult& aErr,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No);
|
||||
void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
|
||||
void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
|
||||
void SetStart(const RawRangeBoundary& aPoint, ErrorResult& aErr);
|
||||
void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
|
||||
void SetStartBefore(nsINode& aNode, ErrorResult& aErr,
|
||||
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
|
||||
AllowRangeCrossShadowBoundary::No);
|
||||
void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
|
||||
void Collapse(bool aToStart);
|
||||
|
||||
static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
|
||||
@ -384,82 +351,6 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
||||
*/
|
||||
nsINode* GetRegisteredClosestCommonInclusiveAncestor();
|
||||
|
||||
template <typename SPT, typename SRT, typename EPT, typename ERT>
|
||||
void CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
|
||||
const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
|
||||
const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary);
|
||||
|
||||
void ResetCrossShadowBoundaryRange() { mCrossShadowBoundaryRange = nullptr; }
|
||||
|
||||
#ifdef DEBUG
|
||||
bool CrossShadowBoundaryRangeCollapsed() const {
|
||||
MOZ_ASSERT(mCrossShadowBoundaryRange);
|
||||
|
||||
return !mCrossShadowBoundaryRange->IsPositioned() ||
|
||||
(mCrossShadowBoundaryRange->GetStartContainer() ==
|
||||
mCrossShadowBoundaryRange->GetEndContainer() &&
|
||||
mCrossShadowBoundaryRange->StartOffset() ==
|
||||
mCrossShadowBoundaryRange->EndOffset());
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The methods marked with MayCrossShadowBoundary[..] additionally check for
|
||||
* the existence of mCrossShadowBoundaryRange, which indicates a range that
|
||||
* crosses a shadow DOM boundary (i.e. mStart and mEnd are in different
|
||||
* trees). If the caller can guarantee that this does not happen, there are
|
||||
* additional variants of these methods named without MayCrossShadowBoundary,
|
||||
* which provide a slightly faster implementation.
|
||||
* */
|
||||
|
||||
nsIContent* GetMayCrossShadowBoundaryChildAtStartOffset() const {
|
||||
return mCrossShadowBoundaryRange
|
||||
? mCrossShadowBoundaryRange->GetChildAtStartOffset()
|
||||
: mStart.GetChildAtOffset();
|
||||
}
|
||||
|
||||
nsIContent* GetMayCrossShadowBoundaryChildAtEndOffset() const {
|
||||
return mCrossShadowBoundaryRange
|
||||
? mCrossShadowBoundaryRange->GetChildAtEndOffset()
|
||||
: mEnd.GetChildAtOffset();
|
||||
}
|
||||
|
||||
mozilla::dom::StaticRange* GetCrossShadowBoundaryRange() const {
|
||||
return mCrossShadowBoundaryRange;
|
||||
}
|
||||
|
||||
nsINode* GetMayCrossShadowBoundaryStartContainer() const {
|
||||
return mCrossShadowBoundaryRange
|
||||
? mCrossShadowBoundaryRange->GetStartContainer()
|
||||
: mStart.Container();
|
||||
}
|
||||
|
||||
nsINode* GetMayCrossShadowBoundaryEndContainer() const {
|
||||
return mCrossShadowBoundaryRange
|
||||
? mCrossShadowBoundaryRange->GetEndContainer()
|
||||
: mEnd.Container();
|
||||
}
|
||||
|
||||
uint32_t MayCrossShadowBoundaryStartOffset() const {
|
||||
return mCrossShadowBoundaryRange ? mCrossShadowBoundaryRange->StartOffset()
|
||||
: StartOffset();
|
||||
}
|
||||
|
||||
uint32_t MayCrossShadowBoundaryEndOffset() const {
|
||||
return mCrossShadowBoundaryRange ? mCrossShadowBoundaryRange->EndOffset()
|
||||
: EndOffset();
|
||||
}
|
||||
|
||||
const RangeBoundary& MayCrossShadowBoundaryStartRef() const {
|
||||
return mCrossShadowBoundaryRange ? mCrossShadowBoundaryRange->StartRef()
|
||||
: StartRef();
|
||||
}
|
||||
|
||||
const RangeBoundary& MayCrossShadowBoundaryEndRef() const {
|
||||
return mCrossShadowBoundaryRange ? mCrossShadowBoundaryRange->EndRef()
|
||||
: EndRef();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* DoSetRange() is called when `AbstractRange::SetStartAndEndInternal()` sets
|
||||
@ -481,9 +372,7 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoSetRange(
|
||||
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);
|
||||
nsINode* aRootNode, bool aNotInsertedYet = false);
|
||||
|
||||
// Assume that this is guaranteed that this is held by the caller when
|
||||
// this is used. (Note that we cannot use AutoRestore for mCalledByJS
|
||||
@ -535,22 +424,6 @@ class nsRange final : public mozilla::dom::AbstractRange,
|
||||
|
||||
static nsTArray<RefPtr<nsRange>>* sCachedRanges;
|
||||
|
||||
// Used to keep track of the real start and end for a
|
||||
// selection where the start and the end are in different trees.
|
||||
// It's NULL when the nodes are in the same tree.
|
||||
//
|
||||
// mCrossShadowBoundaryRange doesn't deal with DOM mutations, because
|
||||
// it's still an open question about how it should be handled.
|
||||
// Spec: https://github.com/w3c/selection-api/issues/168.
|
||||
// As a result, it'll be set to NULL if that happens.
|
||||
//
|
||||
// Theoretically, mCrossShadowBoundaryRange isn't really needed because
|
||||
// we should be able to always store the real start and end, and
|
||||
// just return one point when a collapse is needed.
|
||||
// Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1886028 is going
|
||||
// to be used to improve mCrossShadowBoundaryRange.
|
||||
RefPtr<mozilla::dom::StaticRange> mCrossShadowBoundaryRange;
|
||||
|
||||
friend class mozilla::dom::AbstractRange;
|
||||
};
|
||||
namespace mozilla::dom {
|
||||
|
@ -1170,8 +1170,6 @@ skip-if = [
|
||||
|
||||
["test_content_iterator_subtree.html"]
|
||||
|
||||
["test_content_iterator_subtree_shadow_tree.html"]
|
||||
|
||||
["test_copyimage.html"]
|
||||
skip-if = [
|
||||
"os == 'android'",
|
||||
|
@ -1,286 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for content subtree iterator with ShadowDOM involved</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<script>
|
||||
var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
function finish() {
|
||||
// The SimpleTest may require usual elements in the template, but they shouldn't be during test.
|
||||
// So, let's create them at end of the test.
|
||||
document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function createContentIterator() {
|
||||
return Cc["@mozilla.org/scriptable-content-iterator;1"]
|
||||
.createInstance(Ci.nsIScriptableContentIterator);
|
||||
}
|
||||
|
||||
function getNodeDescription(aNode) {
|
||||
if (aNode === undefined) {
|
||||
return "undefine";
|
||||
}
|
||||
if (aNode === null) {
|
||||
return "null";
|
||||
}
|
||||
function getElementDescription(aElement) {
|
||||
if (aElement.host) {
|
||||
aElement = aElement.host;
|
||||
}
|
||||
if (aElement.tagName === "BR") {
|
||||
if (aElement.previousSibling) {
|
||||
return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
|
||||
}
|
||||
return `<br> element in ${getElementDescription(aElement.parentElement)}`;
|
||||
}
|
||||
let hasHint = aElement == document.body;
|
||||
let tag = `<${aElement.tagName.toLowerCase()}`;
|
||||
if (aElement.getAttribute("id")) {
|
||||
tag += ` id="${aElement.getAttribute("id")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("class")) {
|
||||
tag += ` class="${aElement.getAttribute("class")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("type")) {
|
||||
tag += ` type="${aElement.getAttribute("type")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("name")) {
|
||||
tag += ` name="${aElement.getAttribute("name")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("value")) {
|
||||
tag += ` value="${aElement.getAttribute("value")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("style")) {
|
||||
tag += ` style="${aElement.getAttribute("style")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (hasHint) {
|
||||
return tag + ">";
|
||||
}
|
||||
|
||||
return `${tag}> in ${getElementDescription(aElement.parentElement || aElement.parentNode)}`;
|
||||
}
|
||||
switch (aNode.nodeType) {
|
||||
case aNode.TEXT_NODE:
|
||||
return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.COMMENT_NODE:
|
||||
return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.ELEMENT_NODE:
|
||||
return getElementDescription(SpecialPowers.unwrap(aNode));
|
||||
default:
|
||||
return "unknown node";
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function () {
|
||||
let iter = createContentIterator();
|
||||
|
||||
/**
|
||||
* Basic tests with complicated tree.
|
||||
*/
|
||||
function check(aIter, aExpectedResult, aDescription) {
|
||||
if (aExpectedResult.length) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
|
||||
|
||||
for (let expected of aExpectedResult) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), expected,
|
||||
`${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
|
||||
aIter.next();
|
||||
}
|
||||
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
|
||||
} else {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
|
||||
}
|
||||
}
|
||||
|
||||
// Structure
|
||||
// <div>OuterText1</div>
|
||||
// <div #host1>
|
||||
// #ShadowRoot
|
||||
// InnerText1
|
||||
// <div>OuterText2</div>
|
||||
// <div #host2>
|
||||
// #ShadowRoot
|
||||
// <div>InnerText2</div>
|
||||
// <div>InnerText3</div>
|
||||
// <div #host3>
|
||||
// #ShadowRoot
|
||||
// <div #host4>
|
||||
// #ShadowRoot
|
||||
// InnerText4
|
||||
// OuterText3
|
||||
|
||||
document.body.innerHTML = `<div id="outerText1">OuterText1</div>` +
|
||||
`<div id="host1"></div>` +
|
||||
`<div id="outerText2">OuterText2</div>` +
|
||||
`<div id="host2"></div>` +
|
||||
`<div id="host3"></div>` +
|
||||
`OuterText3`;
|
||||
const outerText1 = document.getElementById("outerText1");
|
||||
const outerText2 = document.getElementById("outerText2");
|
||||
|
||||
const host1 = document.getElementById("host1");
|
||||
const root1 = host1.attachShadow({mode: "open"});
|
||||
root1.innerHTML = "InnerText1";
|
||||
|
||||
const host2 = document.getElementById("host2");
|
||||
const root2 = host2.attachShadow({mode: "open"});
|
||||
root2.innerHTML = "<div>InnerText2</div><div>InnerText3</div>";
|
||||
|
||||
const host3 = document.getElementById("host3");
|
||||
const root3 = host3.attachShadow({mode: "open"});
|
||||
root3.innerHTML = `<div id="host4"></div>`;
|
||||
|
||||
const host4 = root3.getElementById("host4");
|
||||
const root4 = host4.attachShadow({mode: "open"});
|
||||
root4.innerHTML = "InnerText4";
|
||||
|
||||
/**
|
||||
* Selects the <body> with a range.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNode(document.body);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [document.body], "Initialized with range selecting the <body>");
|
||||
|
||||
/**
|
||||
* Selects all children in the <body> with a range.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(document.body);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [outerText1, host1,
|
||||
outerText2, host2,
|
||||
host3, // host4 is a child of host3
|
||||
document.body.lastChild],
|
||||
"Initialized with range selecting all children in the <body>");
|
||||
|
||||
/**
|
||||
* range around elements.
|
||||
*/
|
||||
range = document.createRange();
|
||||
SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1.firstChild, 0);
|
||||
SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root1.firstChild, root1.firstChild.length);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
// outerText1.firstChild is a node without children, so the
|
||||
// next candidate is root1.firstChild, given root1.firstChild
|
||||
// is also the end container which isn't fully contained
|
||||
// by this range, so the iterator returns nothing.
|
||||
check(iter, [], "Initialized with range selecting 'OuterText1 and InnerText1'");
|
||||
|
||||
// From light DOM to Shadow DOM #1
|
||||
range = document.createRange();
|
||||
SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1, 0);
|
||||
SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root1.firstChild, root1.firstChild.length);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
// [start] outerText1 is a container and it has children, so the first node
|
||||
// is the topmost descendant, which is outerText.firstChild.
|
||||
// [end] The end point of this iteration is also outerText1.firstChild because
|
||||
// it is also the topmost element in the previous node of root1.firstChild.
|
||||
// Iteration #1: outerText1.firstChild as it is the start node
|
||||
check(iter, [outerText1.firstChild], "Initialized with range selecting 'OuterText1 and InnerText1'");
|
||||
|
||||
// From light DOM to Shadow DOM #2
|
||||
SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1, 0);
|
||||
SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2, root2.childNodes.length);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
// [start] outerText1 is a container and it has children, so the first node
|
||||
// is the topmost descendant, which is outerText.firstChild.
|
||||
// [end] root2 is the container and it has children, so the end node is
|
||||
// the last node of root2, which is root2.lastChild
|
||||
// Iteration #1: outerText1.firstChild, as it's the start node
|
||||
// Iteration #2: host1, as it's next available node after outerText1.firstChild
|
||||
// Iteration #3: outerText2, as it's the next sibiling of host1
|
||||
// Iteration #4: host2, as it's the next sibling of outerText2. Since it's
|
||||
// the ancestor of the end node, so we get into this tree and returns
|
||||
// root2.firstChild here.
|
||||
// Iteration #5: root2.lastChild, as it's the next sibling of root2.firstChild
|
||||
check(iter, [outerText1.firstChild, host1, outerText2, root2.firstChild, root2.lastChild],
|
||||
"Initialized with range selecting 'OuterText1, InnerText1, OuterText2 and InnerText2'");
|
||||
|
||||
// From Shadow DOM to Shadow DOM #1
|
||||
SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1.firstChild, 0);
|
||||
SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2.lastChild, root2.lastChild.length);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
// [start] outerText2 is the start because root1.firstChild doesn't have children,
|
||||
// so we look for next available node which is outerText2.
|
||||
// [end] root2.lastChild is the end container, so we look for previous
|
||||
// nodes and get root2.firstChild
|
||||
// Iteration #1: outerText2, as it's the start node
|
||||
// Iteration #2: host2, as it's the next sibling of outerText2. Since it's
|
||||
// the ancestor of the end node, so we get into this tree and returns
|
||||
// root2.firstChild here.
|
||||
check(iter, [outerText2, root2.firstChild], "Initialized with range selecting 'InnerText1, OuterText2 and InnerText2'");
|
||||
|
||||
// From Shadow DOM to Shadow DOM #2
|
||||
SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1, 0);
|
||||
SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2.lastChild, root2.lastChild.length);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
// [start] root1 is the start container and it has children, so the first node
|
||||
// is the topmost descendant, which is root1.firstChild.
|
||||
// [end] root2.lastChild is the end container, so we look for previous
|
||||
// nodes and get root2.firstChild
|
||||
// Iteration #1: root1.firstChild, as it's the start node
|
||||
// Iteration #2: outerText2, as it's the next available node
|
||||
// Iteration #3: host2, as it's the next sibling of outerText2. Since it's
|
||||
// the ancestor of the end node, so we get into this tree and returns
|
||||
// root2.firstChild here.
|
||||
check(iter, [root1.firstChild, outerText2, root2.firstChild], "Initialized with range selecting 'InnerText1, OuterText2 and InnerText2'");
|
||||
|
||||
SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1.firstChild, 1);
|
||||
SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root4.firstChild, root4.firstChild.length);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
// [start] outerText2 is the start because root1.firstChild doesn't have children,
|
||||
// so we look for next available node which is outerText2.
|
||||
// [end] host2 is the end container, so we look for previous
|
||||
// nodes root4.firstChild and eventually get host2.
|
||||
// Iteration #1: outerText2, as it's the start node
|
||||
// Iteration #2: host2, as it's the next sibling of outerText2
|
||||
check(iter, [outerText2, host2], "Initialized with range selecting 'InnerText1, OuterText2, InnerText2 and InnerText3'");
|
||||
|
||||
// From light to light
|
||||
SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1.firstChild, 0);
|
||||
SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(document.body.lastChild, document.body.lastChild.length);
|
||||
iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
// [start] host1 is the start because it's the next available node of
|
||||
// outerText1.firstChild.
|
||||
// [end] host3 is the end because the previous node of document.body.lastChild is host3.
|
||||
// Iteration #1: host1, as it's the start node
|
||||
// Iteration #2: outerText2, as it's the next sibling of host1
|
||||
// Iteration #3: host2, as it's the next sibling of outerText2
|
||||
// Iteration #4: host3, as it's the next sibling of host2
|
||||
check(iter, [host1, outerText2, host2, host3],
|
||||
"Initialized with range selecting 'OuterText1, InnerText1, OuterText2, InnerText2, InnerText3 and OuterText3'");
|
||||
|
||||
finish();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
@ -1270,14 +1270,8 @@ nsresult nsDocumentEncoder::RangeSerializer::SerializeRangeToString(
|
||||
const nsRange* aRange) {
|
||||
if (!aRange || aRange->Collapsed()) return NS_OK;
|
||||
|
||||
// Consider a case where the boundary of the selection is ShadowRoot (ie, the
|
||||
// first child of ShadowRoot is selected, so ShadowRoot is the container hence
|
||||
// the boundary), allowing GetClosestCommonInclusiveAncestor to cross the
|
||||
// boundary can return the host element as the container.
|
||||
// SerializeRangeContextStart doesn't support this case.
|
||||
mClosestCommonInclusiveAncestorOfRange =
|
||||
aRange->GetClosestCommonInclusiveAncestor(
|
||||
AllowRangeCrossShadowBoundary::No);
|
||||
aRange->GetClosestCommonInclusiveAncestor();
|
||||
|
||||
if (!mClosestCommonInclusiveAncestorOfRange) {
|
||||
return NS_OK;
|
||||
|
@ -94,12 +94,3 @@ partial interface Range {
|
||||
[ChromeOnly, Throws]
|
||||
ClientRectsAndTexts getClientRectsAndTexts();
|
||||
};
|
||||
|
||||
// ChromeOnly methods that allow setting Range boundaries to cross
|
||||
// shadow boundary.
|
||||
partial interface Range {
|
||||
[ChromeOnly, Throws]
|
||||
undefined setStartAllowCrossShadowBoundary(Node refNode, unsigned long offset);
|
||||
[ChromeOnly, Throws]
|
||||
undefined setEndAllowCrossShadowBoundary(Node refNode, unsigned long offset);
|
||||
};
|
||||
|
@ -26,7 +26,6 @@ interface Selection {
|
||||
*/
|
||||
readonly attribute unsigned long rangeCount;
|
||||
readonly attribute DOMString type;
|
||||
readonly attribute DOMString direction;
|
||||
/**
|
||||
* Returns the range at the specified index. Throws if the index is
|
||||
* out of range.
|
||||
@ -50,10 +49,6 @@ interface Selection {
|
||||
undefined removeAllRanges();
|
||||
[Throws, BinaryName="RemoveAllRanges"]
|
||||
undefined empty();
|
||||
|
||||
[Pref="dom.shadowdom.selection_across_boundary_enabled"]
|
||||
sequence<StaticRange> getComposedRanges(ShadowRoot... shadowRoots);
|
||||
|
||||
[Throws, BinaryName="collapseJS"]
|
||||
undefined collapse(Node? node, optional unsigned long offset = 0);
|
||||
[Throws, BinaryName="collapseJS"]
|
||||
|
@ -539,11 +539,7 @@ nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
|
||||
|
||||
NS_ENSURE_STATE(mPresShell);
|
||||
RefPtr<PresShell> presShell = mPresShell;
|
||||
nsIContent* anchorRoot =
|
||||
anchorContent
|
||||
->GetSelectionRootContent(
|
||||
presShell,
|
||||
StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() /* aAllowCrossShadowBoundary */);
|
||||
nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(presShell);
|
||||
NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED);
|
||||
|
||||
//
|
||||
@ -553,10 +549,7 @@ nsresult nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(
|
||||
nsCOMPtr<nsIContent> content = aFrame->GetContent();
|
||||
|
||||
if (content) {
|
||||
nsIContent* contentRoot =
|
||||
content->GetSelectionRootContent(
|
||||
presShell, StaticPrefs::
|
||||
dom_shadowdom_selection_across_boundary_enabled() /* aAllowCrossShadowBoundary */);
|
||||
nsIContent* contentRoot = content->GetSelectionRootContent(presShell);
|
||||
NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
|
||||
|
||||
if (anchorRoot == contentRoot) {
|
||||
@ -580,9 +573,8 @@ 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, StaticPrefs::
|
||||
dom_shadowdom_selection_across_boundary_enabled() /* aAllowCrossShadowBoundary */);
|
||||
nsIContent* cursorContentRoot =
|
||||
cursorContent->GetSelectionRootContent(presShell);
|
||||
NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED);
|
||||
if (cursorContentRoot == anchorRoot) {
|
||||
*aRetFrame = cursorFrame;
|
||||
@ -1501,11 +1493,11 @@ void nsFrameSelection::SetDragState(bool aState) {
|
||||
// Notify that reason is mouse up.
|
||||
SetChangeReasons(nsISelectionListener::MOUSEUP_REASON);
|
||||
|
||||
// flag is set to NotApplicable in `Selection::NotifySelectionListeners`.
|
||||
// flag is set to false in `NotifySelectionListeners`.
|
||||
// since this function call is part of click event, this would immediately
|
||||
// reset the flag, rendering it useless.
|
||||
AutoRestore<ClickSelectionType> restoreClickSelectionType(
|
||||
mClickSelectionType);
|
||||
AutoRestore<bool> restoreIsDoubleClickSelectionFlag(
|
||||
mIsDoubleClickSelection);
|
||||
// Be aware, the Selection instance may be destroyed after this call.
|
||||
NotifySelectionListeners(SelectionType::eNormal);
|
||||
}
|
||||
|
@ -200,7 +200,6 @@ class SelectionChangeEventDispatcher;
|
||||
namespace dom {
|
||||
class Highlight;
|
||||
class Selection;
|
||||
enum class ClickSelectionType { NotApplicable, Double, Triple };
|
||||
} // namespace dom
|
||||
|
||||
/**
|
||||
@ -260,17 +259,14 @@ class nsFrameSelection final {
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets the type of the selection based on whether a selection is created
|
||||
* by doubleclick, long tapping a word or tripleclick.
|
||||
* Sets flag to true if a selection is created by doubleclick or
|
||||
* long tapping a word.
|
||||
*
|
||||
* @param aClickSelectionType ClickSelectionType::Double if the selection
|
||||
* is created by doubleclick,
|
||||
* ClickSelectionType::Triple if the selection
|
||||
* is created by tripleclick.
|
||||
* @param aIsDoubleClickSelection True if the selection is created by
|
||||
* doubleclick or long tap over a word.
|
||||
*/
|
||||
void SetClickSelectionType(
|
||||
mozilla::dom::ClickSelectionType aClickSelectionType) {
|
||||
mClickSelectionType = aClickSelectionType;
|
||||
void SetIsDoubleClickSelection(bool aIsDoubleClickSelection) {
|
||||
mIsDoubleClickSelection = aIsDoubleClickSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -278,14 +274,7 @@ class nsFrameSelection final {
|
||||
* long tap over a word.
|
||||
*/
|
||||
[[nodiscard]] bool IsDoubleClickSelection() const {
|
||||
return mClickSelectionType == mozilla::dom::ClickSelectionType::Double;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the selection was created by triple click
|
||||
*/
|
||||
[[nodiscard]] bool IsTripleClickSelection() const {
|
||||
return mClickSelectionType == mozilla::dom::ClickSelectionType::Triple;
|
||||
return mIsDoubleClickSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1113,12 +1102,11 @@ class nsFrameSelection final {
|
||||
bool mDragState = false; // for drag purposes
|
||||
bool mAccessibleCaretEnabled = false;
|
||||
|
||||
// Records if a selection was created by doubleclicking or tripleclicking
|
||||
// a word. This information is needed later on to determine if a leading
|
||||
// Records if a selection was created by doubleclicking a word.
|
||||
// This information is needed later on to determine if a leading
|
||||
// or trailing whitespace needs to be removed as well to achieve
|
||||
// native behaviour on macOS.
|
||||
mozilla::dom::ClickSelectionType mClickSelectionType =
|
||||
mozilla::dom::ClickSelectionType::NotApplicable;
|
||||
bool mIsDoubleClickSelection{false};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -5120,9 +5120,7 @@ nsresult nsIFrame::PeekBackwardAndForward(nsSelectionAmount aAmountBack,
|
||||
return rv;
|
||||
}
|
||||
if (aAmountBack == eSelectWord) {
|
||||
frameSelection->SetClickSelectionType(ClickSelectionType::Double);
|
||||
} else if (aAmountBack == eSelectParagraph) {
|
||||
frameSelection->SetClickSelectionType(ClickSelectionType::Triple);
|
||||
frameSelection->SetIsDoubleClickSelection(true);
|
||||
}
|
||||
|
||||
// maintain selection
|
||||
@ -8562,12 +8560,6 @@ 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,13 +8983,6 @@ 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) {
|
||||
|
@ -12,7 +12,6 @@ support-files = [
|
||||
"file_SlowTallImage.sjs",
|
||||
"bug1174521.html",
|
||||
"!/gfx/layers/apz/test/mochitest/apz_test_utils.js",
|
||||
"selection_cross_shadow_boundary_helper.js",
|
||||
]
|
||||
|
||||
["test_bug240933.html"]
|
||||
@ -266,30 +265,6 @@ support-files = [
|
||||
|
||||
["test_selection_changes_with_middle_mouse_button.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_1_backward_click.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_1_backward_drag.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_1_forward_click.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_1_forward_drag.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_2_backward_click.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_2_backward_drag.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_2_forward_click.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_2_forward_drag.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_multi_ranges_forward_drag.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_multi_ranges_forward_click.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_multi_ranges_backward_drag.html"]
|
||||
|
||||
["test_selection_cross_shadow_boundary_multi_ranges_backward_click.html"]
|
||||
|
||||
["test_selection_doubleclick.html"]
|
||||
|
||||
["test_selection_expanding.html"]
|
||||
|
@ -1,28 +0,0 @@
|
||||
// Helper file for test_selection_cross_shadow_boundary_* related tests
|
||||
|
||||
function drag(
|
||||
fromTarget,
|
||||
fromX,
|
||||
fromY,
|
||||
toTarget,
|
||||
toX,
|
||||
toY,
|
||||
withAccelKey = false
|
||||
) {
|
||||
synthesizeMouse(fromTarget, fromX, fromY, {
|
||||
type: "mousemove",
|
||||
accelKey: withAccelKey,
|
||||
});
|
||||
synthesizeMouse(fromTarget, fromX, fromY, {
|
||||
type: "mousedown",
|
||||
accelKey: withAccelKey,
|
||||
});
|
||||
synthesizeMouse(toTarget, toX, toY, {
|
||||
type: "mousemove",
|
||||
accelKey: withAccelKey,
|
||||
});
|
||||
synthesizeMouse(toTarget, toX, toY, {
|
||||
type: "mouseup",
|
||||
accelKey: withAccelKey,
|
||||
});
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<!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() {
|
||||
document.getElementById("host").attachShadow({ mode: "open" }).innerHTML = "<span>InnerText</span>";
|
||||
|
||||
const inner = host.shadowRoot.firstChild;
|
||||
const rect = inner.getBoundingClientRect();
|
||||
|
||||
// Click the bottom right of "InnerText"
|
||||
synthesizeMouse(inner, rect.width, rect.height, { type: "mousedown"});
|
||||
synthesizeMouse(inner, rect.width, rect.height, { type: "mouseup" });
|
||||
|
||||
// Click the top left of "OuterText"
|
||||
synthesizeMouse(document.getElementById("outer"), 0, 0, { type: "mousedown" , shiftKey: true});
|
||||
synthesizeMouse(document.getElementById("outer"), 0, 0, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
// Above two clicks should select both "OuterText" and "InnerText"
|
||||
const sel = document.getSelection().getComposedRanges(host.shadowRoot)[0];
|
||||
|
||||
// backward selection
|
||||
is(sel.endContainer, inner.firstChild, "endContainer is the InnerText");
|
||||
is(sel.endOffset, 9, "endOffset ends at the last character");
|
||||
is(sel.startContainer, outer.firstChild, "startContainer is the OuterText");
|
||||
is(sel.startOffset, 0, "startOffset starts at the first character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer">OuterText</span>
|
||||
<div id="host"></div>
|
||||
</body>
|
@ -1,40 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function run() {
|
||||
document.getElementById("host").attachShadow({ mode: "open" }).innerHTML = "<span>InnerText</span>";
|
||||
|
||||
const inner = host.shadowRoot.firstChild;
|
||||
const rect = inner.getBoundingClientRect();
|
||||
|
||||
// Drag from the bottom right of InnerText to the
|
||||
// top left of OuterText.
|
||||
drag(
|
||||
inner,
|
||||
rect.width,
|
||||
rect.height,
|
||||
document.getElementById("outer"),
|
||||
0,
|
||||
0);
|
||||
|
||||
// Above drag selects both "OuterText" and "InnerText"
|
||||
const sel = document.getSelection().getComposedRanges(host.shadowRoot)[0];
|
||||
|
||||
// backward selection
|
||||
is(sel.endContainer, inner.firstChild, "endContainer is the InnerText");
|
||||
is(sel.endOffset, 9, "endOffset ends at the last character");
|
||||
is(sel.startContainer, outer.firstChild, "startContainer is the OuterText");
|
||||
is(sel.startOffset, 0, "startOffset starts at the first character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer">OuterText</span>
|
||||
<div id="host"></div>
|
||||
</body>
|
@ -1,35 +0,0 @@
|
||||
<!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() {
|
||||
document.getElementById("host").attachShadow({ mode: "open" }).innerHTML = "<span>InnerText</span>";
|
||||
|
||||
// Click the top left of "OuterText"
|
||||
synthesizeMouse(document.getElementById("outer"), 0, 0, { type: "mousedown" });
|
||||
synthesizeMouse(document.getElementById("outer"), 0, 0, { type: "mouseup" });
|
||||
|
||||
// Click the bottom right of "InnerText"
|
||||
const inner = host.shadowRoot.firstChild;
|
||||
const rect = inner.getBoundingClientRect();
|
||||
synthesizeMouse(inner, rect.width, rect.height, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(inner, rect.width, rect.height, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
// Above two clicks should select both "OuterText" and "InnerText"
|
||||
const 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");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer">OuterText</span>
|
||||
<div id="host"></div>
|
||||
</body>
|
@ -1,39 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
function run() {
|
||||
document.getElementById("host").attachShadow({ mode: "open" }).innerHTML = "<span>InnerText</span>";
|
||||
|
||||
const inner = host.shadowRoot.firstChild;
|
||||
const rect = inner.getBoundingClientRect();
|
||||
|
||||
// drag from the top left of OuterText
|
||||
// to the bottom right of InnerText
|
||||
drag(
|
||||
document.getElementById("outer"),
|
||||
0,
|
||||
0,
|
||||
inner,
|
||||
rect.width,
|
||||
rect.height);
|
||||
|
||||
// Above drag selects should select both "OuterText" and "InnerText"
|
||||
const 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");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer">OuterText</span>
|
||||
<div id="host"></div>
|
||||
</body>
|
@ -1,47 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function run() {
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "InnerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "<span>InnerText2</span>";
|
||||
|
||||
|
||||
|
||||
const inner2 = root2.firstChild;
|
||||
const rect = inner2.getBoundingClientRect();
|
||||
|
||||
// Click the bottom right of "InnerText2"
|
||||
synthesizeMouse(inner2, rect.width, rect.height, { type: "mousedown" });
|
||||
synthesizeMouse(inner2, rect.width, rect.height, { type: "mouseup" });
|
||||
|
||||
// Click the top left of "OuterText1"
|
||||
synthesizeMouse(document.getElementById("outer1"), 0, 0, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(document.getElementById("outer1"), 0, 0, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
// Above clicks selects should select
|
||||
// "OuterText1", "OuterText2", "InnerText1" and "InnerText2".
|
||||
const sel = document.getSelection().getComposedRanges(root2)[0];
|
||||
|
||||
// backward selection
|
||||
is(sel.endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(sel.endOffset, 10, "endOffset ends at the last character");
|
||||
is(sel.startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(sel.startOffset, 0, "startOffset starts at the first character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1"></div>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2"></div>
|
||||
</body>
|
@ -1,48 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function run() {
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "InnerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "<span>InnerText2</span>";
|
||||
|
||||
const inner2 = root2.firstChild;
|
||||
const rect = inner2.getBoundingClientRect();
|
||||
|
||||
const outer1 = document.getElementById("outer1");
|
||||
// Drag from the bottom right of InnerText2 to
|
||||
// the top left of OuterText1.
|
||||
drag(
|
||||
inner2,
|
||||
rect.width,
|
||||
rect.height,
|
||||
outer1,
|
||||
0,
|
||||
0);
|
||||
|
||||
// Above drag should selects
|
||||
// "OuterText1", "OuterText2", "InnerText1" and "InnerText2".
|
||||
const sel = document.getSelection().getComposedRanges(root2)[0];
|
||||
|
||||
// backward selection
|
||||
is(sel.endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(sel.endOffset, 10, "endOffset ends at the last character");
|
||||
is(sel.startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(sel.startOffset, 0, "startOffset starts at the first character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1"></div>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2"></div>
|
||||
</body>
|
@ -1,46 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function run() {
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "InnerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "<span>InnerText2</span>";
|
||||
|
||||
const outer1 = document.getElementById("outer1");
|
||||
// Click the top left of "OuterText1"
|
||||
synthesizeMouse(outer1, 0, 0, { type: "mousedown" });
|
||||
synthesizeMouse(outer1, 0, 0, { type: "mouseup" });
|
||||
|
||||
const inner2 = root2.firstChild;
|
||||
const rect = inner2.getBoundingClientRect();
|
||||
|
||||
// Click the bottom right of "InnerText2"
|
||||
synthesizeMouse(inner2, rect.width, rect.height, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(inner2, rect.width, rect.height, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
// Above clicks should select
|
||||
// "OuterText1", "OuterText2", "InnerText1" and "InnerText2".
|
||||
const sel = document.getSelection().getComposedRanges(root2)[0];
|
||||
|
||||
// forward selection
|
||||
is(sel.startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(sel.startOffset, 0, "startOffset starts at the first character");
|
||||
is(sel.endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(sel.endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1"></div>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2"></div>
|
||||
</body>
|
@ -1,48 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function run() {
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "InnerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "<span>InnerText2</span>";
|
||||
|
||||
const inner2 = root2.firstChild;
|
||||
const rect = inner2.getBoundingClientRect();
|
||||
|
||||
const outer1 = document.getElementById("outer1");
|
||||
// Drag from the top lef of OuterText1 to the
|
||||
// bottom right of InnerText2.
|
||||
drag(
|
||||
outer1,
|
||||
0,
|
||||
0,
|
||||
inner2,
|
||||
rect.width,
|
||||
rect.height);
|
||||
|
||||
// Above drag should select
|
||||
// "OuterText1", "OuterText2", "InnerText1" and "InnerText2".
|
||||
const sel = document.getSelection().getComposedRanges(root2)[0];
|
||||
|
||||
// forward selection
|
||||
is(sel.startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(sel.startOffset, 0, "startOffset starts at the first character");
|
||||
is(sel.endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(sel.endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1"></div>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2"></div>
|
||||
</body>
|
@ -1,61 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
function run() {
|
||||
const inner1 = host1.shadowRoot.querySelector("span");
|
||||
const rect1 = inner1.getBoundingClientRect();
|
||||
|
||||
// Click the bottom right of "InnerText1"
|
||||
synthesizeMouse(inner1, rect1.width - 1, rect1.height - 1, { type: "mousedown" });
|
||||
synthesizeMouse(inner1, rect1.width - 1, rect1.height - 1, { type: "mouseup" });
|
||||
|
||||
// Click the top left of "OuterText1"
|
||||
synthesizeMouse(document.getElementById("outer1"), 0, 0, { type: "mousedown", shiftKey: true });
|
||||
synthesizeMouse(document.getElementById("outer1"), 0, 0, { type: "mouseup", shiftKey: true });
|
||||
|
||||
const inner2 = host2.shadowRoot.querySelector("span");
|
||||
const rect2 = inner2.getBoundingClientRect();
|
||||
|
||||
// Click the bottom right of "InnerText2" with accelKey
|
||||
synthesizeMouse(inner2, rect2.width, rect2.height, { type: "mousedown", accelKey: true});
|
||||
synthesizeMouse(inner2, rect2.width, rect2.height, { type: "mouseup" , accelKey: true});
|
||||
|
||||
// Click the top left of "OuterText2"
|
||||
synthesizeMouse(document.getElementById("outer2"), 1, 1, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(document.getElementById("outer2"), 1, 1, { type: "mouseup", shiftKey: true});
|
||||
|
||||
const ranges = document.getSelection().getComposedRanges(host1.shadowRoot, host2.shadowRoot);
|
||||
is(ranges.length, 2, "Above two drag selection should produce two ranges");
|
||||
|
||||
is(ranges[0].startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(ranges[0].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[0].endContainer, inner1.firstChild, "endContainer is the InnerText1");
|
||||
is(ranges[0].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
is(ranges[1].startContainer, outer2.firstChild, "startContainer is the OuterText2");
|
||||
is(ranges[1].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[1].endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(ranges[1].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText1</span>
|
||||
</template>
|
||||
</div>
|
||||
<br>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText2</span>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
@ -1,65 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
function run() {
|
||||
const inner1 = host1.shadowRoot.querySelector("span");
|
||||
const rect1 = inner1.getBoundingClientRect();
|
||||
|
||||
// drag from the bottom right of InnerText1
|
||||
// to the top left of OuterText1
|
||||
drag(
|
||||
inner1,
|
||||
rect1.width - 1,
|
||||
rect1.height - 1,
|
||||
document.getElementById("outer1"),
|
||||
1,
|
||||
1);
|
||||
|
||||
const inner2 = host2.shadowRoot.querySelector("span");
|
||||
const rect2 = inner2.getBoundingClientRect();
|
||||
// drag from the bottom right of InnerText2
|
||||
// to the top left of OuterText2
|
||||
drag(
|
||||
inner2,
|
||||
rect2.width,
|
||||
rect2.height,
|
||||
document.getElementById("outer2"),
|
||||
1,
|
||||
1,
|
||||
true /* accelKey */);
|
||||
|
||||
const ranges = document.getSelection().getComposedRanges(host1.shadowRoot, host2.shadowRoot);
|
||||
is(ranges.length, 2, "Above two drag selection should produce two ranges");
|
||||
|
||||
is(ranges[0].startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(ranges[0].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[0].endContainer, inner1.firstChild, "endContainer is the InnerText1");
|
||||
is(ranges[0].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
is(ranges[1].startContainer, outer2.firstChild, "startContainer is the OuterText2");
|
||||
is(ranges[1].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[1].endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(ranges[1].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText1</span>
|
||||
</template>
|
||||
</div>
|
||||
<br>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText2</span>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
@ -1,59 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
function run() {
|
||||
// Click the top left of "OuterText1"
|
||||
synthesizeMouse(document.getElementById("outer1"), 1, 1, { type: "mousedown" });
|
||||
synthesizeMouse(document.getElementById("outer1"), 1, 1, { type: "mouseup" });
|
||||
|
||||
const inner1 = host1.shadowRoot.querySelector("span");
|
||||
const rect1 = inner1.getBoundingClientRect();
|
||||
// Click the bottom right of "InnerText1"
|
||||
synthesizeMouse(inner1, rect1.width - 1, rect1.height - 1, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(inner1, rect1.width - 1, rect1.height - 1, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
// Click the top left of "OuterText2" with accelKey
|
||||
synthesizeMouse(document.getElementById("outer2"), 1, 1, { type: "mousedown", accelKey: true});
|
||||
synthesizeMouse(document.getElementById("outer2"), 1, 1, { type: "mouseup", accelKey: true});
|
||||
|
||||
const inner2 = host2.shadowRoot.querySelector("span");
|
||||
const rect2 = inner2.getBoundingClientRect();
|
||||
// Click the bottom right of "InnerText2"
|
||||
synthesizeMouse(inner2, rect2.width, rect2.height, { type: "mousedown", shiftKey: true});
|
||||
synthesizeMouse(inner2, rect2.width, rect2.height, { type: "mouseup" , shiftKey: true});
|
||||
|
||||
const ranges = document.getSelection().getComposedRanges(host1.shadowRoot, host2.shadowRoot);
|
||||
is(ranges.length, 2, "Above two drag selection should produce two ranges");
|
||||
|
||||
is(ranges[0].startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(ranges[0].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[0].endContainer, inner1.firstChild, "endContainer is the InnerText1");
|
||||
is(ranges[0].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
is(ranges[1].startContainer, outer2.firstChild, "startContainer is the OuterText2");
|
||||
is(ranges[1].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[1].endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(ranges[1].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText1</span>
|
||||
</template>
|
||||
</div>
|
||||
<br>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText2</span>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
@ -1,65 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/layout/generic/test/selection_cross_shadow_boundary_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script>
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
function run() {
|
||||
const inner1 = host1.shadowRoot.querySelector("span");
|
||||
const rect1 = inner1.getBoundingClientRect();
|
||||
|
||||
// drag from the top left of OuterText1
|
||||
// to the bottom right of InnerText1
|
||||
drag(
|
||||
document.getElementById("outer1"),
|
||||
1,
|
||||
1,
|
||||
inner1,
|
||||
rect1.width - 1,
|
||||
rect1.height - 1);
|
||||
|
||||
const inner2 = host2.shadowRoot.querySelector("span");
|
||||
const rect2 = inner2.getBoundingClientRect();
|
||||
// drag from the top left of OuterText2
|
||||
// to the bottom right of InnerText2
|
||||
drag(
|
||||
document.getElementById("outer2"),
|
||||
1,
|
||||
1,
|
||||
inner2,
|
||||
rect2.width,
|
||||
rect2.height,
|
||||
true /* accelKey */);
|
||||
|
||||
const ranges = document.getSelection().getComposedRanges(host1.shadowRoot, host2.shadowRoot);
|
||||
is(ranges.length, 2, "Above two drag selection should produce two ranges");
|
||||
|
||||
is(ranges[0].startContainer, outer1.firstChild, "startContainer is the OuterText1");
|
||||
is(ranges[0].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[0].endContainer, inner1.firstChild, "endContainer is the InnerText1");
|
||||
is(ranges[0].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
is(ranges[1].startContainer, outer2.firstChild, "startContainer is the OuterText2");
|
||||
is(ranges[1].startOffset, 0, "startOffset starts at the first character");
|
||||
is(ranges[1].endContainer, inner2.firstChild, "endContainer is the InnerText2");
|
||||
is(ranges[1].endOffset, 10, "endOffset ends at the last character");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<body onload="SimpleTest.waitForFocus(run);">
|
||||
<span id="outer1">OuterText1</span>
|
||||
<div id="host1">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText1</span>
|
||||
</template>
|
||||
</div>
|
||||
<br>
|
||||
<span id="outer2">OuterText2</span>
|
||||
<div id="host2">
|
||||
<template shadowrootmode="open">
|
||||
<span>InnerText2</span>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
@ -4588,14 +4588,6 @@
|
||||
value: @IS_NOT_ANDROID@
|
||||
mirror: always
|
||||
|
||||
# Whether allowing selection across the boundary
|
||||
# between shadow DOM and light DOM.
|
||||
# This is based on https://github.com/mfreed7/shadow-dom-selection
|
||||
- name: dom.shadowdom.selection_across_boundary.enabled
|
||||
type: bool
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
|
||||
# NOTE: This preference is used in unit tests. If it is removed or its default
|
||||
# value changes, please update test_sharedMap_static_prefs.js accordingly.
|
||||
- name: dom.webcomponents.shadowdom.report_usage
|
||||
|
@ -1,3 +1,18 @@
|
||||
[idlharness.window.html]
|
||||
[Selection interface: operation modify(optional DOMString, optional DOMString, optional DOMString)]
|
||||
expected: FAIL
|
||||
|
||||
[Selection interface: attribute direction]
|
||||
expected: FAIL
|
||||
|
||||
[Selection interface: operation getComposedRanges(ShadowRoot...)]
|
||||
expected: FAIL
|
||||
|
||||
[Selection interface: getSelection() must inherit property "direction" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Selection interface: getSelection() must inherit property "getComposedRanges(ShadowRoot...)" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Selection interface: calling getComposedRanges(ShadowRoot...) on getSelection() with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
@ -0,0 +1,12 @@
|
||||
[selection-collapse-and-extend.tentative.html]
|
||||
[collapse can set selection to a node inside a shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[collapse abort steps when called with a disconnected node inside a shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[extend can set selection to a node inside a shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[extend abort steps when called with a disconnected node inside a shadow tree]
|
||||
expected: FAIL
|
@ -0,0 +1,21 @@
|
||||
[selection-direction.tentative.html]
|
||||
[direction returns "none" when there is no selection]
|
||||
expected: FAIL
|
||||
|
||||
[direction returns "forward" when there is a forward-direction selection in the document tree]
|
||||
expected: FAIL
|
||||
|
||||
[direction returns "backward" when there is a backward-direction selection in the document tree]
|
||||
expected: FAIL
|
||||
|
||||
[direction returns "forward" when there is a forward selection in the shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[direction returns "backward" when there is a backward selection in the shadow tree]
|
||||
expected: FAIL
|
||||
|
||||
[direction returns "forward" when there is a forward selection that crosses shadow boundaries]
|
||||
expected: FAIL
|
||||
|
||||
[direction returns "backward" when there is a forward selection that crosses shadow boundaries]
|
||||
expected: FAIL
|
@ -0,0 +1,30 @@
|
||||
[selection-getComposedRanges.tentative.html]
|
||||
[getComposedRanges returns an empty sequence when there is no selection]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges returns a sequence with a static range when there is a forward-direction selection in the document tree]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges returns a sequence with a static range when there is a backward-direction selection in the document tree]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges returns a sequence with a static range pointing to a shadaw tree when there is a selection in the shadow tree and the shadow tree is specified as an argument]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges returns a sequence with a static range pointing to the shadow host when there is a selection in a shadow tree and the shadow tree is not specified as an argument]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges a sequence with a static range pointing to the shadow host when there is a forward selection that crosses shadow boundaries and the shadow tree is not specified as an argument]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges a sequence with a static range that crosses shadow boundaries when there is a forward selection that crosses shadow boundaries and the shadow tree is specified as an argument]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges returns a sequence with a static range pointing to the outer shadow host when there is a selection in an inner shadow tree and no shadow tree is specified as an argument]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges returns a sequence with a static range pointing to the inner shadow tree when there is a selection in an inner shadow tree and the inner shadow tree is specified as an argument]
|
||||
expected: FAIL
|
||||
|
||||
[getComposedRanges returns a sequence with a static range pointing to the outer shadow tree when there is a selection in an inner shadow tree and the outer shadow tree is specified as an argument]
|
||||
expected: FAIL
|
@ -1,6 +0,0 @@
|
||||
<!doctype html>
|
||||
OuterText
|
||||
<div>innerText</div>
|
||||
<script>
|
||||
getSelection().selectAllChildren(document.body);
|
||||
</script>
|
@ -1,10 +0,0 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<link rel="match" href="cross-shadow-boundary-1-ref.html"/>
|
||||
</head>
|
||||
OuterText
|
||||
<div id="host"></div>
|
||||
<script>
|
||||
document.getElementById("host").attachShadow({mode: "open"}).innerHTML = "innerText";
|
||||
getSelection().selectAllChildren(document.body);
|
||||
</script>
|
@ -1,7 +0,0 @@
|
||||
<!doctype html>
|
||||
OuterText
|
||||
<div>innerText</div>
|
||||
OuterText
|
||||
<script>
|
||||
getSelection().selectAllChildren(document.body);
|
||||
</script>
|
@ -1,11 +0,0 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<link rel="match" href="cross-shadow-boundary-2-ref.html" />
|
||||
</head>
|
||||
OuterText
|
||||
<div id="host"></div>
|
||||
OuterText
|
||||
<script>
|
||||
document.getElementById("host").attachShadow({ mode: "open" }).innerHTML = "innerText";
|
||||
getSelection().selectAllChildren(document.body);
|
||||
</script>
|
@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
OuterText
|
||||
<div id="host1">innerText1</div>
|
||||
OuterText
|
||||
<div id="host2">innerText2</div>
|
||||
<script>
|
||||
const host1 = document.getElementById("host1");
|
||||
const host2 = document.getElementById("host2");
|
||||
|
||||
getSelection().setBaseAndExtent(
|
||||
host1.firstChild, 3, host2.firstChild, 3);
|
||||
</script>
|
@ -1,17 +0,0 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<link rel="match" href="cross-shadow-boundary-3-ref.html" />
|
||||
</head>
|
||||
OuterText
|
||||
<div id="host1"></div>
|
||||
OuterText
|
||||
<div id="host2"></div>
|
||||
<script>
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "innerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "innerText2";
|
||||
|
||||
getSelection().setBaseAndExtent(root1.firstChild, 3, root2.firstChild, 3);
|
||||
</script>
|
@ -1,21 +0,0 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<!--Intentionally to use cross-shadow-boundary-3-ref.html here-->
|
||||
<link rel=match href="cross-shadow-boundary-3-ref.html">
|
||||
</head>
|
||||
OuterText
|
||||
<div id="host1"></div>
|
||||
OuterText
|
||||
<div id="host2"></div>
|
||||
<script>
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "innerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "<div></div>";
|
||||
|
||||
const root3 = root2.querySelector("div").attachShadow({ mode: "open" });
|
||||
root3.innerHTML = "innerText2";
|
||||
|
||||
getSelection().setBaseAndExtent(root1.firstChild, 3, root3.firstChild, 3);
|
||||
</script>
|
@ -1,13 +0,0 @@
|
||||
<!doctype html>
|
||||
OuterText1
|
||||
<div>innerText1</div>
|
||||
OuterText2
|
||||
<div>innerText2</div>
|
||||
OuterText3
|
||||
<script>
|
||||
getSelection().setBaseAndExtent(
|
||||
document.body.firstChild,
|
||||
3,
|
||||
document.body.childNodes[4],
|
||||
3);
|
||||
</script>
|
@ -1,20 +0,0 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<link rel=match href="cross-shadow-boundary-5-ref.html">
|
||||
</head>
|
||||
OuterText1
|
||||
<div id="host1"></div>
|
||||
OuterText2
|
||||
<div id="host2"></div>
|
||||
OuterText3
|
||||
<script>
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "innerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "<div></div>";
|
||||
|
||||
const root3 = root2.querySelector("div").attachShadow({ mode: "open" });
|
||||
root3.innerHTML = "innerText2";
|
||||
getSelection().setBaseAndExtent(document.body.firstChild, 3, document.body.childNodes[4], 3);
|
||||
</script>
|
@ -1,9 +0,0 @@
|
||||
<!doctype html>
|
||||
OuterText1
|
||||
<div>innerText1</div>
|
||||
OuterText2
|
||||
<div><img style="width: 10px; height: 10px; background-color: black"></img></div>
|
||||
OuterText3
|
||||
<script>
|
||||
getSelection().setBaseAndExtent(document.body.firstChild, 3, document.body.childNodes[4], 3);
|
||||
</script>
|
@ -1,26 +0,0 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<link rel=match href="cross-shadow-boundary-img-ref.html">
|
||||
</head>
|
||||
OuterText1
|
||||
<div id="host1"></div>
|
||||
OuterText2
|
||||
<div id="host2"></div>
|
||||
OuterText3
|
||||
<script>
|
||||
const root1 = document.getElementById("host1").attachShadow({ mode: "open" });
|
||||
root1.innerHTML = "innerText1";
|
||||
|
||||
const root2 = document.getElementById("host2").attachShadow({ mode: "open" });
|
||||
root2.innerHTML = "<div></div>";
|
||||
|
||||
const root3 = root2.querySelector("div").attachShadow({ mode: "open" });
|
||||
root3.innerHTML = "<img>";
|
||||
|
||||
const img = root3.querySelector("img");
|
||||
img.style.width = "10px";
|
||||
img.style.height = "10px";
|
||||
img.style.backgroundColor = "black";
|
||||
|
||||
getSelection().setBaseAndExtent(document.body.firstChild, 3, document.body.childNodes[4], 3);
|
||||
</script>
|
@ -1,9 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<div>CONTENT</div>
|
||||
<script>
|
||||
const div = document.querySelector("div");
|
||||
getSelection().setBaseAndExtent(div.firstChild, 0, div.firstChild, 2);
|
||||
</script>
|
||||
</html>
|
||||
|
@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel=match href="cross-shadow-boundary-select-document-ref.html">
|
||||
</head>
|
||||
<div></div>
|
||||
<script>
|
||||
const root = document.querySelector("div").attachShadow({mode: "open"});
|
||||
root.innerHTML = "CONTENT";
|
||||
getSelection().setBaseAndExtent(document, 0, root.firstChild, 2);
|
||||
</script>
|
||||
</html>
|
@ -1,11 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<div id="outerText">OuterText1</div>
|
||||
<div>InnerText1</div>
|
||||
<div>OuterText2</div>
|
||||
<div id="host">InnerText2</div>
|
||||
<script>
|
||||
window.getSelection().setBaseAndExtent(outerText, 0, host, host.childNodes.length);
|
||||
</script>
|
||||
</body></html>
|
@ -1,24 +0,0 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<link rel=match href="cross-shadow-boundary-select-root-ref.html">
|
||||
</head>
|
||||
<div id="outerText1">OuterText1</div>
|
||||
<div id="host1"></div>
|
||||
<div id="outerText2">OuterText2</div>
|
||||
<div id="host2"></div>
|
||||
<div id="host3"></div>
|
||||
<script>
|
||||
const outerText1 = document.getElementById("outerText1");
|
||||
const outerText2 = document.getElementById("outerText2");
|
||||
|
||||
const host1 = document.getElementById("host1");
|
||||
const root1 = host1.attachShadow({mode: "open"});
|
||||
root1.innerHTML = "InnerText1";
|
||||
|
||||
const host2 = document.getElementById("host2");
|
||||
const root2 = host2.attachShadow({mode: "open"});
|
||||
root2.innerHTML = "InnerText2";
|
||||
|
||||
getSelection().setBaseAndExtent(outerText1, 0, root2, root2.childNodes.length);
|
||||
</script>
|
||||
|
@ -6,9 +6,6 @@
|
||||
<link rel="help" href="https://w3c.github.io/selection-api/#dom-selection-getcomposedrange">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-actions.js"></script>
|
||||
<script src='/resources/testdriver-vendor.js'></script>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
|
||||
@ -61,49 +58,6 @@ test(() => {
|
||||
assert_equals(getSelection().direction, 'backward');
|
||||
}, 'direction returns "backward" when there is a forward selection that crosses shadow boundaries');
|
||||
|
||||
async_test(t => {
|
||||
container.innerHTML = 'hello, world';
|
||||
const doubleClick = new test_driver.Actions()
|
||||
.pointerMove(0, 0, container.firstChild)
|
||||
.pointerDown()
|
||||
.pointerUp()
|
||||
.pointerDown()
|
||||
.pointerUp()
|
||||
.send();
|
||||
|
||||
doubleClick.then(function() {
|
||||
const sel = getSelection();
|
||||
assert_equals(sel.anchorNode, container.firstChild);
|
||||
assert_equals(sel.anchorOffset, 0);
|
||||
assert_equals(sel.focusNode, container.firstChild);
|
||||
assert_equals(sel.focusOffset, 5); // hello
|
||||
assert_equals(sel.direction, 'none');
|
||||
t.done();
|
||||
});
|
||||
}, 'direction returns "none" when there is a double click selection(directionless)');
|
||||
|
||||
async_test(t => {
|
||||
container.innerHTML = 'hello, world';
|
||||
const tripleClick = new test_driver.Actions()
|
||||
.pointerMove(0, 0, container.firstChild)
|
||||
.pointerDown()
|
||||
.pointerUp()
|
||||
.pointerDown()
|
||||
.pointerUp()
|
||||
.pointerDown()
|
||||
.pointerUp()
|
||||
.send();
|
||||
|
||||
tripleClick.then(function() {
|
||||
const sel = getSelection();
|
||||
assert_equals(sel.anchorNode, container);
|
||||
assert_equals(sel.anchorOffset, 0);
|
||||
assert_equals(sel.focusNode, container);
|
||||
assert_equals(sel.focusOffset, 1);
|
||||
assert_equals(sel.direction, 'none');
|
||||
t.done();
|
||||
});
|
||||
}, 'direction returns "none" when there is a triple click selection(directionless)');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user