Bug 1881096 - Make nsPrintJob handles shadow-crossing selection r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D212929
This commit is contained in:
Sean Feng 2024-06-20 20:15:51 +00:00
parent 0fd347f4a2
commit 3389d06b50
6 changed files with 91 additions and 36 deletions

View File

@ -15,6 +15,7 @@
#include "mozilla/dom/Document.h"
#include "mozilla/dom/StaticRange.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/CrossShadowBoundaryRange.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsGkAtoms.h"
@ -573,6 +574,24 @@ JSObject* AbstractRange::WrapObject(JSContext* aCx,
MOZ_CRASH("Must be overridden");
}
bool AbstractRange::AreNormalRangeAndCrossShadowBoundaryRangeCollapsed() const {
if (!Collapsed()) {
return false;
}
// We know normal range is collapsed at this point
if (IsStaticRange()) {
return true;
}
if (const CrossShadowBoundaryRange* crossShadowBoundaryRange =
AsDynamicRange()->GetCrossShadowBoundaryRange()) {
return crossShadowBoundaryRange->Collapsed();
}
return true;
}
void AbstractRange::ClearForReuse() {
mOwner = nullptr;
mStart = RangeBoundary();

View File

@ -125,6 +125,8 @@ class AbstractRange : public nsISupports,
StartOffset() == EndOffset());
}
bool AreNormalRangeAndCrossShadowBoundaryRangeCollapsed() const;
nsINode* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;

View File

@ -13458,8 +13458,8 @@ static void CachePrintSelectionRanges(const Document& aSourceDoc,
const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
: origSelection->GetRangeAt(i);
MOZ_ASSERT(range);
nsINode* startContainer = range->GetStartContainer();
nsINode* endContainer = range->GetEndContainer();
nsINode* startContainer = range->GetMayCrossShadowBoundaryStartContainer();
nsINode* endContainer = range->GetMayCrossShadowBoundaryEndContainer();
if (!startContainer || !endContainer) {
continue;
@ -13474,10 +13474,11 @@ static void CachePrintSelectionRanges(const Document& aSourceDoc,
continue;
}
RefPtr<nsRange> clonedRange =
nsRange::Create(startNode, range->StartOffset(), endNode,
range->EndOffset(), IgnoreErrors());
if (clonedRange && !clonedRange->Collapsed()) {
RefPtr<nsRange> clonedRange = nsRange::Create(
startNode, range->MayCrossShadowBoundaryStartOffset(), endNode,
range->MayCrossShadowBoundaryEndOffset(), IgnoreErrors());
if (clonedRange &&
!clonedRange->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) {
printRanges->AppendElement(std::move(clonedRange));
}
}

View File

@ -730,8 +730,12 @@ void nsRange::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) {
bool newStartIsSet = newStart.IsSet();
bool newEndIsSet = newEnd.IsSet();
if (newStartIsSet || newEndIsSet) {
DoSetRange(newStartIsSet ? newStart : mStart.AsRaw(),
newEndIsSet ? newEnd : mEnd.AsRaw(), mRoot);
DoSetRange(
newStartIsSet ? newStart : mStart.AsRaw(),
newEndIsSet ? newEnd : mEnd.AsRaw(), mRoot, false,
// CrossShadowBoundaryRange mutates content
// removal fot itself, so no need for nsRange to do anything with it.
RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges);
} else {
nsRange::AssertIfMismatchRootAndRangeBoundaries(mStart, mEnd, mRoot);
}
@ -1463,7 +1467,8 @@ class MOZ_STACK_CLASS RangeSubtreeIterator {
RangeSubtreeIterator() : mIterState(eDone) {}
~RangeSubtreeIterator() = default;
nsresult Init(nsRange* aRange);
nsresult Init(nsRange* aRange, AllowRangeCrossShadowBoundary =
AllowRangeCrossShadowBoundary::No);
already_AddRefed<nsINode> GetCurrentNode();
void First();
void Last();
@ -1473,9 +1478,10 @@ class MOZ_STACK_CLASS RangeSubtreeIterator {
bool IsDone() { return mIterState == eDone; }
};
nsresult RangeSubtreeIterator::Init(nsRange* aRange) {
nsresult RangeSubtreeIterator::Init(
nsRange* aRange, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
mIterState = eDone;
if (aRange->Collapsed()) {
if (aRange->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) {
return NS_OK;
}
@ -1487,14 +1493,14 @@ nsresult RangeSubtreeIterator::Init(nsRange* aRange) {
return NS_ERROR_FAILURE;
}
nsINode* node = aRange->GetStartContainer();
nsINode* node = aRange->GetMayCrossShadowBoundaryStartContainer();
if (NS_WARN_IF(!node)) {
return NS_ERROR_FAILURE;
}
if (node->IsCharacterData() ||
(node->IsElement() &&
node->AsElement()->GetChildCount() == aRange->StartOffset())) {
(node->IsElement() && node->AsElement()->GetChildCount() ==
aRange->MayCrossShadowBoundaryStartOffset())) {
mStart = node;
}
@ -1502,13 +1508,13 @@ nsresult RangeSubtreeIterator::Init(nsRange* aRange) {
// a CharacterData pointer. If it is CharacterData store
// a pointer to the node.
node = aRange->GetEndContainer();
node = aRange->GetMayCrossShadowBoundaryEndContainer();
if (NS_WARN_IF(!node)) {
return NS_ERROR_FAILURE;
}
if (node->IsCharacterData() ||
(node->IsElement() && aRange->EndOffset() == 0)) {
(node->IsElement() && aRange->MayCrossShadowBoundaryEndOffset() == 0)) {
mEnd = node;
}
@ -1524,7 +1530,10 @@ nsresult RangeSubtreeIterator::Init(nsRange* aRange) {
mSubtreeIter.emplace();
nsresult res = mSubtreeIter->Init(aRange);
nsresult res =
aAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
? mSubtreeIter->InitWithAllowCrossShadowBoundary(aRange)
: mSubtreeIter->Init(aRange);
if (NS_FAILED(res)) return res;
if (mSubtreeIter->IsDone()) {
@ -1757,14 +1766,18 @@ void nsRange::CutContents(DocumentFragment** aFragment, ErrorResult& aRv) {
*aFragment = nullptr;
}
if (!CanAccess(*mStart.Container()) || !CanAccess(*mEnd.Container())) {
if (!CanAccess(*GetMayCrossShadowBoundaryStartContainer()) ||
!CanAccess(*GetMayCrossShadowBoundaryEndContainer())) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<Document> doc = mStart.Container()->OwnerDoc();
nsCOMPtr<nsINode> commonAncestor = GetCommonAncestorContainer(aRv);
nsCOMPtr<nsINode> commonAncestor = GetCommonAncestorContainer(
aRv, StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
? AllowRangeCrossShadowBoundary::Yes
: AllowRangeCrossShadowBoundary::No);
if (aRv.Failed()) {
return;
}
@ -1783,13 +1796,16 @@ void nsRange::CutContents(DocumentFragment** aFragment, ErrorResult& aRv) {
// Save the range end points locally to avoid interference
// of Range gravity during our edits!
nsCOMPtr<nsINode> startContainer = mStart.Container();
nsCOMPtr<nsINode> startContainer = GetMayCrossShadowBoundaryStartContainer();
// `GetCommonAncestorContainer()` above ensures the range is positioned, hence
// there have to be valid offsets.
uint32_t startOffset =
*mStart.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
nsCOMPtr<nsINode> endContainer = mEnd.Container();
uint32_t endOffset = *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
const uint32_t startOffset = *MayCrossShadowBoundaryStartRef().Offset(
RangeBoundary::OffsetFilter::kValidOffsets);
nsCOMPtr<nsINode> endContainer = GetMayCrossShadowBoundaryEndContainer();
const uint32_t endOffset = *MayCrossShadowBoundaryEndRef().Offset(
RangeBoundary::OffsetFilter::kValidOffsets);
if (retval) {
// For extractContents(), abort early if there's a doctype (bug 719533).
@ -1820,7 +1836,10 @@ void nsRange::CutContents(DocumentFragment** aFragment, ErrorResult& aRv) {
RangeSubtreeIterator iter;
aRv = iter.Init(this);
aRv = iter.Init(this,
StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
? AllowRangeCrossShadowBoundary::Yes
: AllowRangeCrossShadowBoundary::No);
if (aRv.Failed()) {
return;
}

View File

@ -223,12 +223,15 @@ class nsRange final : public mozilla::dom::AbstractRange,
void DeleteContents(ErrorResult& aRv);
already_AddRefed<mozilla::dom::DocumentFragment> ExtractContents(
ErrorResult& aErr);
nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const {
nsINode* GetCommonAncestorContainer(
ErrorResult& aRv,
AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
AllowRangeCrossShadowBoundary::No) const {
if (!mIsPositioned) {
aRv.Throw(NS_ERROR_NOT_INITIALIZED);
return nullptr;
}
return GetClosestCommonInclusiveAncestor();
return GetClosestCommonInclusiveAncestor(aAllowCrossShadowBoundary);
}
void InsertNode(nsINode& aNode, ErrorResult& aErr);
bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);

View File

@ -1553,14 +1553,16 @@ struct MOZ_STACK_CLASS SelectionRangeState {
void SelectionRangeState::SelectComplementOf(
Span<const RefPtr<nsRange>> aRanges) {
for (const auto& range : aRanges) {
auto start = Position{range->GetStartContainer(), range->StartOffset()};
auto end = Position{range->GetEndContainer(), range->EndOffset()};
auto start = Position{range->GetMayCrossShadowBoundaryStartContainer(),
range->MayCrossShadowBoundaryStartOffset()};
auto end = Position{range->GetMayCrossShadowBoundaryEndContainer(),
range->MayCrossShadowBoundaryEndOffset()};
SelectNodesExcept(start, end);
}
}
void SelectionRangeState::SelectRange(nsRange* aRange) {
if (aRange && !aRange->Collapsed()) {
if (aRange && !aRange->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) {
mSelection->AddRangeAndSelectFramesAndNotifyListeners(*aRange,
IgnoreErrors());
}
@ -1569,11 +1571,16 @@ void SelectionRangeState::SelectRange(nsRange* aRange) {
void SelectionRangeState::SelectNodesExcept(const Position& aStart,
const Position& aEnd) {
SelectNodesExceptInSubtree(aStart, aEnd);
if (auto* shadow = ShadowRoot::FromNode(aStart.mNode->SubtreeRoot())) {
auto* host = shadow->Host();
SelectNodesExcept(Position{host, 0}, Position{host, host->GetChildCount()});
} else {
MOZ_ASSERT(aStart.mNode->IsInUncomposedDoc());
if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
if (auto* shadow = ShadowRoot::FromNode(aStart.mNode->SubtreeRoot())) {
auto* host = shadow->Host();
// Can't just select other nodes except the host, because other nodes that
// are not in this particular shadow tree could also be selected
SelectNodesExcept(Position{host, 0},
Position{host, host->GetChildCount()});
} else {
MOZ_ASSERT(aStart.mNode->IsInUncomposedDoc());
}
}
}
@ -1581,7 +1588,11 @@ void SelectionRangeState::SelectNodesExceptInSubtree(const Position& aStart,
const Position& aEnd) {
static constexpr auto kEllipsis = u"\x2026"_ns;
nsINode* root = aStart.mNode->SubtreeRoot();
// Finish https://bugzilla.mozilla.org/show_bug.cgi?id=1903871 once the pref
// is shipped, so that we only need one position.
nsINode* root = StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
? aStart.mNode->OwnerDoc()
: aStart.mNode->SubtreeRoot();
auto& start =
mPositions.WithEntryHandle(root, [&](auto&& entry) -> Position& {
return entry.OrInsertWith([&] { return Position{root, 0}; });