1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Implementation of the DOM Range object.
11 #include "RangeBoundary.h"
17 #include "nsReadableUtils.h"
18 #include "nsIContent.h"
19 #include "mozilla/dom/Document.h"
21 #include "nsINodeList.h"
22 #include "nsGkAtoms.h"
23 #include "nsContentUtils.h"
24 #include "nsFrameSelection.h"
25 #include "nsLayoutUtils.h"
26 #include "nsTextFrame.h"
27 #include "nsContainerFrame.h"
28 #include "mozilla/Assertions.h"
29 #include "mozilla/CheckedInt.h"
30 #include "mozilla/ContentIterator.h"
31 #include "mozilla/dom/CharacterData.h"
32 #include "mozilla/dom/ChildIterator.h"
33 #include "mozilla/dom/DOMRect.h"
34 #include "mozilla/dom/DOMStringList.h"
35 #include "mozilla/dom/DocumentFragment.h"
36 #include "mozilla/dom/DocumentType.h"
37 #include "mozilla/dom/RangeBinding.h"
38 #include "mozilla/dom/Selection.h"
39 #include "mozilla/dom/Text.h"
40 #include "mozilla/dom/TrustedTypeUtils.h"
41 #include "mozilla/dom/TrustedTypesConstants.h"
42 #include "mozilla/Logging.h"
43 #include "mozilla/Maybe.h"
44 #include "mozilla/PresShell.h"
45 #include "mozilla/RangeUtils.h"
46 #include "mozilla/Telemetry.h"
47 #include "mozilla/ToString.h"
48 #include "mozilla/UniquePtr.h"
49 #include "mozilla/Likely.h"
50 #include "nsCSSFrameConstructor.h"
51 #include "nsStyleStruct.h"
52 #include "nsStyleStructInlines.h"
53 #include "nsComputedDOMStyle.h"
54 #include "mozilla/dom/InspectorFontFace.h"
57 # include "nsAccessibilityService.h"
61 extern LazyLogModule sSelectionAPILog
;
62 extern void LogStackForSelectionAPI();
64 template <typename SPT
, typename SRT
, typename EPT
, typename ERT
>
65 static void LogSelectionAPI(const dom::Selection
* aSelection
,
66 const char* aFuncName
, const char* aArgName1
,
67 const RangeBoundaryBase
<SPT
, SRT
>& aBoundary1
,
68 const char* aArgName2
,
69 const RangeBoundaryBase
<EPT
, ERT
>& aBoundary2
,
70 const char* aArgName3
, bool aBoolArg
) {
71 if (aBoundary1
== aBoundary2
) {
72 MOZ_LOG(sSelectionAPILog
, LogLevel::Info
,
73 ("%p nsRange::%s(%s=%s=%s, %s=%s)", aSelection
, aFuncName
,
74 aArgName1
, aArgName2
, ToString(aBoundary1
).c_str(), aArgName3
,
75 aBoolArg
? "true" : "false"));
78 sSelectionAPILog
, LogLevel::Info
,
79 ("%p nsRange::%s(%s=%s, %s=%s, %s=%s)", aSelection
, aFuncName
,
80 aArgName1
, ToString(aBoundary1
).c_str(), aArgName2
,
81 ToString(aBoundary2
).c_str(), aArgName3
, aBoolArg
? "true" : "false"));
84 } // namespace mozilla
86 using namespace mozilla
;
87 using namespace mozilla::dom
;
89 template already_AddRefed
<nsRange
> nsRange::Create(
90 const RangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
,
92 template already_AddRefed
<nsRange
> nsRange::Create(
93 const RangeBoundary
& aStartBoundary
, const RawRangeBoundary
& aEndBoundary
,
95 template already_AddRefed
<nsRange
> nsRange::Create(
96 const RawRangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
,
98 template already_AddRefed
<nsRange
> nsRange::Create(
99 const RawRangeBoundary
& aStartBoundary
,
100 const RawRangeBoundary
& aEndBoundary
, ErrorResult
& aRv
);
102 template nsresult
nsRange::SetStartAndEnd(const RangeBoundary
& aStartBoundary
,
103 const RangeBoundary
& aEndBoundary
);
104 template nsresult
nsRange::SetStartAndEnd(const RangeBoundary
& aStartBoundary
,
105 const RawRangeBoundary
& aEndBoundary
);
106 template nsresult
nsRange::SetStartAndEnd(
107 const RawRangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
);
108 template nsresult
nsRange::SetStartAndEnd(
109 const RawRangeBoundary
& aStartBoundary
,
110 const RawRangeBoundary
& aEndBoundary
);
112 template void nsRange::DoSetRange(const RangeBoundary
& aStartBoundary
,
113 const RangeBoundary
& aEndBoundary
,
114 nsINode
* aRootNode
, bool aNotInsertedYet
,
115 RangeBehaviour aRangeBehaviour
);
116 template void nsRange::DoSetRange(const RangeBoundary
& aStartBoundary
,
117 const RawRangeBoundary
& aEndBoundary
,
118 nsINode
* aRootNode
, bool aNotInsertedYet
,
119 RangeBehaviour aRangeBehaviour
);
120 template void nsRange::DoSetRange(const RawRangeBoundary
& aStartBoundary
,
121 const RangeBoundary
& aEndBoundary
,
122 nsINode
* aRootNode
, bool aNotInsertedYet
,
123 RangeBehaviour aRangeBehaviour
);
124 template void nsRange::DoSetRange(const RawRangeBoundary
& aStartBoundary
,
125 const RawRangeBoundary
& aEndBoundary
,
126 nsINode
* aRootNode
, bool aNotInsertedYet
,
127 RangeBehaviour aRangeBehaviour
);
129 template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
130 const RangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
);
131 template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
132 const RangeBoundary
& aStartBoundary
, const RawRangeBoundary
& aEndBoundary
);
133 template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
134 const RawRangeBoundary
& aStartBoundary
, const RangeBoundary
& aEndBoundary
);
135 template void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
136 const RawRangeBoundary
& aStartBoundary
,
137 const RawRangeBoundary
& aEndBoundary
);
139 JSObject
* nsRange::WrapObject(JSContext
* aCx
,
140 JS::Handle
<JSObject
*> aGivenProto
) {
141 return Range_Binding::Wrap(aCx
, this, aGivenProto
);
144 DocGroup
* nsRange::GetDocGroup() const {
145 return mOwner
? mOwner
->GetDocGroup() : nullptr;
148 /******************************************************
149 * stack based utility class for managing monitor
150 ******************************************************/
152 static void InvalidateAllFrames(nsINode
* aNode
) {
153 MOZ_ASSERT(aNode
, "bad arg");
155 nsIFrame
* frame
= nullptr;
156 switch (aNode
->NodeType()) {
157 case nsINode::TEXT_NODE
:
158 case nsINode::ELEMENT_NODE
: {
159 nsIContent
* content
= static_cast<nsIContent
*>(aNode
);
160 frame
= content
->GetPrimaryFrame();
163 case nsINode::DOCUMENT_NODE
: {
164 Document
* doc
= static_cast<Document
*>(aNode
);
165 PresShell
* presShell
= doc
? doc
->GetPresShell() : nullptr;
166 frame
= presShell
? presShell
->GetRootFrame() : nullptr;
170 for (nsIFrame
* f
= frame
; f
; f
= f
->GetNextContinuation()) {
171 f
->InvalidateFrameSubtree();
175 /******************************************************
176 * constructor/destructor
177 ******************************************************/
179 nsTArray
<RefPtr
<nsRange
>>* nsRange::sCachedRanges
= nullptr;
181 nsRange::~nsRange() {
182 NS_ASSERTION(!IsInAnySelection(), "deleting nsRange that is in use");
184 // we want the side effects (releases and list removals)
185 DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
188 nsRange::nsRange(nsINode
* aNode
)
189 : AbstractRange(aNode
, /* aIsDynamicRange = */ true),
190 mNextStartRef(nullptr),
191 mNextEndRef(nullptr) {
192 // printf("Size of nsRange: %zu\n", sizeof(nsRange));
194 static_assert(sizeof(nsRange
) <= 248,
195 "nsRange size shouldn't be increased as far as possible");
199 already_AddRefed
<nsRange
> nsRange::Create(nsINode
* aNode
) {
201 if (!sCachedRanges
|| sCachedRanges
->IsEmpty()) {
202 return do_AddRef(new nsRange(aNode
));
204 RefPtr
<nsRange
> range
= sCachedRanges
->PopLastElement().forget();
206 return range
.forget();
210 template <typename SPT
, typename SRT
, typename EPT
, typename ERT
>
211 already_AddRefed
<nsRange
> nsRange::Create(
212 const RangeBoundaryBase
<SPT
, SRT
>& aStartBoundary
,
213 const RangeBoundaryBase
<EPT
, ERT
>& aEndBoundary
, ErrorResult
& aRv
) {
214 // If we fail to initialize the range a lot, nsRange should have a static
215 // initializer since the allocation cost is not cheap in hot path.
216 RefPtr
<nsRange
> range
= nsRange::Create(aStartBoundary
.Container());
217 aRv
= range
->SetStartAndEnd(aStartBoundary
, aEndBoundary
);
218 if (NS_WARN_IF(aRv
.Failed())) {
221 return range
.forget();
225 * When a new boundary is given to a nsRange, compare its position with other
226 * existing boundaries to see if we need to collapse the end points.
228 * aRange: The nsRange that aNewBoundary is being set to.
229 * aNewRoot: The shadow-including root of the container of aNewBoundary
230 * aNewBoundary: The new boundary
231 * aIsSetStart: true if GetRangeBehaviour is called by nsRange::SetStart,
233 * aAllowCrossShadowBoundary: Indicates whether the boundaries allowed to cross
234 * shadow boundary or not
236 static RangeBehaviour
GetRangeBehaviour(
237 const nsRange
* aRange
, const nsINode
* aNewRoot
,
238 const RawRangeBoundary
& aNewBoundary
, const bool aIsSetStart
,
239 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
240 if (!aRange
->IsPositioned()) {
241 return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
;
244 MOZ_ASSERT(aRange
->GetRoot());
246 if (aNewRoot
!= aRange
->GetRoot()) {
247 // Boundaries are in different document (or not connected), so collapse
248 // the both the default range and the crossBoundaryRange range.
249 if (aNewRoot
->GetComposedDoc() != aRange
->GetRoot()->GetComposedDoc()) {
250 return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
;
253 // Always collapse both ranges if the one of the roots is an UA widget
254 // regardless whether the boundaries are allowed to cross shadow boundary
256 if (AbstractRange::IsRootUAWidget(aNewRoot
) ||
257 AbstractRange::IsRootUAWidget(aRange
->GetRoot())) {
258 return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
;
261 if (const CrossShadowBoundaryRange
* crossShadowBoundaryRange
=
262 aRange
->GetCrossShadowBoundaryRange()) {
263 // Check if the existing-other-side boundary in
264 // aRange::mCrossShadowBoundaryRange has the same root
265 // as aNewRoot. If this is the case, it means default range
266 // is good enough to represent this range, so that we can
267 // merge the cross-shadow-boundary range and the default range.
268 const RangeBoundary
& otherSideExistingBoundary
=
269 aIsSetStart
? crossShadowBoundaryRange
->EndRef()
270 : crossShadowBoundaryRange
->StartRef();
271 const nsINode
* otherSideRoot
=
272 RangeUtils::ComputeRootNode(otherSideExistingBoundary
.Container());
273 if (aNewRoot
== otherSideRoot
) {
274 return RangeBehaviour::MergeDefaultRangeAndCrossShadowBoundaryRanges
;
278 // Different root, but same document. So we only collapse the
279 // default range if boundaries are allowed to cross shadow boundary.
280 return aAllowCrossShadowBoundary
== AllowRangeCrossShadowBoundary::Yes
281 ? RangeBehaviour::CollapseDefaultRange
283 CollapseDefaultRangeAndCrossShadowBoundaryRanges
;
286 const RangeBoundary
& otherSideExistingBoundary
=
287 aIsSetStart
? aRange
->EndRef() : aRange
->StartRef();
289 // Both bondaries are in the same root, now check for their position
290 const Maybe
<int32_t> order
=
291 aIsSetStart
? nsContentUtils::ComparePoints(aNewBoundary
,
292 otherSideExistingBoundary
)
293 : nsContentUtils::ComparePoints(otherSideExistingBoundary
,
298 // aNewBoundary is at a valid position.
300 // If aIsSetStart is true, this means
301 // aNewBoundary <= otherSideExistingBoundary which is
302 // good because aNewBoundary intends to be the start.
304 // If aIsSetStart is false, this means
305 // otherSideExistingBoundary <= aNewBoundary which is good because
306 // aNewBoundary intends to be the end.
308 // So no collapse for above cases.
309 return RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges
;
312 if (!aRange
->MayCrossShadowBoundary() ||
313 aAllowCrossShadowBoundary
== AllowRangeCrossShadowBoundary::No
) {
314 return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
;
317 const RangeBoundary
& otherSideExistingCrossShadowBoundaryBoundary
=
318 aIsSetStart
? aRange
->MayCrossShadowBoundaryEndRef()
319 : aRange
->MayCrossShadowBoundaryStartRef();
321 // Please see the comment for (*order != 1) to see what "valid" means.
323 // We reach to this line when (*order == 1), it means aNewBoundary is
324 // at an invalid position, so we need to collapse aNewBoundary with
325 // otherSideExistingBoundary. However, it's possible that aNewBoundary
326 // is valid with the otherSideExistingCrossShadowBoundaryBoundary.
327 const Maybe
<int32_t> withCrossShadowBoundaryOrder
=
329 ? nsContentUtils::ComparePoints(
330 aNewBoundary
, otherSideExistingCrossShadowBoundaryBoundary
)
331 : nsContentUtils::ComparePoints(
332 otherSideExistingCrossShadowBoundaryBoundary
, aNewBoundary
);
334 // Valid to the cross boundary boundary.
335 if (withCrossShadowBoundaryOrder
&& *withCrossShadowBoundaryOrder
!= 1) {
336 return RangeBehaviour::CollapseDefaultRange
;
339 // Not valid to both existing boundaries.
340 return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
;
343 MOZ_ASSERT_UNREACHABLE();
344 return RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
;
346 /******************************************************
348 ******************************************************/
350 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsRange
)
351 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
352 nsRange
, DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr),
353 MaybeInterruptLastRelease())
355 // QueryInterface implementation for nsRange
356 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsRange
)
357 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver
)
358 NS_INTERFACE_MAP_END_INHERITING(AbstractRange
)
360 NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange
)
362 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsRange
, AbstractRange
)
363 // `Reset()` unlinks `mStart`, `mEnd` and `mRoot`.
364 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrossShadowBoundaryRange
);
366 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
368 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsRange
, AbstractRange
)
369 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot
)
370 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrossShadowBoundaryRange
);
371 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
373 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsRange
, AbstractRange
)
374 NS_IMPL_CYCLE_COLLECTION_TRACE_END
376 bool nsRange::MaybeInterruptLastRelease() {
377 bool interrupt
= AbstractRange::MaybeCacheToReuse(*this);
378 ResetCrossShadowBoundaryRange();
379 MOZ_ASSERT(!interrupt
|| IsCleared());
383 void nsRange::AdjustNextRefsOnCharacterDataSplit(
384 const nsIContent
& aContent
, const CharacterDataChangeInfo
& aInfo
) {
385 // If the splitted text node is immediately before a range boundary point
386 // that refers to a child index (i.e. its parent is the boundary container)
387 // then we need to adjust the corresponding boundary to account for the new
388 // text node that will be inserted. However, because the new sibling hasn't
389 // been inserted yet, that would result in an invalid boundary. Therefore,
390 // we store the new child in mNext*Ref to make sure we adjust the boundary
391 // in the next ContentInserted or ContentAppended call.
392 nsINode
* parentNode
= aContent
.GetParentNode();
393 if (parentNode
== mEnd
.Container()) {
394 if (&aContent
== mEnd
.Ref()) {
395 MOZ_ASSERT(aInfo
.mDetails
->mNextSibling
);
396 mNextEndRef
= aInfo
.mDetails
->mNextSibling
;
400 if (parentNode
== mStart
.Container()) {
401 if (&aContent
== mStart
.Ref()) {
402 MOZ_ASSERT(aInfo
.mDetails
->mNextSibling
);
403 mNextStartRef
= aInfo
.mDetails
->mNextSibling
;
408 nsRange::RangeBoundariesAndRoot
409 nsRange::DetermineNewRangeBoundariesAndRootOnCharacterDataMerge(
410 nsIContent
* aContent
, const CharacterDataChangeInfo
& aInfo
) const {
411 RawRangeBoundary newStart
;
412 RawRangeBoundary newEnd
;
413 nsINode
* newRoot
= nullptr;
415 // normalize(), aInfo.mDetails->mNextSibling is the merged text node
416 // that will be removed
417 nsIContent
* removed
= aInfo
.mDetails
->mNextSibling
;
418 if (removed
== mStart
.Container()) {
419 CheckedUint32 newStartOffset
{
420 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
)};
421 newStartOffset
+= aInfo
.mChangeStart
;
423 // newStartOffset.isValid() isn't checked explicitly here, because
424 // newStartOffset.value() contains an assertion.
425 newStart
= {aContent
, newStartOffset
.value()};
426 if (MOZ_UNLIKELY(removed
== mRoot
)) {
427 newRoot
= RangeUtils::ComputeRootNode(newStart
.Container());
430 if (removed
== mEnd
.Container()) {
431 CheckedUint32 newEndOffset
{
432 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
)};
433 newEndOffset
+= aInfo
.mChangeStart
;
435 // newEndOffset.isValid() isn't checked explicitly here, because
436 // newEndOffset.value() contains an assertion.
437 newEnd
= {aContent
, newEndOffset
.value()};
438 if (MOZ_UNLIKELY(removed
== mRoot
)) {
439 newRoot
= {RangeUtils::ComputeRootNode(newEnd
.Container())};
442 // When the removed text node's parent is one of our boundary nodes we may
443 // need to adjust the offset to account for the removed node. However,
444 // there will also be a ContentRemoved notification later so the only cases
445 // we need to handle here is when the removed node is the text node after
446 // the boundary. (The m*Offset > 0 check is an optimization - a boundary
447 // point before the first child is never affected by normalize().)
448 nsINode
* parentNode
= aContent
->GetParentNode();
449 if (parentNode
== mStart
.Container() &&
450 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) > 0 &&
451 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) <
452 parentNode
->GetChildCount() &&
453 removed
== mStart
.GetChildAtOffset()) {
454 newStart
= {aContent
, aInfo
.mChangeStart
};
456 if (parentNode
== mEnd
.Container() &&
457 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) > 0 &&
458 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) <
459 parentNode
->GetChildCount() &&
460 removed
== mEnd
.GetChildAtOffset()) {
461 newEnd
= {aContent
, aInfo
.mChangeEnd
};
464 return {newStart
, newEnd
, newRoot
};
467 /******************************************************
468 * nsIMutationObserver implementation
469 ******************************************************/
470 void nsRange::CharacterDataChanged(nsIContent
* aContent
,
471 const CharacterDataChangeInfo
& aInfo
) {
472 MOZ_ASSERT(aContent
);
473 MOZ_ASSERT(mIsPositioned
);
474 MOZ_ASSERT(!mNextEndRef
);
475 MOZ_ASSERT(!mNextStartRef
);
477 nsINode
* newRoot
= nullptr;
478 RawRangeBoundary newStart
;
479 RawRangeBoundary newEnd
;
481 if (aInfo
.mDetails
&&
482 aInfo
.mDetails
->mType
== CharacterDataChangeInfo::Details::eSplit
) {
483 AdjustNextRefsOnCharacterDataSplit(*aContent
, aInfo
);
486 // If the changed node contains our start boundary and the change starts
487 // before the boundary we'll need to adjust the offset.
488 if (aContent
== mStart
.Container() &&
490 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
)) {
491 if (aInfo
.mDetails
) {
492 // splitText(), aInfo->mDetails->mNextSibling is the new text node
494 aInfo
.mDetails
->mType
== CharacterDataChangeInfo::Details::eSplit
,
495 "only a split can start before the end");
497 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) <=
498 aInfo
.mChangeEnd
+ 1,
499 "mStart.Offset() is beyond the end of this node");
500 const uint32_t newStartOffset
=
501 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) -
503 newStart
= {aInfo
.mDetails
->mNextSibling
, newStartOffset
};
504 if (MOZ_UNLIKELY(aContent
== mRoot
)) {
505 newRoot
= RangeUtils::ComputeRootNode(newStart
.Container());
508 bool isCommonAncestor
=
509 IsInAnySelection() && mStart
.Container() == mEnd
.Container();
510 if (isCommonAncestor
) {
511 MOZ_DIAGNOSTIC_ASSERT(mStart
.Container() ==
512 mRegisteredClosestCommonInclusiveAncestor
);
513 UnregisterClosestCommonInclusiveAncestor();
514 RegisterClosestCommonInclusiveAncestor(newStart
.Container());
516 if (mStart
.Container()
517 ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
519 ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
522 newStart
= ComputeNewBoundaryWhenBoundaryInsideChangedText(
523 aInfo
, mStart
.AsRaw());
527 // Do the same thing for the end boundary, except for splitText of a node
528 // with no parent then only switch to the new node if the start boundary
529 // did so too (otherwise the range would end up with disconnected nodes).
530 if (aContent
== mEnd
.Container() &&
532 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
)) {
533 if (aInfo
.mDetails
&& (aContent
->GetParentNode() || newStart
.Container())) {
534 // splitText(), aInfo.mDetails->mNextSibling is the new text node
536 aInfo
.mDetails
->mType
== CharacterDataChangeInfo::Details::eSplit
,
537 "only a split can start before the end");
539 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) <=
540 aInfo
.mChangeEnd
+ 1,
541 "mEnd.Offset() is beyond the end of this node");
543 const uint32_t newEndOffset
{
544 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) -
546 newEnd
= {aInfo
.mDetails
->mNextSibling
, newEndOffset
};
548 bool isCommonAncestor
=
549 IsInAnySelection() && mStart
.Container() == mEnd
.Container();
550 if (isCommonAncestor
&& !newStart
.Container()) {
551 MOZ_DIAGNOSTIC_ASSERT(mStart
.Container() ==
552 mRegisteredClosestCommonInclusiveAncestor
);
553 // The split occurs inside the range.
554 UnregisterClosestCommonInclusiveAncestor();
555 RegisterClosestCommonInclusiveAncestor(
556 mStart
.Container()->GetParentNode());
558 ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
561 ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
563 ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
567 ComputeNewBoundaryWhenBoundaryInsideChangedText(aInfo
, mEnd
.AsRaw());
571 if (aInfo
.mDetails
&&
572 aInfo
.mDetails
->mType
== CharacterDataChangeInfo::Details::eMerge
) {
573 MOZ_ASSERT(!newStart
.IsSet());
574 MOZ_ASSERT(!newEnd
.IsSet());
576 RangeBoundariesAndRoot rangeBoundariesAndRoot
=
577 DetermineNewRangeBoundariesAndRootOnCharacterDataMerge(aContent
, aInfo
);
579 newStart
= rangeBoundariesAndRoot
.mStart
;
580 newEnd
= rangeBoundariesAndRoot
.mEnd
;
581 newRoot
= rangeBoundariesAndRoot
.mRoot
;
584 if (newStart
.IsSet() || newEnd
.IsSet()) {
585 if (!newStart
.IsSet()) {
586 newStart
.CopyFrom(mStart
, RangeBoundaryIsMutationObserved::Yes
);
588 if (!newEnd
.IsSet()) {
589 newEnd
.CopyFrom(mEnd
, RangeBoundaryIsMutationObserved::Yes
);
591 DoSetRange(newStart
, newEnd
, newRoot
? newRoot
: mRoot
.get(),
592 !newEnd
.Container()->GetParentNode() ||
593 !newStart
.Container()->GetParentNode());
595 nsRange::AssertIfMismatchRootAndRangeBoundaries(
597 (mStart
.IsSet() && !mStart
.Container()->GetParentNode()) ||
598 (mEnd
.IsSet() && !mEnd
.Container()->GetParentNode()));
602 void nsRange::ContentAppended(nsIContent
* aFirstNewContent
) {
603 MOZ_ASSERT(mIsPositioned
);
605 nsINode
* container
= aFirstNewContent
->GetParentNode();
606 MOZ_ASSERT(container
);
607 if (container
->IsMaybeSelected() && IsInAnySelection()) {
608 nsINode
* child
= aFirstNewContent
;
611 ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
612 MarkDescendants(*child
);
614 ->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
616 child
= child
->GetNextSibling();
620 if (mNextStartRef
|| mNextEndRef
) {
621 // A splitText has occurred, if any mNext*Ref was set, we need to adjust
622 // the range boundaries.
624 mStart
= {mStart
.Container(), mNextStartRef
};
625 MOZ_ASSERT(mNextStartRef
== aFirstNewContent
);
626 mNextStartRef
= nullptr;
629 mEnd
= {mEnd
.Container(), mNextEndRef
};
630 MOZ_ASSERT(mNextEndRef
== aFirstNewContent
);
631 mNextEndRef
= nullptr;
633 DoSetRange(mStart
, mEnd
, mRoot
, true);
635 nsRange::AssertIfMismatchRootAndRangeBoundaries(mStart
, mEnd
, mRoot
);
639 void nsRange::ContentInserted(nsIContent
* aChild
) {
640 MOZ_ASSERT(mIsPositioned
);
642 bool updateBoundaries
= false;
643 nsINode
* container
= aChild
->GetParentNode();
644 MOZ_ASSERT(container
);
645 RawRangeBoundary
newStart(mStart
, RangeBoundaryIsMutationObserved::Yes
);
646 RawRangeBoundary
newEnd(mEnd
, RangeBoundaryIsMutationObserved::Yes
);
647 MOZ_ASSERT(aChild
->GetParentNode() == container
);
649 // Invalidate boundary offsets if a child that may have moved them was
651 if (container
== mStart
.Container()) {
652 newStart
.InvalidateOffset();
653 updateBoundaries
= true;
656 if (container
== mEnd
.Container()) {
657 newEnd
.InvalidateOffset();
658 updateBoundaries
= true;
661 if (container
->IsMaybeSelected() &&
663 ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
664 MarkDescendants(*aChild
);
665 aChild
->SetDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
668 if (mNextStartRef
|| mNextEndRef
) {
670 newStart
= {mStart
.Container(), mNextStartRef
};
671 MOZ_ASSERT(mNextStartRef
== aChild
);
672 mNextStartRef
= nullptr;
675 newEnd
= {mEnd
.Container(), mNextEndRef
};
676 MOZ_ASSERT(mNextEndRef
== aChild
);
677 mNextEndRef
= nullptr;
680 updateBoundaries
= true;
683 if (updateBoundaries
) {
684 DoSetRange(newStart
, newEnd
, mRoot
);
686 nsRange::AssertIfMismatchRootAndRangeBoundaries(mStart
, mEnd
, mRoot
);
690 void nsRange::ContentWillBeRemoved(nsIContent
* aChild
,
691 const BatchRemovalState
*) {
692 MOZ_ASSERT(mIsPositioned
);
694 nsINode
* container
= aChild
->GetParentNode();
695 MOZ_ASSERT(container
);
697 nsINode
* startContainer
= mStart
.Container();
698 nsINode
* endContainer
= mEnd
.Container();
700 RawRangeBoundary newStart
;
701 RawRangeBoundary newEnd
;
702 Maybe
<bool> gravitateStart
;
705 // Adjust position if a sibling was removed...
706 if (container
== startContainer
) {
707 // We're only interested if our boundary reference was removed, otherwise
708 // we can just invalidate the offset.
709 if (aChild
== mStart
.Ref()) {
710 newStart
= {container
, aChild
->GetPreviousSibling()};
712 newStart
.CopyFrom(mStart
, RangeBoundaryIsMutationObserved::Yes
);
713 newStart
.InvalidateOffset();
716 gravitateStart
= Some(startContainer
->IsInclusiveDescendantOf(aChild
));
717 if (gravitateStart
.value()) {
718 newStart
= {container
, aChild
->GetPreviousSibling()};
722 // Do same thing for end boundry.
723 if (container
== endContainer
) {
724 if (aChild
== mEnd
.Ref()) {
725 newEnd
= {container
, aChild
->GetPreviousSibling()};
727 newEnd
.CopyFrom(mEnd
, RangeBoundaryIsMutationObserved::Yes
);
728 newEnd
.InvalidateOffset();
731 if (startContainer
== endContainer
&& gravitateStart
.isSome()) {
732 gravitateEnd
= gravitateStart
.value();
734 gravitateEnd
= endContainer
->IsInclusiveDescendantOf(aChild
);
737 newEnd
= {container
, aChild
->GetPreviousSibling()};
741 bool newStartIsSet
= newStart
.IsSet();
742 bool newEndIsSet
= newEnd
.IsSet();
743 if (newStartIsSet
|| newEndIsSet
) {
745 newStartIsSet
? newStart
: mStart
.AsRaw(),
746 newEndIsSet
? newEnd
: mEnd
.AsRaw(), mRoot
, false,
747 // CrossShadowBoundaryRange mutates content
748 // removal fot itself, so no need for nsRange to do anything with it.
749 RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges
);
751 nsRange::AssertIfMismatchRootAndRangeBoundaries(mStart
, mEnd
, mRoot
);
754 MOZ_ASSERT(mStart
.Ref() != aChild
);
755 MOZ_ASSERT(mEnd
.Ref() != aChild
);
757 if (container
->IsMaybeSelected() &&
759 ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) {
761 ->ClearDescendantOfClosestCommonInclusiveAncestorForRangeInSelection();
762 UnmarkDescendants(*aChild
);
766 void nsRange::ParentChainChanged(nsIContent
* aContent
) {
767 NS_ASSERTION(mRoot
== aContent
, "Wrong ParentChainChanged notification?");
768 nsINode
* newRoot
= RangeUtils::ComputeRootNode(mStart
.Container());
769 NS_ASSERTION(newRoot
, "No valid boundary or root found!");
770 if (newRoot
!= RangeUtils::ComputeRootNode(mEnd
.Container())) {
771 // Sometimes ordering involved in cycle collection can lead to our
772 // start parent and/or end parent being disconnected from our root
773 // without our getting a ContentRemoved notification.
774 // See bug 846096 for more details.
775 NS_ASSERTION(mEnd
.Container()->IsInNativeAnonymousSubtree(),
776 "This special case should happen only with "
777 "native-anonymous content");
778 // When that happens, bail out and set pointers to null; since we're
779 // in cycle collection and unreachable it shouldn't matter.
783 // This is safe without holding a strong ref to self as long as the change
784 // of mRoot is the last thing in DoSetRange.
785 DoSetRange(mStart
, mEnd
, newRoot
);
788 bool nsRange::IsShadowIncludingInclusiveDescendantOfCrossBoundaryRangeAncestor(
789 const nsINode
& aContainer
) const {
790 MOZ_ASSERT(mCrossShadowBoundaryRange
&&
791 mCrossShadowBoundaryRange
->GetCommonAncestor());
792 return aContainer
.IsShadowIncludingInclusiveDescendantOf(
793 mCrossShadowBoundaryRange
->GetCommonAncestor());
796 bool nsRange::IsPointComparableToRange(const nsINode
& aContainer
,
798 bool aAllowCrossShadowBoundary
,
799 ErrorResult
& aRv
) const {
800 // our range is in a good state?
801 if (!mIsPositioned
) {
802 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
806 const bool isContainerInRange
=
807 aContainer
.IsInclusiveDescendantOf(mRoot
) ||
808 (aAllowCrossShadowBoundary
&& mCrossShadowBoundaryRange
&&
809 IsShadowIncludingInclusiveDescendantOfCrossBoundaryRangeAncestor(
812 if (!isContainerInRange
) {
813 // TODO(emilio): Switch to ThrowWrongDocumentError, but IsPointInRange
814 // relies on the error code right now in order to suppress the exception.
815 aRv
.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR
);
819 auto chromeOnlyAccess
= mStart
.Container()->ChromeOnlyAccess();
820 NS_ASSERTION(chromeOnlyAccess
== mEnd
.Container()->ChromeOnlyAccess(),
821 "Start and end of a range must be either both native anonymous "
823 if (aContainer
.ChromeOnlyAccess() != chromeOnlyAccess
) {
824 aRv
.ThrowInvalidNodeTypeError(
825 "Trying to compare restricted with unrestricted nodes");
829 if (aContainer
.NodeType() == nsINode::DOCUMENT_TYPE_NODE
) {
830 aRv
.ThrowInvalidNodeTypeError("Trying to compare with a document");
834 if (aOffset
> aContainer
.Length()) {
835 aRv
.ThrowIndexSizeError("Offset is out of bounds");
842 bool nsRange::IsPointInRange(const nsINode
& aContainer
, uint32_t aOffset
,
844 bool aAllowCrossShadowBoundary
) const {
845 int16_t compareResult
=
846 ComparePoint(aContainer
, aOffset
, aRv
, aAllowCrossShadowBoundary
);
847 // If the node isn't in the range's document, it clearly isn't in the range.
848 if (aRv
.ErrorCodeIs(NS_ERROR_DOM_WRONG_DOCUMENT_ERR
)) {
849 aRv
.SuppressException();
853 return compareResult
== 0;
856 int16_t nsRange::ComparePoint(const nsINode
& aContainer
, uint32_t aOffset
,
858 bool aAllowCrossShadowBoundary
) const {
859 if (!IsPointComparableToRange(aContainer
, aOffset
, aAllowCrossShadowBoundary
,
864 const RawRangeBoundary point
{const_cast<nsINode
*>(&aContainer
), aOffset
};
866 MOZ_ASSERT(point
.IsSetAndValid());
868 if (Maybe
<int32_t> order
= nsContentUtils::ComparePoints(
869 point
, aAllowCrossShadowBoundary
? MayCrossShadowBoundaryStartRef()
871 order
&& *order
<= 0) {
872 return int16_t(*order
);
874 if (Maybe
<int32_t> order
= nsContentUtils::ComparePoints(
875 aAllowCrossShadowBoundary
? MayCrossShadowBoundaryEndRef() : EndRef(),
877 order
&& *order
== -1) {
883 bool nsRange::IntersectsNode(nsINode
& aNode
, ErrorResult
& aRv
) {
884 if (!mIsPositioned
) {
885 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
889 nsINode
* parent
= aNode
.GetParentNode();
891 // |parent| is null, so |node|'s root is |node| itself.
892 return GetRoot() == &aNode
;
895 const Maybe
<uint32_t> nodeIndex
= parent
->ComputeIndexOf(&aNode
);
896 if (nodeIndex
.isNothing()) {
900 if (!IsPointComparableToRange(*parent
, *nodeIndex
,
901 false /* aAllowCrossShadowBoundary */,
906 const Maybe
<int32_t> startOrder
= nsContentUtils::ComparePoints(
908 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
), parent
,
910 if (startOrder
&& (*startOrder
< 0)) {
911 const Maybe
<int32_t> endOrder
= nsContentUtils::ComparePoints(
912 parent
, *nodeIndex
, mEnd
.Container(),
913 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
));
914 return endOrder
&& (*endOrder
< 0);
920 void nsRange::NotifySelectionListenersAfterRangeSet() {
921 if (mSelections
.IsEmpty()) {
925 // Our internal code should not move focus with using this instance while
926 // it's calling Selection::NotifySelectionListeners() which may move focus
927 // or calls selection listeners. So, let's set mCalledByJS to false here
928 // since non-*JS() methods don't set it to false.
929 AutoCalledByJSRestore
calledByJSRestorer(*this);
932 // If this instance is not a proper range for selection, we need to remove
933 // this from selections.
934 const Document
* const docForSelf
=
935 mStart
.Container() ? mStart
.Container()->GetComposedDoc() : nullptr;
936 const nsFrameSelection
* const frameSelection
=
937 mSelections
[0]->GetFrameSelection();
938 const Document
* const docForSelection
=
939 frameSelection
&& frameSelection
->GetPresShell()
940 ? frameSelection
->GetPresShell()->GetDocument()
942 if (!IsPositioned() || docForSelf
!= docForSelection
) {
943 // XXX Why Selection::RemoveRangeAndUnselectFramesAndNotifyListeners() does
944 // not set whether the caller is JS or not?
945 if (IsPartOfOneSelectionOnly()) {
946 RefPtr
<Selection
> selection
= mSelections
[0].get();
947 selection
->RemoveRangeAndUnselectFramesAndNotifyListeners(*this,
950 nsTArray
<WeakPtr
<Selection
>> copiedSelections
= mSelections
.Clone();
951 for (const auto& weakSelection
: copiedSelections
) {
952 RefPtr
<Selection
> selection
= weakSelection
.get();
953 if (MOZ_LIKELY(selection
)) {
954 selection
->RemoveRangeAndUnselectFramesAndNotifyListeners(
955 *this, IgnoreErrors());
959 // FYI: NotifySelectionListeners() should be called by
960 // RemoveRangeAndUnselectFramesAndNotifyListeners() if it's required.
961 // Therefore, we need to do nothing anymore.
965 // Notify all Selections. This may modify the range,
966 // remove it from the selection, or the selection itself may have gone after
967 // the call. Also, new selections may be added.
968 // To ensure that listeners are notified for all *current* selections,
969 // create a copy of the list of selections and use that for iterating. This
970 // way selections can be added or removed safely during iteration.
971 // To save allocation cost, the copy is only created if there is more than
972 // one Selection present (which will barely ever be the case).
973 if (IsPartOfOneSelectionOnly()) {
974 RefPtr
<Selection
> selection
= mSelections
[0].get();
976 a11y::SelectionManager::SelectionRangeChanged(selection
->GetType(), *this);
978 selection
->NotifySelectionListeners(calledByJSRestorer
.SavedValue());
980 nsTArray
<WeakPtr
<Selection
>> copiedSelections
= mSelections
.Clone();
981 for (const auto& weakSelection
: copiedSelections
) {
982 RefPtr
<Selection
> selection
= weakSelection
.get();
983 if (MOZ_LIKELY(selection
)) {
985 a11y::SelectionManager::SelectionRangeChanged(selection
->GetType(),
988 selection
->NotifySelectionListeners(calledByJSRestorer
.SavedValue());
994 /******************************************************
995 * Private helper routines
996 ******************************************************/
999 template <typename SPT
, typename SRT
, typename EPT
, typename ERT
>
1000 void nsRange::AssertIfMismatchRootAndRangeBoundaries(
1001 const RangeBoundaryBase
<SPT
, SRT
>& aStartBoundary
,
1002 const RangeBoundaryBase
<EPT
, ERT
>& aEndBoundary
, const nsINode
* aRootNode
,
1003 bool aNotInsertedYet
/* = false */) {
1006 MOZ_ASSERT(!aStartBoundary
.IsSet());
1007 MOZ_ASSERT(!aEndBoundary
.IsSet());
1011 MOZ_ASSERT(aStartBoundary
.IsSet());
1012 MOZ_ASSERT(aEndBoundary
.IsSet());
1013 if (!aNotInsertedYet
) {
1014 // Compute temporary root for given range boundaries. If a range in native
1015 // anonymous subtree is being removed, tempRoot may return the fragment's
1016 // root content, but it shouldn't be used for new root node because the node
1017 // may be bound to the root element again.
1018 nsINode
* tempRoot
= RangeUtils::ComputeRootNode(aStartBoundary
.Container());
1019 // The new range should be in the temporary root node at least.
1020 MOZ_ASSERT(tempRoot
==
1021 RangeUtils::ComputeRootNode(aEndBoundary
.Container()));
1022 MOZ_ASSERT(aStartBoundary
.Container()->IsInclusiveDescendantOf(tempRoot
));
1023 MOZ_ASSERT(aEndBoundary
.Container()->IsInclusiveDescendantOf(tempRoot
));
1024 // If the new range is not disconnected or not in native anonymous subtree,
1025 // the temporary root must be same as the new root node. Otherwise,
1026 // aRootNode should be the parent of root of the NAC (e.g., `<input>` if the
1027 // range is in NAC under `<input>`), but tempRoot is now root content node
1028 // of the disconnected subtree (e.g., `<div>` element in `<input>` element).
1029 const bool tempRootIsDisconnectedNAC
=
1030 tempRoot
->IsInNativeAnonymousSubtree() && !tempRoot
->GetParentNode();
1031 MOZ_ASSERT_IF(!tempRootIsDisconnectedNAC
, tempRoot
== aRootNode
);
1033 MOZ_ASSERT(aRootNode
->IsDocument() || aRootNode
->IsAttr() ||
1034 aRootNode
->IsDocumentFragment() || aRootNode
->IsContent());
1035 #endif // #ifdef DEBUG
1038 // It's important that all setting of the range start/end points
1039 // go through this function, which will do all the right voodoo
1040 // for content notification of range ownership.
1041 // Calling DoSetRange with either parent argument null will collapse
1042 // the range to have both endpoints point to the other node
1043 template <typename SPT
, typename SRT
, typename EPT
, typename ERT
>
1045 DoSetRange(const RangeBoundaryBase
<SPT
, SRT
>& aStartBoundary
,
1046 const RangeBoundaryBase
<EPT
, ERT
>& aEndBoundary
,
1048 bool aNotInsertedYet
/* = false */, RangeBehaviour aRangeBehaviour
/* = CollapseDefaultRangeAndCrossShadowBoundaryRanges */) {
1049 mIsPositioned
= aStartBoundary
.IsSetAndValid() &&
1050 aEndBoundary
.IsSetAndValid() && aRootNode
;
1051 MOZ_ASSERT_IF(!mIsPositioned
, !aStartBoundary
.IsSet());
1052 MOZ_ASSERT_IF(!mIsPositioned
, !aEndBoundary
.IsSet());
1053 MOZ_ASSERT_IF(!mIsPositioned
, !aRootNode
);
1055 nsRange::AssertIfMismatchRootAndRangeBoundaries(aStartBoundary
, aEndBoundary
,
1056 aRootNode
, aNotInsertedYet
);
1058 if (mRoot
!= aRootNode
) {
1060 mRoot
->RemoveMutationObserver(this);
1063 aRootNode
->AddMutationObserver(this);
1066 bool checkCommonAncestor
=
1067 (mStart
.Container() != aStartBoundary
.Container() ||
1068 mEnd
.Container() != aEndBoundary
.Container()) &&
1069 IsInAnySelection() && !aNotInsertedYet
;
1071 // GetClosestCommonInclusiveAncestor is unreliable while we're unlinking
1072 // (could return null if our start/end have already been unlinked), so make
1073 // sure to not use it here to determine our "old" current ancestor.
1074 mStart
.CopyFrom(aStartBoundary
, RangeBoundaryIsMutationObserved::Yes
);
1075 mEnd
.CopyFrom(aEndBoundary
, RangeBoundaryIsMutationObserved::Yes
);
1077 if (aRangeBehaviour
==
1078 RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
) {
1079 ResetCrossShadowBoundaryRange();
1082 if (checkCommonAncestor
) {
1083 UpdateCommonAncestorIfNecessary();
1086 // This needs to be the last thing this function does, other than notifying
1087 // selection listeners. See comment in ParentChainChanged.
1088 if (mRoot
!= aRootNode
) {
1092 // Notify any selection listeners. This has to occur last because otherwise
1093 // the world could be observed by a selection listener while the range was in
1094 // an invalid state. So we run it off of a script runner to ensure it runs
1095 // after the mutation observers have finished running.
1096 if (!mSelections
.IsEmpty()) {
1097 if (MOZ_LOG_TEST(sSelectionAPILog
, LogLevel::Info
)) {
1098 for (const auto& selection
: mSelections
) {
1099 if (selection
&& selection
->Type() == SelectionType::eNormal
) {
1100 LogSelectionAPI(selection
, __FUNCTION__
, "aStartBoundary",
1101 aStartBoundary
, "aEndBoundary", aEndBoundary
,
1102 "aNotInsertedYet", aNotInsertedYet
);
1103 LogStackForSelectionAPI();
1107 nsContentUtils::AddScriptRunner(
1108 NewRunnableMethod("NotifySelectionListenersAfterRangeSet", this,
1109 &nsRange::NotifySelectionListenersAfterRangeSet
));
1113 void nsRange::Reset() {
1114 DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
1117 /******************************************************
1118 * public functionality
1119 ******************************************************/
1121 void nsRange::SetStartJS(nsINode
& aNode
, uint32_t aOffset
, ErrorResult
& aErr
) {
1122 AutoCalledByJSRestore
calledByJSRestorer(*this);
1124 SetStart(aNode
, aOffset
, aErr
);
1127 bool nsRange::CanAccess(const nsINode
& aNode
) const {
1128 if (nsContentUtils::LegacyIsCallerNativeCode()) {
1131 return nsContentUtils::CanCallerAccess(&aNode
);
1134 void nsRange::SetStart(
1135 nsINode
& aNode
, uint32_t aOffset
, ErrorResult
& aRv
,
1136 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
1137 if (!CanAccess(aNode
)) {
1138 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1142 AutoInvalidateSelection
atEndOfBlock(this);
1143 SetStart(RawRangeBoundary(&aNode
, aOffset
), aRv
, aAllowCrossShadowBoundary
);
1146 void nsRange::SetStart(
1147 const RawRangeBoundary
& aPoint
, ErrorResult
& aRv
,
1148 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
1149 nsINode
* newRoot
= RangeUtils::ComputeRootNode(aPoint
.Container());
1151 aRv
.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
);
1155 if (!aPoint
.IsSetAndValid()) {
1156 aRv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
1160 RangeBehaviour behaviour
=
1161 GetRangeBehaviour(this, newRoot
, aPoint
, true /* aIsSetStart= */,
1162 aAllowCrossShadowBoundary
);
1164 switch (behaviour
) {
1165 case RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges
:
1166 // EndRef(..) may be same as mStart or not, depends on
1167 // the value of mCrossShadowBoundaryRange->mEnd, We need to update
1168 // mCrossShadowBoundaryRange and the default boundaries separately
1169 if (aAllowCrossShadowBoundary
== AllowRangeCrossShadowBoundary::Yes
) {
1170 if (MayCrossShadowBoundaryEndRef() != mEnd
) {
1171 CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
1172 aPoint
, MayCrossShadowBoundaryEndRef());
1174 // The normal range is good enough for this case, just use that.
1175 ResetCrossShadowBoundaryRange();
1178 DoSetRange(aPoint
, mEnd
, mRoot
, false, behaviour
);
1180 case RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
:
1181 DoSetRange(aPoint
, aPoint
, newRoot
, false, behaviour
);
1183 case RangeBehaviour::CollapseDefaultRange
:
1184 MOZ_ASSERT(aAllowCrossShadowBoundary
==
1185 AllowRangeCrossShadowBoundary::Yes
);
1186 CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
1187 aPoint
, MayCrossShadowBoundaryEndRef());
1188 DoSetRange(aPoint
, aPoint
, newRoot
, false, behaviour
);
1190 case RangeBehaviour::MergeDefaultRangeAndCrossShadowBoundaryRanges
:
1191 DoSetRange(aPoint
, MayCrossShadowBoundaryEndRef(), newRoot
, false,
1193 ResetCrossShadowBoundaryRange();
1196 MOZ_ASSERT_UNREACHABLE();
1200 void nsRange::SetStartAllowCrossShadowBoundary(nsINode
& aNode
, uint32_t aOffset
,
1201 ErrorResult
& aErr
) {
1202 AutoCalledByJSRestore
calledByJSRestorer(*this);
1204 SetStart(aNode
, aOffset
, aErr
, AllowRangeCrossShadowBoundary::Yes
);
1207 void nsRange::SetStartBeforeJS(nsINode
& aNode
, ErrorResult
& aErr
) {
1208 AutoCalledByJSRestore
calledByJSRestorer(*this);
1210 SetStartBefore(aNode
, aErr
);
1213 void nsRange::SetStartBefore(
1214 nsINode
& aNode
, ErrorResult
& aRv
,
1215 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
1216 if (!CanAccess(aNode
)) {
1217 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1221 AutoInvalidateSelection
atEndOfBlock(this);
1222 // If the node is being removed from its parent, GetRawRangeBoundaryBefore()
1223 // returns unset instance. Then, SetStart() will throw
1224 // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
1225 SetStart(RangeUtils::GetRawRangeBoundaryBefore(&aNode
), aRv
,
1226 aAllowCrossShadowBoundary
);
1229 void nsRange::SetStartAfterJS(nsINode
& aNode
, ErrorResult
& aErr
) {
1230 AutoCalledByJSRestore
calledByJSRestorer(*this);
1232 SetStartAfter(aNode
, aErr
);
1235 void nsRange::SetStartAfter(nsINode
& aNode
, ErrorResult
& aRv
) {
1236 if (!CanAccess(aNode
)) {
1237 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1241 AutoInvalidateSelection
atEndOfBlock(this);
1242 // If the node is being removed from its parent, GetRawRangeBoundaryAfter()
1243 // returns unset instance. Then, SetStart() will throw
1244 // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
1245 SetStart(RangeUtils::GetRawRangeBoundaryAfter(&aNode
), aRv
);
1248 void nsRange::SetEndJS(nsINode
& aNode
, uint32_t aOffset
, ErrorResult
& aErr
) {
1249 AutoCalledByJSRestore
calledByJSRestorer(*this);
1251 SetEnd(aNode
, aOffset
, aErr
);
1254 void nsRange::SetEnd(nsINode
& aNode
, uint32_t aOffset
, ErrorResult
& aRv
,
1255 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
1256 if (!CanAccess(aNode
)) {
1257 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1260 AutoInvalidateSelection
atEndOfBlock(this);
1261 SetEnd(RawRangeBoundary(&aNode
, aOffset
), aRv
, aAllowCrossShadowBoundary
);
1264 void nsRange::SetEnd(const RawRangeBoundary
& aPoint
, ErrorResult
& aRv
,
1265 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
1266 nsINode
* newRoot
= RangeUtils::ComputeRootNode(aPoint
.Container());
1268 aRv
.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
);
1272 if (!aPoint
.IsSetAndValid()) {
1273 aRv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
1277 RangeBehaviour policy
=
1278 GetRangeBehaviour(this, newRoot
, aPoint
, false /* aIsStartStart */,
1279 aAllowCrossShadowBoundary
);
1282 case RangeBehaviour::KeepDefaultRangeAndCrossShadowBoundaryRanges
:
1283 // StartRef(..) may be same as mStart or not, depends on
1284 // the value of mCrossShadowBoundaryRange->mStart, so we need to update
1285 // mCrossShadowBoundaryRange and the default boundaries separately
1286 if (aAllowCrossShadowBoundary
== AllowRangeCrossShadowBoundary::Yes
) {
1287 if (MayCrossShadowBoundaryStartRef() != mStart
) {
1288 CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
1289 MayCrossShadowBoundaryStartRef(), aPoint
);
1291 // The normal range is good enough for this case, just use that.
1292 ResetCrossShadowBoundaryRange();
1295 DoSetRange(mStart
, aPoint
, mRoot
, false, policy
);
1297 case RangeBehaviour::CollapseDefaultRangeAndCrossShadowBoundaryRanges
:
1298 DoSetRange(aPoint
, aPoint
, newRoot
, false, policy
);
1300 case RangeBehaviour::CollapseDefaultRange
:
1301 MOZ_ASSERT(aAllowCrossShadowBoundary
==
1302 AllowRangeCrossShadowBoundary::Yes
);
1303 CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
1304 MayCrossShadowBoundaryStartRef(), aPoint
);
1305 DoSetRange(aPoint
, aPoint
, newRoot
, false, policy
);
1307 case RangeBehaviour::MergeDefaultRangeAndCrossShadowBoundaryRanges
:
1308 DoSetRange(MayCrossShadowBoundaryStartRef(), aPoint
, newRoot
, false,
1310 ResetCrossShadowBoundaryRange();
1313 MOZ_ASSERT_UNREACHABLE();
1317 void nsRange::SetEndAllowCrossShadowBoundary(nsINode
& aNode
, uint32_t aOffset
,
1318 ErrorResult
& aErr
) {
1319 AutoCalledByJSRestore
calledByJSRestorer(*this);
1321 SetEnd(aNode
, aOffset
, aErr
,
1322 AllowRangeCrossShadowBoundary::Yes
/* aAllowCrossShadowBoundary */);
1325 void nsRange::SelectNodesInContainer(nsINode
* aContainer
,
1326 nsIContent
* aStartContent
,
1327 nsIContent
* aEndContent
) {
1328 MOZ_ASSERT(aContainer
);
1329 MOZ_ASSERT(aContainer
->ComputeIndexOf(aStartContent
).valueOr(0) <=
1330 aContainer
->ComputeIndexOf(aEndContent
).valueOr(0));
1331 MOZ_ASSERT(aStartContent
&&
1332 aContainer
->ComputeIndexOf(aStartContent
).isSome());
1333 MOZ_ASSERT(aEndContent
&& aContainer
->ComputeIndexOf(aEndContent
).isSome());
1335 nsINode
* newRoot
= RangeUtils::ComputeRootNode(aContainer
);
1336 MOZ_ASSERT(newRoot
);
1341 RawRangeBoundary
start(aContainer
, aStartContent
->GetPreviousSibling());
1342 RawRangeBoundary
end(aContainer
, aEndContent
);
1343 DoSetRange(start
, end
, newRoot
);
1346 void nsRange::SetEndBeforeJS(nsINode
& aNode
, ErrorResult
& aErr
) {
1347 AutoCalledByJSRestore
calledByJSRestorer(*this);
1349 SetEndBefore(aNode
, aErr
);
1352 void nsRange::SetEndBefore(
1353 nsINode
& aNode
, ErrorResult
& aRv
,
1354 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
1355 if (!CanAccess(aNode
)) {
1356 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1360 AutoInvalidateSelection
atEndOfBlock(this);
1361 // If the node is being removed from its parent, GetRawRangeBoundaryBefore()
1362 // returns unset instance. Then, SetEnd() will throw
1363 // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
1364 SetEnd(RangeUtils::GetRawRangeBoundaryBefore(&aNode
), aRv
,
1365 aAllowCrossShadowBoundary
);
1368 void nsRange::SetEndAfterJS(nsINode
& aNode
, ErrorResult
& aErr
) {
1369 AutoCalledByJSRestore
calledByJSRestorer(*this);
1371 SetEndAfter(aNode
, aErr
);
1374 void nsRange::SetEndAfter(nsINode
& aNode
, ErrorResult
& aRv
) {
1375 if (!CanAccess(aNode
)) {
1376 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1380 AutoInvalidateSelection
atEndOfBlock(this);
1381 // If the node is being removed from its parent, GetRawRangeBoundaryAfter()
1382 // returns unset instance. Then, SetEnd() will throw
1383 // NS_ERROR_DOM_INVALID_NODE_TYPE_ERR.
1384 SetEnd(RangeUtils::GetRawRangeBoundaryAfter(&aNode
), aRv
);
1387 void nsRange::Collapse(bool aToStart
) {
1388 if (!mIsPositioned
) return;
1390 AutoInvalidateSelection
atEndOfBlock(this);
1392 DoSetRange(mStart
, mStart
, mRoot
);
1394 DoSetRange(mEnd
, mEnd
, mRoot
);
1398 void nsRange::CollapseJS(bool aToStart
) {
1399 AutoCalledByJSRestore
calledByJSRestorer(*this);
1404 void nsRange::SelectNodeJS(nsINode
& aNode
, ErrorResult
& aErr
) {
1405 AutoCalledByJSRestore
calledByJSRestorer(*this);
1407 SelectNode(aNode
, aErr
);
1410 void nsRange::SelectNode(nsINode
& aNode
, ErrorResult
& aRv
) {
1411 if (!CanAccess(aNode
)) {
1412 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1416 nsINode
* container
= aNode
.GetParentNode();
1417 nsINode
* newRoot
= RangeUtils::ComputeRootNode(container
);
1419 aRv
.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
);
1423 const Maybe
<uint32_t> index
= container
->ComputeIndexOf(&aNode
);
1424 // MOZ_ASSERT(index.isSome());
1425 // We need to compute the index here unfortunately, because, while we have
1426 // support for XBL, |container| may be the node's binding parent without
1427 // actually containing it.
1428 if (MOZ_UNLIKELY(NS_WARN_IF(index
.isNothing()))) {
1429 aRv
.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
);
1433 AutoInvalidateSelection
atEndOfBlock(this);
1434 DoSetRange(RawRangeBoundary
{container
, *index
},
1435 RawRangeBoundary
{container
, *index
+ 1u}, newRoot
);
1438 void nsRange::SelectNodeContentsJS(nsINode
& aNode
, ErrorResult
& aErr
) {
1439 AutoCalledByJSRestore
calledByJSRestorer(*this);
1441 SelectNodeContents(aNode
, aErr
);
1444 void nsRange::SelectNodeContents(nsINode
& aNode
, ErrorResult
& aRv
) {
1445 if (!CanAccess(aNode
)) {
1446 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1450 nsINode
* newRoot
= RangeUtils::ComputeRootNode(&aNode
);
1452 aRv
.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
);
1456 AutoInvalidateSelection
atEndOfBlock(this);
1457 DoSetRange(RawRangeBoundary(&aNode
, 0u),
1458 RawRangeBoundary(&aNode
, aNode
.Length()), newRoot
);
1461 // The Subtree Content Iterator only returns subtrees that are
1462 // completely within a given range. It doesn't return a CharacterData
1463 // node that contains either the start or end point of the range.,
1464 // nor does it return element nodes when nothing in the element is selected.
1465 // We need an iterator that will also include these start/end points
1466 // so that our methods/algorithms aren't cluttered with special
1467 // case code that tries to include these points while iterating.
1469 // The RangeSubtreeIterator class mimics the ContentSubtreeIterator
1470 // methods we need, so should the Content Iterator support the
1471 // start/end points in the future, we can switchover relatively
1474 class MOZ_STACK_CLASS RangeSubtreeIterator
{
1476 enum RangeSubtreeIterState
{ eDone
= 0, eUseStart
, eUseIterator
, eUseEnd
};
1478 Maybe
<ContentSubtreeIterator
> mSubtreeIter
;
1479 RangeSubtreeIterState mIterState
;
1481 nsCOMPtr
<nsINode
> mStart
;
1482 nsCOMPtr
<nsINode
> mEnd
;
1485 RangeSubtreeIterator() : mIterState(eDone
) {}
1486 ~RangeSubtreeIterator() = default;
1488 nsresult
Init(nsRange
* aRange
, AllowRangeCrossShadowBoundary
=
1489 AllowRangeCrossShadowBoundary::No
);
1490 already_AddRefed
<nsINode
> GetCurrentNode();
1496 bool IsDone() { return mIterState
== eDone
; }
1499 nsresult
RangeSubtreeIterator::Init(
1500 nsRange
* aRange
, AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary
) {
1502 if (aRange
->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) {
1506 // Grab the start point of the range and QI it to
1507 // a CharacterData pointer. If it is CharacterData store
1508 // a pointer to the node.
1510 if (!aRange
->IsPositioned()) {
1511 return NS_ERROR_FAILURE
;
1514 nsINode
* node
= aRange
->GetMayCrossShadowBoundaryStartContainer();
1515 if (NS_WARN_IF(!node
)) {
1516 return NS_ERROR_FAILURE
;
1519 if (node
->IsCharacterData() ||
1520 (node
->IsElement() && node
->AsElement()->GetChildCount() ==
1521 aRange
->MayCrossShadowBoundaryStartOffset())) {
1525 // Grab the end point of the range and QI it to
1526 // a CharacterData pointer. If it is CharacterData store
1527 // a pointer to the node.
1529 node
= aRange
->GetMayCrossShadowBoundaryEndContainer();
1530 if (NS_WARN_IF(!node
)) {
1531 return NS_ERROR_FAILURE
;
1534 if (node
->IsCharacterData() ||
1535 (node
->IsElement() && aRange
->MayCrossShadowBoundaryEndOffset() == 0)) {
1539 if (mStart
&& mStart
== mEnd
) {
1540 // The range starts and stops in the same CharacterData
1541 // node. Null out the end pointer so we only visit the
1546 // Now create a Content Subtree Iterator to be used
1547 // for the subtrees between the end points!
1549 mSubtreeIter
.emplace();
1552 aAllowCrossShadowBoundary
== AllowRangeCrossShadowBoundary::Yes
1553 ? mSubtreeIter
->InitWithAllowCrossShadowBoundary(aRange
)
1554 : mSubtreeIter
->Init(aRange
);
1555 if (NS_FAILED(res
)) return res
;
1557 if (mSubtreeIter
->IsDone()) {
1558 // The subtree iterator thinks there's nothing
1559 // to iterate over, so just free it up so we
1560 // don't accidentally call into it.
1562 mSubtreeIter
.reset();
1566 // Initialize the iterator by calling First().
1567 // Note that we are ignoring the return value on purpose!
1574 already_AddRefed
<nsINode
> RangeSubtreeIterator::GetCurrentNode() {
1575 nsCOMPtr
<nsINode
> node
;
1577 if (mIterState
== eUseStart
&& mStart
) {
1579 } else if (mIterState
== eUseEnd
&& mEnd
) {
1581 } else if (mIterState
== eUseIterator
&& mSubtreeIter
) {
1582 node
= mSubtreeIter
->GetCurrentNode();
1585 return node
.forget();
1588 void RangeSubtreeIterator::First() {
1590 mIterState
= eUseStart
;
1591 else if (mSubtreeIter
) {
1592 mSubtreeIter
->First();
1594 mIterState
= eUseIterator
;
1596 mIterState
= eUseEnd
;
1601 void RangeSubtreeIterator::Last() {
1603 mIterState
= eUseEnd
;
1604 else if (mSubtreeIter
) {
1605 mSubtreeIter
->Last();
1607 mIterState
= eUseIterator
;
1609 mIterState
= eUseStart
;
1614 void RangeSubtreeIterator::Next() {
1615 if (mIterState
== eUseStart
) {
1617 mSubtreeIter
->First();
1619 mIterState
= eUseIterator
;
1621 mIterState
= eUseEnd
;
1624 } else if (mIterState
== eUseIterator
) {
1625 mSubtreeIter
->Next();
1627 if (mSubtreeIter
->IsDone()) {
1629 mIterState
= eUseEnd
;
1637 void RangeSubtreeIterator::Prev() {
1638 if (mIterState
== eUseEnd
) {
1640 mSubtreeIter
->Last();
1642 mIterState
= eUseIterator
;
1644 mIterState
= eUseStart
;
1647 } else if (mIterState
== eUseIterator
) {
1648 mSubtreeIter
->Prev();
1650 if (mSubtreeIter
->IsDone()) {
1652 mIterState
= eUseStart
;
1660 // CollapseRangeAfterDelete() is a utility method that is used by
1661 // DeleteContents() and ExtractContents() to collapse the range
1662 // in the correct place, under the range's root container (the
1663 // range end points common container) as outlined by the Range spec:
1665 // http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/ranges.html
1666 // The assumption made by this method is that the delete or extract
1667 // has been done already, and left the range in a state where there is
1668 // no content between the 2 end points.
1670 static nsresult
CollapseRangeAfterDelete(nsRange
* aRange
) {
1671 NS_ENSURE_ARG_POINTER(aRange
);
1673 // Check if range gravity took care of collapsing the range for us!
1674 if (aRange
->Collapsed()) {
1675 // aRange is collapsed so there's nothing for us to do.
1677 // There are 2 possible scenarios here:
1679 // 1. aRange could've been collapsed prior to the delete/extract,
1680 // which would've resulted in nothing being removed, so aRange
1681 // is already where it should be.
1683 // 2. Prior to the delete/extract, aRange's start and end were in
1684 // the same container which would mean everything between them
1685 // was removed, causing range gravity to collapse the range.
1690 // aRange isn't collapsed so figure out the appropriate place to collapse!
1691 // First get both end points and their common ancestor.
1693 if (!aRange
->IsPositioned()) {
1694 return NS_ERROR_NOT_INITIALIZED
;
1697 nsCOMPtr
<nsINode
> commonAncestor
=
1698 aRange
->GetClosestCommonInclusiveAncestor();
1700 nsCOMPtr
<nsINode
> startContainer
= aRange
->GetStartContainer();
1701 nsCOMPtr
<nsINode
> endContainer
= aRange
->GetEndContainer();
1703 // Collapse to one of the end points if they are already in the
1704 // commonAncestor. This should work ok since this method is called
1705 // immediately after a delete or extract that leaves no content
1706 // between the 2 end points!
1708 if (startContainer
== commonAncestor
) {
1709 aRange
->Collapse(true);
1712 if (endContainer
== commonAncestor
) {
1713 aRange
->Collapse(false);
1717 // End points are at differing levels. We want to collapse to the
1718 // point that is between the 2 subtrees that contain each point,
1719 // under the common ancestor.
1721 nsCOMPtr
<nsINode
> nodeToSelect(startContainer
);
1723 while (nodeToSelect
) {
1724 nsCOMPtr
<nsINode
> parent
= nodeToSelect
->GetParentNode();
1725 if (parent
== commonAncestor
) break; // We found the nodeToSelect!
1727 nodeToSelect
= parent
;
1730 if (!nodeToSelect
) return NS_ERROR_FAILURE
; // This should never happen!
1733 aRange
->SelectNode(*nodeToSelect
, error
);
1734 if (error
.Failed()) {
1735 return error
.StealNSResult();
1738 aRange
->Collapse(false);
1743 PrependChild(nsINode
* aContainer
, nsINode
* aChild
) {
1744 nsCOMPtr
<nsINode
> first
= aContainer
->GetFirstChild();
1746 aContainer
->InsertBefore(*aChild
, first
, rv
);
1747 return rv
.StealNSResult();
1750 // Helper function for CutContents, making sure that the current node wasn't
1751 // removed by mutation events (bug 766426)
1752 static bool ValidateCurrentNode(nsRange
* aRange
, RangeSubtreeIterator
& aIter
) {
1754 nsCOMPtr
<nsINode
> node
= aIter
.GetCurrentNode();
1756 // We don't have to worry that the node was removed if it doesn't exist,
1757 // e.g., the iterator is done.
1761 nsresult rv
= RangeUtils::CompareNodeToRange(node
, aRange
, &before
, &after
);
1762 if (NS_WARN_IF(NS_FAILED(rv
))) {
1766 if (before
|| after
) {
1767 if (node
->IsCharacterData()) {
1768 // If we're dealing with the start/end container which is a character
1769 // node, pretend that the node is in the range.
1770 if (before
&& node
== aRange
->GetStartContainer()) {
1773 if (after
&& node
== aRange
->GetEndContainer()) {
1779 return !before
&& !after
;
1782 void nsRange::CutContents(DocumentFragment
** aFragment
, ErrorResult
& aRv
) {
1784 *aFragment
= nullptr;
1787 if (!CanAccess(*GetMayCrossShadowBoundaryStartContainer()) ||
1788 !CanAccess(*GetMayCrossShadowBoundaryEndContainer())) {
1789 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
1793 nsCOMPtr
<Document
> doc
= mStart
.Container()->OwnerDoc();
1795 nsCOMPtr
<nsINode
> commonAncestor
= GetCommonAncestorContainer(
1796 aRv
, StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
1797 ? AllowRangeCrossShadowBoundary::Yes
1798 : AllowRangeCrossShadowBoundary::No
);
1803 // If aFragment isn't null, create a temporary fragment to hold our return.
1804 RefPtr
<DocumentFragment
> retval
;
1807 new (doc
->NodeInfoManager()) DocumentFragment(doc
->NodeInfoManager());
1809 nsCOMPtr
<nsINode
> commonCloneAncestor
= retval
.get();
1811 // Batch possible DOMSubtreeModified events.
1812 mozAutoSubtreeModified
subtree(mRoot
? mRoot
->OwnerDoc() : nullptr, nullptr);
1814 // Save the range end points locally to avoid interference
1815 // of Range gravity during our edits!
1817 nsCOMPtr
<nsINode
> startContainer
= GetMayCrossShadowBoundaryStartContainer();
1818 // `GetCommonAncestorContainer()` above ensures the range is positioned, hence
1819 // there have to be valid offsets.
1821 const uint32_t startOffset
= *MayCrossShadowBoundaryStartRef().Offset(
1822 RangeBoundary::OffsetFilter::kValidOffsets
);
1824 nsCOMPtr
<nsINode
> endContainer
= GetMayCrossShadowBoundaryEndContainer();
1825 const uint32_t endOffset
= *MayCrossShadowBoundaryEndRef().Offset(
1826 RangeBoundary::OffsetFilter::kValidOffsets
);
1829 // For extractContents(), abort early if there's a doctype (bug 719533).
1830 // This can happen only if the common ancestor is a document, in which case
1831 // we just need to find its doctype child and check if that's in the range.
1832 nsCOMPtr
<Document
> commonAncestorDocument
=
1833 do_QueryInterface(commonAncestor
);
1834 if (commonAncestorDocument
) {
1835 RefPtr
<DocumentType
> doctype
= commonAncestorDocument
->GetDoctype();
1837 // `GetCommonAncestorContainer()` above ensured the range is positioned.
1838 // Hence, start and end are both set and valid. If available, `doctype`
1839 // has a common ancestor with start and end, hence both have to be
1840 // comparable to it.
1842 *nsContentUtils::ComparePoints(startContainer
, startOffset
, doctype
,
1844 *nsContentUtils::ComparePoints(doctype
, 0, endContainer
, endOffset
) <
1846 aRv
.ThrowHierarchyRequestError("Start or end position isn't valid.");
1852 // Create and initialize a subtree iterator that will give
1853 // us all the subtrees within the range.
1855 RangeSubtreeIterator iter
;
1857 aRv
= iter
.Init(this,
1858 StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()
1859 ? AllowRangeCrossShadowBoundary::Yes
1860 : AllowRangeCrossShadowBoundary::No
);
1865 if (iter
.IsDone()) {
1866 // There's nothing for us to delete.
1867 aRv
= CollapseRangeAfterDelete(this);
1868 if (!aRv
.Failed() && aFragment
) {
1869 retval
.forget(aFragment
);
1876 bool handled
= false;
1878 // With the exception of text nodes that contain one of the range
1879 // end points, the subtree iterator should only give us back subtrees
1880 // that are completely contained between the range's end points.
1882 while (!iter
.IsDone()) {
1883 nsCOMPtr
<nsINode
> nodeToResult
;
1884 nsCOMPtr
<nsINode
> node
= iter
.GetCurrentNode();
1886 // Before we delete anything, advance the iterator to the next node that's
1887 // not a descendant of this one. XXX It's a bit silly to iterate through
1888 // the descendants only to throw them out, we should use an iterator that
1889 // skips the descendants to begin with.
1892 nsCOMPtr
<nsINode
> nextNode
= iter
.GetCurrentNode();
1893 while (nextNode
&& nextNode
->IsInclusiveDescendantOf(node
)) {
1895 nextNode
= iter
.GetCurrentNode();
1900 // If it's CharacterData, make sure we might need to delete
1901 // part of the data, instead of removing the whole node.
1903 // XXX_kin: We need to also handle ProcessingInstruction
1904 // XXX_kin: according to the spec.
1906 if (auto charData
= CharacterData::FromNode(node
)) {
1907 uint32_t dataLength
= 0;
1909 if (node
== startContainer
) {
1910 if (node
== endContainer
) {
1911 // This range is completely contained within a single text node.
1912 // Delete or extract the data between startOffset and endOffset.
1914 if (endOffset
> startOffset
) {
1916 nsAutoString cutValue
;
1917 charData
->SubstringData(startOffset
, endOffset
- startOffset
,
1919 if (NS_WARN_IF(aRv
.Failed())) {
1922 nsCOMPtr
<nsINode
> clone
= node
->CloneNode(false, aRv
);
1923 if (NS_WARN_IF(aRv
.Failed())) {
1926 clone
->SetNodeValue(cutValue
, aRv
);
1927 if (NS_WARN_IF(aRv
.Failed())) {
1930 nodeToResult
= clone
;
1933 nsMutationGuard guard
;
1934 charData
->DeleteData(startOffset
, endOffset
- startOffset
, aRv
);
1935 if (NS_WARN_IF(aRv
.Failed())) {
1938 if (guard
.Mutated(0) && !ValidateCurrentNode(this, iter
)) {
1939 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1946 // Delete or extract everything after startOffset.
1948 dataLength
= charData
->Length();
1950 if (dataLength
>= startOffset
) {
1952 nsAutoString cutValue
;
1953 charData
->SubstringData(startOffset
, dataLength
, cutValue
, aRv
);
1954 if (NS_WARN_IF(aRv
.Failed())) {
1957 nsCOMPtr
<nsINode
> clone
= node
->CloneNode(false, aRv
);
1958 if (NS_WARN_IF(aRv
.Failed())) {
1961 clone
->SetNodeValue(cutValue
, aRv
);
1962 if (NS_WARN_IF(aRv
.Failed())) {
1965 nodeToResult
= clone
;
1968 nsMutationGuard guard
;
1969 charData
->DeleteData(startOffset
, dataLength
, aRv
);
1970 if (NS_WARN_IF(aRv
.Failed())) {
1973 if (guard
.Mutated(0) && !ValidateCurrentNode(this, iter
)) {
1974 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1981 } else if (node
== endContainer
) {
1982 // Delete or extract everything before endOffset.
1984 nsAutoString cutValue
;
1985 charData
->SubstringData(0, endOffset
, cutValue
, aRv
);
1986 if (NS_WARN_IF(aRv
.Failed())) {
1989 nsCOMPtr
<nsINode
> clone
= node
->CloneNode(false, aRv
);
1990 if (NS_WARN_IF(aRv
.Failed())) {
1993 clone
->SetNodeValue(cutValue
, aRv
);
1994 if (NS_WARN_IF(aRv
.Failed())) {
1997 nodeToResult
= clone
;
2000 nsMutationGuard guard
;
2001 charData
->DeleteData(0, endOffset
, aRv
);
2002 if (NS_WARN_IF(aRv
.Failed())) {
2005 if (guard
.Mutated(0) && !ValidateCurrentNode(this, iter
)) {
2006 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2013 if (!handled
&& (node
== endContainer
|| node
== startContainer
)) {
2014 if (node
&& node
->IsElement() &&
2015 ((node
== endContainer
&& endOffset
== 0) ||
2016 (node
== startContainer
&&
2017 node
->AsElement()->GetChildCount() == startOffset
))) {
2019 nodeToResult
= node
->CloneNode(false, aRv
);
2029 // node was not handled above, so it must be completely contained
2030 // within the range. Just remove it from the tree!
2031 nodeToResult
= node
;
2034 uint32_t parentCount
= 0;
2035 // Set the result to document fragment if we have 'retval'.
2037 nsCOMPtr
<nsINode
> oldCommonAncestor
= commonAncestor
;
2038 if (!iter
.IsDone()) {
2039 // Setup the parameters for the next iteration of the loop.
2041 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2045 // Get node's and nextNode's common parent. Do this before moving
2046 // nodes from original DOM to result fragment.
2048 nsContentUtils::GetClosestCommonInclusiveAncestor(node
, nextNode
);
2049 if (!commonAncestor
) {
2050 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2054 nsCOMPtr
<nsINode
> parentCounterNode
= node
;
2055 while (parentCounterNode
&& parentCounterNode
!= commonAncestor
) {
2057 parentCounterNode
= parentCounterNode
->GetParentNode();
2058 if (!parentCounterNode
) {
2059 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2065 // Clone the parent hierarchy between commonAncestor and node.
2066 nsCOMPtr
<nsINode
> closestAncestor
, farthestAncestor
;
2067 aRv
= CloneParentsBetween(oldCommonAncestor
, node
,
2068 getter_AddRefs(closestAncestor
),
2069 getter_AddRefs(farthestAncestor
));
2074 if (farthestAncestor
) {
2075 commonCloneAncestor
->AppendChild(*farthestAncestor
, aRv
);
2076 if (NS_WARN_IF(aRv
.Failed())) {
2081 nsMutationGuard guard
;
2082 nsCOMPtr
<nsINode
> parent
= nodeToResult
->GetParentNode();
2083 if (closestAncestor
) {
2084 closestAncestor
->AppendChild(*nodeToResult
, aRv
);
2086 commonCloneAncestor
->AppendChild(*nodeToResult
, aRv
);
2088 if (NS_WARN_IF(aRv
.Failed())) {
2091 if (guard
.Mutated(parent
? 2 : 1) && !ValidateCurrentNode(this, iter
)) {
2092 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2095 } else if (nodeToResult
) {
2096 nsMutationGuard guard
;
2097 nsCOMPtr
<nsINode
> node
= nodeToResult
;
2098 nsCOMPtr
<nsINode
> parent
= node
->GetParentNode();
2100 parent
->RemoveChild(*node
, aRv
);
2105 if (guard
.Mutated(1) && !ValidateCurrentNode(this, iter
)) {
2106 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2111 if (!iter
.IsDone() && retval
) {
2112 // Find the equivalent of commonAncestor in the cloned tree.
2113 nsCOMPtr
<nsINode
> newCloneAncestor
= nodeToResult
;
2114 for (uint32_t i
= parentCount
; i
; --i
) {
2115 newCloneAncestor
= newCloneAncestor
->GetParentNode();
2116 if (!newCloneAncestor
) {
2117 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2121 commonCloneAncestor
= newCloneAncestor
;
2125 aRv
= CollapseRangeAfterDelete(this);
2126 if (!aRv
.Failed() && aFragment
) {
2127 retval
.forget(aFragment
);
2131 void nsRange::DeleteContents(ErrorResult
& aRv
) { CutContents(nullptr, aRv
); }
2133 already_AddRefed
<DocumentFragment
> nsRange::ExtractContents(ErrorResult
& rv
) {
2134 RefPtr
<DocumentFragment
> fragment
;
2135 CutContents(getter_AddRefs(fragment
), rv
);
2136 return fragment
.forget();
2139 int16_t nsRange::CompareBoundaryPoints(uint16_t aHow
,
2140 const nsRange
& aOtherRange
,
2142 if (!mIsPositioned
|| !aOtherRange
.IsPositioned()) {
2143 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
2147 nsINode
*ourNode
, *otherNode
;
2148 uint32_t ourOffset
, otherOffset
;
2151 case Range_Binding::START_TO_START
:
2152 ourNode
= mStart
.Container();
2153 ourOffset
= *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
2154 otherNode
= aOtherRange
.GetStartContainer();
2155 otherOffset
= aOtherRange
.StartOffset();
2157 case Range_Binding::START_TO_END
:
2158 ourNode
= mEnd
.Container();
2159 ourOffset
= *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
2160 otherNode
= aOtherRange
.GetStartContainer();
2161 otherOffset
= aOtherRange
.StartOffset();
2163 case Range_Binding::END_TO_START
:
2164 ourNode
= mStart
.Container();
2165 ourOffset
= *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
2166 otherNode
= aOtherRange
.GetEndContainer();
2167 otherOffset
= aOtherRange
.EndOffset();
2169 case Range_Binding::END_TO_END
:
2170 ourNode
= mEnd
.Container();
2171 ourOffset
= *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
2172 otherNode
= aOtherRange
.GetEndContainer();
2173 otherOffset
= aOtherRange
.EndOffset();
2176 // We were passed an illegal value
2177 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
2181 if (mRoot
!= aOtherRange
.GetRoot()) {
2182 aRv
.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR
);
2186 const Maybe
<int32_t> order
=
2187 nsContentUtils::ComparePoints(ourNode
, ourOffset
, otherNode
, otherOffset
);
2189 // `this` and `aOtherRange` share the same root and (ourNode, ourOffset),
2190 // (otherNode, otherOffset) correspond to some of their boundaries. Hence,
2191 // (ourNode, ourOffset) and (otherNode, otherOffset) have to be comparable.
2196 nsresult
nsRange::CloneParentsBetween(nsINode
* aAncestor
, nsINode
* aNode
,
2197 nsINode
** aClosestAncestor
,
2198 nsINode
** aFarthestAncestor
) {
2199 NS_ENSURE_ARG_POINTER(
2200 (aAncestor
&& aNode
&& aClosestAncestor
&& aFarthestAncestor
));
2202 *aClosestAncestor
= nullptr;
2203 *aFarthestAncestor
= nullptr;
2205 if (aAncestor
== aNode
) return NS_OK
;
2207 AutoTArray
<nsCOMPtr
<nsINode
>, 16> parentStack
;
2209 nsCOMPtr
<nsINode
> parent
= aNode
->GetParentNode();
2210 while (parent
&& parent
!= aAncestor
) {
2211 parentStack
.AppendElement(parent
);
2212 parent
= parent
->GetParentNode();
2215 nsCOMPtr
<nsINode
> firstParent
;
2216 nsCOMPtr
<nsINode
> lastParent
;
2217 for (int32_t i
= parentStack
.Length() - 1; i
>= 0; i
--) {
2219 nsCOMPtr
<nsINode
> clone
= parentStack
[i
]->CloneNode(false, rv
);
2222 return rv
.StealNSResult();
2225 return NS_ERROR_FAILURE
;
2231 firstParent
->AppendChild(*clone
, rv
);
2233 return rv
.StealNSResult();
2237 firstParent
= clone
;
2240 firstParent
.forget(aClosestAncestor
);
2241 lastParent
.forget(aFarthestAncestor
);
2246 already_AddRefed
<DocumentFragment
> nsRange::CloneContents(ErrorResult
& aRv
) {
2247 nsCOMPtr
<nsINode
> commonAncestor
= GetCommonAncestorContainer(aRv
);
2248 MOZ_ASSERT(!aRv
.Failed(), "GetCommonAncestorContainer() shouldn't fail!");
2250 nsCOMPtr
<Document
> doc
= mStart
.Container()->OwnerDoc();
2251 NS_ASSERTION(doc
, "CloneContents needs a document to continue.");
2253 aRv
.Throw(NS_ERROR_FAILURE
);
2257 // Create a new document fragment in the context of this document,
2258 // which might be null
2260 RefPtr
<DocumentFragment
> clonedFrag
=
2261 new (doc
->NodeInfoManager()) DocumentFragment(doc
->NodeInfoManager());
2264 return clonedFrag
.forget();
2267 nsCOMPtr
<nsINode
> commonCloneAncestor
= clonedFrag
.get();
2269 // Create and initialize a subtree iterator that will give
2270 // us all the subtrees within the range.
2272 RangeSubtreeIterator iter
;
2274 aRv
= iter
.Init(this);
2279 if (iter
.IsDone()) {
2280 // There's nothing to add to the doc frag, we must be done!
2281 return clonedFrag
.forget();
2286 // With the exception of text nodes that contain one of the range
2287 // end points and elements which don't have any content selected the subtree
2288 // iterator should only give us back subtrees that are completely contained
2289 // between the range's end points.
2291 // Unfortunately these subtrees don't contain the parent hierarchy/context
2292 // that the Range spec requires us to return. This loop clones the
2293 // parent hierarchy, adds a cloned version of the subtree, to it, then
2294 // correctly places this new subtree into the doc fragment.
2296 while (!iter
.IsDone()) {
2297 nsCOMPtr
<nsINode
> node
= iter
.GetCurrentNode();
2299 !node
->IsElement() ||
2300 (!(node
== mEnd
.Container() &&
2301 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
) == 0) &&
2302 !(node
== mStart
.Container() &&
2303 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
) ==
2304 node
->AsElement()->GetChildCount()));
2306 // Clone the current subtree!
2308 nsCOMPtr
<nsINode
> clone
= node
->CloneNode(deepClone
, aRv
);
2313 // If it's CharacterData, make sure we only clone what
2316 // XXX_kin: We need to also handle ProcessingInstruction
2317 // XXX_kin: according to the spec.
2319 if (auto charData
= CharacterData::FromNode(clone
)) {
2320 if (node
== mEnd
.Container()) {
2321 // We only need the data before mEndOffset, so get rid of any
2324 uint32_t dataLength
= charData
->Length();
2326 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
)) {
2327 charData
->DeleteData(
2328 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2330 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2338 if (node
== mStart
.Container()) {
2339 // We don't need any data before mStartOffset, so just
2342 if (*mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
) > 0) {
2343 charData
->DeleteData(
2344 0, *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2353 // Clone the parent hierarchy between commonAncestor and node.
2355 nsCOMPtr
<nsINode
> closestAncestor
, farthestAncestor
;
2357 aRv
= CloneParentsBetween(commonAncestor
, node
,
2358 getter_AddRefs(closestAncestor
),
2359 getter_AddRefs(farthestAncestor
));
2365 // Hook the parent hierarchy/context of the subtree into the clone tree.
2367 if (farthestAncestor
) {
2368 commonCloneAncestor
->AppendChild(*farthestAncestor
, aRv
);
2375 // Place the cloned subtree into the cloned doc frag tree!
2377 nsCOMPtr
<nsINode
> cloneNode
= clone
;
2378 if (closestAncestor
) {
2379 // Append the subtree under closestAncestor since it is the
2380 // immediate parent of the subtree.
2382 closestAncestor
->AppendChild(*cloneNode
, aRv
);
2384 // If we get here, there is no missing parent hierarchy between
2385 // commonAncestor and node, so just append clone to commonCloneAncestor.
2387 commonCloneAncestor
->AppendChild(*cloneNode
, aRv
);
2393 // Get the next subtree to be processed. The idea here is to setup
2394 // the parameters for the next iteration of the loop.
2398 if (iter
.IsDone()) break; // We must be done!
2400 nsCOMPtr
<nsINode
> nextNode
= iter
.GetCurrentNode();
2402 aRv
.Throw(NS_ERROR_FAILURE
);
2406 // Get node and nextNode's common parent.
2408 nsContentUtils::GetClosestCommonInclusiveAncestor(node
, nextNode
);
2410 if (!commonAncestor
) {
2411 aRv
.Throw(NS_ERROR_FAILURE
);
2415 // Find the equivalent of commonAncestor in the cloned tree!
2417 while (node
&& node
!= commonAncestor
) {
2418 node
= node
->GetParentNode();
2424 aRv
.Throw(NS_ERROR_FAILURE
);
2428 cloneNode
= cloneNode
->GetParentNode();
2430 aRv
.Throw(NS_ERROR_FAILURE
);
2435 commonCloneAncestor
= cloneNode
;
2438 return clonedFrag
.forget();
2441 already_AddRefed
<nsRange
> nsRange::CloneRange() const {
2442 RefPtr
<nsRange
> range
= nsRange::Create(mOwner
);
2443 range
->DoSetRange(mStart
, mEnd
, mRoot
);
2444 if (mCrossShadowBoundaryRange
) {
2445 range
->CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
2446 mCrossShadowBoundaryRange
->StartRef(),
2447 mCrossShadowBoundaryRange
->EndRef());
2449 return range
.forget();
2452 void nsRange::InsertNode(nsINode
& aNode
, ErrorResult
& aRv
) {
2453 if (!CanAccess(aNode
)) {
2454 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2458 if (!IsPositioned()) {
2459 aRv
.Throw(NS_ERROR_NOT_INITIALIZED
);
2463 uint32_t tStartOffset
= StartOffset();
2465 nsCOMPtr
<nsINode
> tStartContainer
= GetStartContainer();
2467 if (!CanAccess(*tStartContainer
)) {
2468 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2472 if (&aNode
== tStartContainer
) {
2473 aRv
.ThrowHierarchyRequestError(
2474 "The inserted node can not be range's start node.");
2478 // This is the node we'll be inserting before, and its parent
2479 nsCOMPtr
<nsINode
> referenceNode
;
2480 nsCOMPtr
<nsINode
> referenceParentNode
= tStartContainer
;
2482 RefPtr
<Text
> startTextNode
= tStartContainer
->GetAsText();
2483 nsCOMPtr
<nsINodeList
> tChildList
;
2484 if (startTextNode
) {
2485 referenceParentNode
= tStartContainer
->GetParentNode();
2486 if (!referenceParentNode
) {
2487 aRv
.ThrowHierarchyRequestError(
2488 "Can not get range's start node's parent.");
2492 referenceParentNode
->EnsurePreInsertionValidity(aNode
, tStartContainer
,
2498 RefPtr
<Text
> secondPart
= startTextNode
->SplitText(tStartOffset
, aRv
);
2503 referenceNode
= secondPart
;
2505 tChildList
= tStartContainer
->ChildNodes();
2507 // find the insertion point in the DOM and insert the Node
2508 referenceNode
= tChildList
->Item(tStartOffset
);
2510 tStartContainer
->EnsurePreInsertionValidity(aNode
, referenceNode
, aRv
);
2516 // We might need to update the end to include the new node (bug 433662).
2517 // Ideally we'd only do this if needed, but it's tricky to know when it's
2518 // needed in advance (bug 765799).
2521 if (referenceNode
) {
2522 Maybe
<uint32_t> indexInParent
= referenceNode
->ComputeIndexInParentNode();
2523 if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent
.isNothing()))) {
2524 aRv
.Throw(NS_ERROR_FAILURE
);
2527 newOffset
= *indexInParent
;
2529 newOffset
= tChildList
->Length();
2532 if (aNode
.NodeType() == nsINode::DOCUMENT_FRAGMENT_NODE
) {
2533 newOffset
+= aNode
.GetChildCount();
2538 // Now actually insert the node
2539 nsCOMPtr
<nsINode
> tResultNode
;
2540 tResultNode
= referenceParentNode
->InsertBefore(aNode
, referenceNode
, aRv
);
2546 aRv
= SetEnd(referenceParentNode
, newOffset
);
2550 void nsRange::SurroundContents(nsINode
& aNewParent
, ErrorResult
& aRv
) {
2551 if (!CanAccess(aNewParent
)) {
2552 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2557 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2560 // INVALID_STATE_ERROR: Raised if the Range partially selects a non-text
2562 if (mStart
.Container() != mEnd
.Container()) {
2563 bool startIsText
= mStart
.Container()->IsText();
2564 bool endIsText
= mEnd
.Container()->IsText();
2565 nsINode
* startGrandParent
= mStart
.Container()->GetParentNode();
2566 nsINode
* endGrandParent
= mEnd
.Container()->GetParentNode();
2567 if (!((startIsText
&& endIsText
&& startGrandParent
&&
2568 startGrandParent
== endGrandParent
) ||
2569 (startIsText
&& startGrandParent
&&
2570 startGrandParent
== mEnd
.Container()) ||
2571 (endIsText
&& endGrandParent
&&
2572 endGrandParent
== mStart
.Container()))) {
2573 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2578 // INVALID_NODE_TYPE_ERROR if aNewParent is something that can't be inserted
2579 // (Document, DocumentType, DocumentFragment)
2580 uint16_t nodeType
= aNewParent
.NodeType();
2581 if (nodeType
== nsINode::DOCUMENT_NODE
||
2582 nodeType
== nsINode::DOCUMENT_TYPE_NODE
||
2583 nodeType
== nsINode::DOCUMENT_FRAGMENT_NODE
) {
2584 aRv
.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR
);
2588 // Extract the contents within the range.
2590 RefPtr
<DocumentFragment
> docFrag
= ExtractContents(aRv
);
2597 aRv
.Throw(NS_ERROR_FAILURE
);
2601 // Spec says we need to remove all of aNewParent's
2602 // children prior to insertion.
2604 nsCOMPtr
<nsINodeList
> children
= aNewParent
.ChildNodes();
2606 aRv
.Throw(NS_ERROR_FAILURE
);
2610 uint32_t numChildren
= children
->Length();
2612 while (numChildren
) {
2613 nsCOMPtr
<nsINode
> child
= children
->Item(--numChildren
);
2615 aRv
.Throw(NS_ERROR_FAILURE
);
2619 aNewParent
.RemoveChild(*child
, aRv
);
2625 // Insert aNewParent at the range's start point.
2627 InsertNode(aNewParent
, aRv
);
2632 // Append the content we extracted under aNewParent.
2633 aNewParent
.AppendChild(*docFrag
, aRv
);
2638 // Select aNewParent, and its contents.
2640 SelectNode(aNewParent
, aRv
);
2643 void nsRange::ToString(nsAString
& aReturn
, ErrorResult
& aErr
) {
2647 // If we're unpositioned, return the empty string
2648 if (!mIsPositioned
) {
2653 printf("Range dump: -----------------------\n");
2656 // effeciency hack for simple case
2657 if (mStart
.Container() == mEnd
.Container()) {
2659 mStart
.Container() ? mStart
.Container()->GetAsText() : nullptr;
2663 // If debug, dump it:
2664 textNode
->List(stdout
);
2665 printf("End Range dump: -----------------------\n");
2669 textNode
->SubstringData(
2670 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2671 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
) -
2672 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2678 /* complex case: mStart.Container() != mEnd.Container(), or mStartParent not a
2679 text node revisit - there are potential optimizations here and also
2683 PostContentIterator postOrderIter
;
2684 nsresult rv
= postOrderIter
.Init(this);
2685 if (NS_WARN_IF(NS_FAILED(rv
))) {
2690 nsString tempString
;
2692 // loop through the content iterator, which returns nodes in the range in
2693 // close tag order, and grab the text from any text node
2694 for (; !postOrderIter
.IsDone(); postOrderIter
.Next()) {
2695 nsINode
* n
= postOrderIter
.GetCurrentNode();
2698 // If debug, dump it:
2701 Text
* textNode
= n
->GetAsText();
2702 if (textNode
) // if it's a text node, get the text
2704 if (n
== mStart
.Container()) { // only include text past start offset
2705 uint32_t strLength
= textNode
->Length();
2706 textNode
->SubstringData(
2707 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2709 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2710 tempString
, IgnoreErrors());
2711 aReturn
+= tempString
;
2713 mEnd
.Container()) { // only include text before end offset
2714 textNode
->SubstringData(
2715 0, *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
2716 tempString
, IgnoreErrors());
2717 aReturn
+= tempString
;
2718 } else { // grab the whole kit-n-kaboodle
2719 textNode
->GetData(tempString
);
2720 aReturn
+= tempString
;
2726 printf("End Range dump: -----------------------\n");
2730 void nsRange::Detach() {}
2732 already_AddRefed
<DocumentFragment
> nsRange::CreateContextualFragment(
2733 const nsAString
& aFragment
, ErrorResult
& aRv
) const {
2734 if (!mIsPositioned
) {
2735 aRv
.Throw(NS_ERROR_FAILURE
);
2739 return nsContentUtils::CreateContextualFragment(mStart
.Container(), aFragment
,
2743 already_AddRefed
<DocumentFragment
> nsRange::CreateContextualFragment(
2744 const TrustedHTMLOrString
& aFragment
, ErrorResult
& aRv
) const {
2745 if (!mIsPositioned
) {
2746 aRv
.Throw(NS_ERROR_FAILURE
);
2749 MOZ_ASSERT(mStart
.Container());
2751 constexpr nsLiteralString sink
= u
"Range createContextualFragment"_ns
;
2752 Maybe
<nsAutoString
> compliantStringHolder
;
2753 nsCOMPtr
<nsINode
> node
= mStart
.Container();
2754 const nsAString
* compliantString
=
2755 TrustedTypeUtils::GetTrustedTypesCompliantString(
2756 aFragment
, sink
, kTrustedTypesOnlySinkGroup
, *node
,
2757 compliantStringHolder
, aRv
);
2762 return nsContentUtils::CreateContextualFragment(mStart
.Container(),
2763 *compliantString
, false, aRv
);
2766 static void ExtractRectFromOffset(nsIFrame
* aFrame
, const int32_t aOffset
,
2767 nsRect
* aR
, bool aFlushToOriginEdge
,
2768 bool aClampToEdge
) {
2773 aFrame
->GetPointFromOffset(aOffset
, &point
);
2775 // Determine if aFrame has a vertical writing mode, which will change our math
2776 // on the output rect.
2777 bool isVertical
= aFrame
->GetWritingMode().IsVertical();
2779 if (!aClampToEdge
&& !aR
->Contains(point
)) {
2780 // If point is outside aR, and we aren't clamping, output an empty rect
2781 // with origin at the point.
2793 point
= aR
->ClampPoint(point
);
2796 // point is within aR, and now we'll modify aR to output a rect that has point
2797 // on one edge. But which edge?
2798 if (aFlushToOriginEdge
) {
2799 // The output rect should be flush to the edge of aR that contains the
2802 aR
->SetHeight(point
.y
- aR
->y
);
2804 aR
->SetWidth(point
.x
- aR
->x
);
2807 // The output rect should be flush to the edge of aR opposite the origin.
2809 aR
->SetHeight(aR
->YMost() - point
.y
);
2812 aR
->SetWidth(aR
->XMost() - point
.x
);
2818 static nsTextFrame
* GetTextFrameForContent(nsIContent
* aContent
,
2819 bool aFlushLayout
) {
2820 RefPtr
<Document
> doc
= aContent
->OwnerDoc();
2821 PresShell
* presShell
= doc
->GetPresShell();
2826 // Try to un-suppress whitespace if needed, but only if we'll be able to flush
2827 // to immediately see the results of the un-suppression. If we can't flush
2828 // here, then calling EnsureFrameForTextNodeIsCreatedAfterFlush would be
2829 // pointless anyway.
2831 const bool frameWillBeUnsuppressed
=
2832 presShell
->FrameConstructor()
2833 ->EnsureFrameForTextNodeIsCreatedAfterFlush(
2834 static_cast<CharacterData
*>(aContent
));
2835 if (frameWillBeUnsuppressed
) {
2836 doc
->FlushPendingNotifications(FlushType::Layout
);
2840 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
2841 if (!frame
|| !frame
->IsTextFrame()) {
2844 return static_cast<nsTextFrame
*>(frame
);
2847 static nsresult
GetPartialTextRect(RectCallback
* aCallback
,
2848 Sequence
<nsString
>* aTextList
,
2849 nsIContent
* aContent
, int32_t aStartOffset
,
2850 int32_t aEndOffset
, bool aClampToEdge
,
2851 bool aFlushLayout
) {
2852 nsTextFrame
* textFrame
= GetTextFrameForContent(aContent
, aFlushLayout
);
2854 nsIFrame
* relativeTo
=
2855 nsLayoutUtils::GetContainingBlockForClientRect(textFrame
);
2857 for (nsTextFrame
* f
= textFrame
->FindContinuationForOffset(aStartOffset
); f
;
2858 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
2859 int32_t fstart
= f
->GetContentOffset(), fend
= f
->GetContentEnd();
2860 if (fend
<= aStartOffset
) {
2863 if (fstart
>= aEndOffset
) {
2867 // Calculate the text content offsets we'll need if text is requested.
2868 int32_t textContentStart
= fstart
;
2869 int32_t textContentEnd
= fend
;
2871 // overlapping with the offset we want
2872 f
->EnsureTextRun(nsTextFrame::eInflated
);
2873 NS_ENSURE_TRUE(f
->GetTextRun(nsTextFrame::eInflated
),
2874 NS_ERROR_OUT_OF_MEMORY
);
2875 bool topLeftToBottomRight
=
2876 !f
->GetTextRun(nsTextFrame::eInflated
)->IsInlineReversed();
2877 nsRect r
= f
->GetRectRelativeToSelf();
2878 if (fstart
< aStartOffset
) {
2879 // aStartOffset is within this frame
2880 ExtractRectFromOffset(f
, aStartOffset
, &r
, !topLeftToBottomRight
,
2882 textContentStart
= aStartOffset
;
2884 if (fend
> aEndOffset
) {
2885 // aEndOffset is in the middle of this frame
2886 ExtractRectFromOffset(f
, aEndOffset
, &r
, topLeftToBottomRight
,
2888 textContentEnd
= aEndOffset
;
2890 r
= nsLayoutUtils::TransformFrameRectToAncestor(f
, r
, relativeTo
);
2891 aCallback
->AddRect(r
);
2893 // Finally capture the text, if requested.
2895 nsIFrame::RenderedText renderedText
=
2896 f
->GetRenderedText(textContentStart
, textContentEnd
,
2897 nsIFrame::TextOffsetType::OffsetsInContentText
,
2898 nsIFrame::TrailingWhitespace::DontTrim
);
2900 NS_ENSURE_TRUE(aTextList
->AppendElement(renderedText
.mString
, fallible
),
2901 NS_ERROR_OUT_OF_MEMORY
);
2908 static void CollectClientRectsForSubtree(
2909 nsINode
* aNode
, RectCallback
* aCollector
, Sequence
<nsString
>* aTextList
,
2910 nsINode
* aStartContainer
, uint32_t aStartOffset
, nsINode
* aEndContainer
,
2911 uint32_t aEndOffset
, bool aClampToEdge
, bool aFlushLayout
, bool aTextOnly
) {
2912 auto* content
= nsIContent::FromNode(aNode
);
2917 const bool isText
= content
->IsText();
2919 if (aNode
== aStartContainer
) {
2920 int32_t offset
= aStartContainer
== aEndContainer
2921 ? static_cast<int32_t>(aEndOffset
)
2922 : content
->AsText()->TextDataLength();
2923 GetPartialTextRect(aCollector
, aTextList
, content
,
2924 static_cast<int32_t>(aStartOffset
), offset
,
2925 aClampToEdge
, aFlushLayout
);
2929 if (aNode
== aEndContainer
) {
2930 GetPartialTextRect(aCollector
, aTextList
, content
, 0,
2931 static_cast<int32_t>(aEndOffset
), aClampToEdge
,
2937 if (nsIFrame
* frame
= content
->GetPrimaryFrame()) {
2938 if (!aTextOnly
|| isText
) {
2939 nsLayoutUtils::GetAllInFlowRectsAndTexts(
2940 frame
, nsLayoutUtils::GetContainingBlockForClientRect(frame
),
2941 aCollector
, aTextList
,
2942 nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms
);
2947 // We just get the text when calling GetAllInFlowRectsAndTexts, so we
2948 // don't need to call it again when visiting the children.
2949 aTextList
= nullptr;
2951 } else if (!content
->IsElement() ||
2952 !content
->AsElement()->IsDisplayContents()) {
2956 FlattenedChildIterator
childIter(content
);
2957 for (nsIContent
* child
= childIter
.GetNextChild(); child
;
2958 child
= childIter
.GetNextChild()) {
2959 CollectClientRectsForSubtree(child
, aCollector
, aTextList
, aStartContainer
,
2960 aStartOffset
, aEndContainer
, aEndOffset
,
2961 aClampToEdge
, aFlushLayout
, aTextOnly
);
2966 void nsRange::CollectClientRectsAndText(
2967 RectCallback
* aCollector
, Sequence
<nsString
>* aTextList
, nsRange
* aRange
,
2968 nsINode
* aStartContainer
, uint32_t aStartOffset
, nsINode
* aEndContainer
,
2969 uint32_t aEndOffset
, bool aClampToEdge
, bool aFlushLayout
) {
2970 // Currently, this method is called with start of end offset of nsRange.
2971 // So, they must be between 0 - INT32_MAX.
2972 MOZ_ASSERT(RangeUtils::IsValidOffset(aStartOffset
));
2973 MOZ_ASSERT(RangeUtils::IsValidOffset(aEndOffset
));
2975 // Hold strong pointers across the flush
2976 nsCOMPtr
<nsINode
> startContainer
= aStartContainer
;
2977 nsCOMPtr
<nsINode
> endContainer
= aEndContainer
;
2979 // Flush out layout so our frames are up to date.
2980 if (!aStartContainer
->IsInComposedDoc()) {
2985 aStartContainer
->OwnerDoc()->FlushPendingNotifications(FlushType::Layout
);
2986 // Recheck whether we're still in the document
2987 if (!aStartContainer
->IsInComposedDoc()) {
2992 RangeSubtreeIterator iter
;
2994 nsresult rv
= iter
.Init(aRange
);
2995 if (NS_FAILED(rv
)) return;
2997 if (iter
.IsDone()) {
2998 // the range is collapsed, only continue if the cursor is in a text node
2999 if (aStartContainer
->IsText()) {
3000 nsTextFrame
* textFrame
=
3001 GetTextFrameForContent(aStartContainer
->AsText(), aFlushLayout
);
3005 textFrame
->GetChildFrameContainingOffset(
3006 static_cast<int32_t>(aStartOffset
), false, &outOffset
, &outFrame
);
3008 nsIFrame
* relativeTo
=
3009 nsLayoutUtils::GetContainingBlockForClientRect(outFrame
);
3010 nsRect r
= outFrame
->GetRectRelativeToSelf();
3011 ExtractRectFromOffset(outFrame
, static_cast<int32_t>(aStartOffset
),
3012 &r
, false, aClampToEdge
);
3014 r
= nsLayoutUtils::TransformFrameRectToAncestor(outFrame
, r
,
3016 aCollector
->AddRect(r
);
3024 nsCOMPtr
<nsINode
> node
= iter
.GetCurrentNode();
3027 CollectClientRectsForSubtree(node
, aCollector
, aTextList
, aStartContainer
,
3028 aStartOffset
, aEndContainer
, aEndOffset
,
3029 aClampToEdge
, aFlushLayout
, false);
3030 } while (!iter
.IsDone());
3033 already_AddRefed
<DOMRect
> nsRange::GetBoundingClientRect(bool aClampToEdge
,
3034 bool aFlushLayout
) {
3035 RefPtr
<DOMRect
> rect
= new DOMRect(ToSupports(mOwner
));
3036 if (!mIsPositioned
) {
3037 return rect
.forget();
3040 nsLayoutUtils::RectAccumulator accumulator
;
3041 CollectClientRectsAndText(
3042 &accumulator
, nullptr, this, mStart
.Container(),
3043 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
3045 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
), aClampToEdge
,
3048 nsRect r
= accumulator
.mResultRect
.IsEmpty() ? accumulator
.mFirstRect
3049 : accumulator
.mResultRect
;
3050 rect
->SetLayoutRect(r
);
3051 return rect
.forget();
3054 already_AddRefed
<DOMRectList
> nsRange::GetClientRects(bool aClampToEdge
,
3055 bool aFlushLayout
) {
3056 return GetClientRectsInner(AllowRangeCrossShadowBoundary::No
, aClampToEdge
,
3060 already_AddRefed
<DOMRectList
> nsRange::GetAllowCrossShadowBoundaryClientRects(
3061 bool aClampToEdge
, bool aFlushLayout
) {
3062 return GetClientRectsInner(AllowRangeCrossShadowBoundary::Yes
, aClampToEdge
,
3066 already_AddRefed
<DOMRectList
> nsRange::GetClientRectsInner(
3067 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundaryRange
,
3068 bool aClampToEdge
, bool aFlushLayout
) {
3069 if (!mIsPositioned
) {
3073 RefPtr
<DOMRectList
> rectList
= new DOMRectList(ToSupports(mOwner
));
3075 nsLayoutUtils::RectListBuilder
builder(rectList
);
3077 const auto& startRef
=
3078 aAllowCrossShadowBoundaryRange
== AllowRangeCrossShadowBoundary::Yes
3079 ? MayCrossShadowBoundaryStartRef()
3081 const auto& endRef
=
3082 aAllowCrossShadowBoundaryRange
== AllowRangeCrossShadowBoundary::Yes
3083 ? MayCrossShadowBoundaryEndRef()
3086 CollectClientRectsAndText(
3087 &builder
, nullptr, this, startRef
.Container(),
3088 *startRef
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
3090 *endRef
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
), aClampToEdge
,
3092 return rectList
.forget();
3095 void nsRange::GetClientRectsAndTexts(mozilla::dom::ClientRectsAndTexts
& aResult
,
3096 ErrorResult
& aErr
) {
3097 if (!mIsPositioned
) {
3101 aResult
.mRectList
= new DOMRectList(ToSupports(mOwner
));
3103 nsLayoutUtils::RectListBuilder
builder(aResult
.mRectList
);
3105 CollectClientRectsAndText(
3106 &builder
, &aResult
.mTextList
, this, mStart
.Container(),
3107 *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
3109 *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
), true, true);
3112 nsresult
nsRange::GetUsedFontFaces(nsLayoutUtils::UsedFontFaceList
& aResult
,
3113 uint32_t aMaxRanges
,
3114 bool aSkipCollapsedWhitespace
) {
3115 NS_ENSURE_TRUE(mIsPositioned
, NS_ERROR_UNEXPECTED
);
3117 nsCOMPtr
<nsINode
> startContainer
= mStart
.Container();
3118 nsCOMPtr
<nsINode
> endContainer
= mEnd
.Container();
3120 // Flush out layout so our frames are up to date.
3121 Document
* doc
= mStart
.Container()->OwnerDoc();
3122 NS_ENSURE_TRUE(doc
, NS_ERROR_UNEXPECTED
);
3123 doc
->FlushPendingNotifications(FlushType::Frames
);
3125 // Recheck whether we're still in the document
3126 NS_ENSURE_TRUE(mStart
.Container()->IsInComposedDoc(), NS_ERROR_UNEXPECTED
);
3128 // A table to map gfxFontEntry objects to InspectorFontFace objects.
3129 // This table does NOT own the InspectorFontFace objects, it only holds
3130 // raw pointers to them. They are owned by the aResult array.
3131 nsLayoutUtils::UsedFontFaceTable fontFaces
;
3133 RangeSubtreeIterator iter
;
3134 nsresult rv
= iter
.Init(this);
3135 NS_ENSURE_SUCCESS(rv
, rv
);
3137 while (!iter
.IsDone()) {
3138 // only collect anything if the range is not collapsed
3139 nsCOMPtr
<nsINode
> node
= iter
.GetCurrentNode();
3142 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(node
);
3146 nsIFrame
* frame
= content
->GetPrimaryFrame();
3151 if (content
->IsText()) {
3152 if (node
== startContainer
) {
3154 startContainer
== endContainer
3155 ? *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
)
3156 : content
->AsText()->TextDataLength();
3157 nsLayoutUtils::GetFontFacesForText(
3158 frame
, *mStart
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
3159 offset
, true, aResult
, fontFaces
, aMaxRanges
,
3160 aSkipCollapsedWhitespace
);
3163 if (node
== endContainer
) {
3164 nsLayoutUtils::GetFontFacesForText(
3165 frame
, 0, *mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
),
3166 true, aResult
, fontFaces
, aMaxRanges
, aSkipCollapsedWhitespace
);
3171 nsLayoutUtils::GetFontFacesForFrames(frame
, aResult
, fontFaces
, aMaxRanges
,
3172 aSkipCollapsedWhitespace
);
3178 nsINode
* nsRange::GetRegisteredClosestCommonInclusiveAncestor() {
3179 MOZ_ASSERT(IsInAnySelection(),
3180 "GetRegisteredClosestCommonInclusiveAncestor only valid for range "
3182 MOZ_ASSERT(mRegisteredClosestCommonInclusiveAncestor
);
3183 return mRegisteredClosestCommonInclusiveAncestor
;
3187 bool nsRange::AutoInvalidateSelection::sIsNested
;
3189 nsRange::AutoInvalidateSelection::~AutoInvalidateSelection() {
3190 if (!mCommonAncestor
) {
3194 ::InvalidateAllFrames(mCommonAncestor
);
3196 // Our range might not be in a selection anymore, because one of our selection
3197 // listeners might have gone ahead and run script of various sorts that messed
3198 // with selections, ranges, etc. But if it still is, we should check whether
3199 // we have a different common ancestor now, and if so invalidate its subtree
3200 // so it paints the selection it's in now.
3201 if (mRange
->IsInAnySelection()) {
3202 nsINode
* commonAncestor
=
3203 mRange
->GetRegisteredClosestCommonInclusiveAncestor();
3204 // XXXbz can commonAncestor really be null here? I wouldn't think so! If
3205 // it _were_, then in a debug build
3206 // GetRegisteredClosestCommonInclusiveAncestor() would have fatally
3208 if (commonAncestor
&& commonAncestor
!= mCommonAncestor
) {
3209 ::InvalidateAllFrames(commonAncestor
);
3215 already_AddRefed
<nsRange
> nsRange::Constructor(const GlobalObject
& aGlobal
,
3217 nsCOMPtr
<nsPIDOMWindowInner
> window
=
3218 do_QueryInterface(aGlobal
.GetAsSupports());
3219 if (!window
|| !window
->GetDoc()) {
3220 aRv
.Throw(NS_ERROR_FAILURE
);
3224 return window
->GetDoc()->CreateRange(aRv
);
3227 static bool ExcludeIfNextToNonSelectable(nsIContent
* aContent
) {
3228 return aContent
->IsText() &&
3229 aContent
->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE
);
3232 void nsRange::ExcludeNonSelectableNodes(nsTArray
<RefPtr
<nsRange
>>* aOutRanges
) {
3233 if (!mIsPositioned
) {
3237 MOZ_ASSERT(mEnd
.Container());
3238 MOZ_ASSERT(mStart
.Container());
3240 nsRange
* range
= this;
3241 RefPtr
<nsRange
> newRange
;
3243 PreContentIterator preOrderIter
;
3244 nsresult rv
= preOrderIter
.Init(range
);
3245 if (NS_FAILED(rv
)) {
3250 bool seenSelectable
= false;
3251 // |firstNonSelectableContent| is the first node in a consecutive sequence
3252 // of non-IsSelectable nodes. When we find a selectable node after such
3253 // a sequence we'll end the last nsRange, create a new one and restart
3255 nsIContent
* firstNonSelectableContent
= nullptr;
3257 nsINode
* node
= preOrderIter
.GetCurrentNode();
3258 preOrderIter
.Next();
3259 bool selectable
= true;
3260 nsIContent
* content
=
3261 node
&& node
->IsContent() ? node
->AsContent() : nullptr;
3263 if (firstNonSelectableContent
&&
3264 ExcludeIfNextToNonSelectable(content
)) {
3265 // Ignorable whitespace next to a sequence of non-selectable nodes
3266 // counts as non-selectable (bug 1216001).
3270 nsIFrame
* frame
= content
->GetPrimaryFrame();
3271 for (nsIContent
* p
= content
; !frame
&& (p
= p
->GetParent());) {
3272 frame
= p
->GetPrimaryFrame();
3275 selectable
= frame
->IsSelectable(nullptr);
3281 if (!firstNonSelectableContent
) {
3282 firstNonSelectableContent
= content
;
3284 if (preOrderIter
.IsDone()) {
3285 if (seenSelectable
) {
3286 // The tail end of the initial range is non-selectable - truncate
3287 // the current range before the first non-selectable node.
3288 range
->SetEndBefore(*firstNonSelectableContent
, IgnoreErrors());
3295 if (firstNonSelectableContent
) {
3296 if (range
== this && !seenSelectable
) {
3297 // This is the initial range and all its nodes until now are
3298 // non-selectable so just trim them from the start.
3299 IgnoredErrorResult err
;
3300 range
->SetStartBefore(*node
, err
, AllowRangeCrossShadowBoundary::Yes
);
3304 break; // restart the same range with a new iterator
3307 // Save the end point before truncating the range.
3308 nsINode
* endContainer
= range
->mEnd
.Container();
3309 const uint32_t endOffset
=
3310 *range
->mEnd
.Offset(RangeBoundary::OffsetFilter::kValidOffsets
);
3312 // Truncate the current range before the first non-selectable node.
3313 IgnoredErrorResult err
;
3314 range
->SetEndBefore(*firstNonSelectableContent
, err
,
3315 AllowRangeCrossShadowBoundary::Yes
);
3317 // Store it in the result (strong ref) - do this before creating
3318 // a new range in |newRange| below so we don't drop the last ref
3319 // to the range created in the previous iteration.
3320 if (!added
&& !err
.Failed()) {
3321 aOutRanges
->AppendElement(range
);
3324 // Create a new range for the remainder.
3325 nsINode
* startContainer
= node
;
3326 Maybe
<uint32_t> startOffset
= Some(0);
3327 // Don't start *inside* a node with independent selection though
3329 if (content
&& content
->HasIndependentSelection()) {
3330 nsINode
* parent
= startContainer
->GetParent();
3332 startOffset
= parent
->ComputeIndexOf(startContainer
);
3333 startContainer
= parent
;
3337 nsRange::Create(startContainer
, startOffset
.valueOr(UINT32_MAX
),
3338 endContainer
, endOffset
, IgnoreErrors());
3339 if (!newRange
|| newRange
->Collapsed()) {
3343 break; // create a new iterator for the new range, if any
3346 seenSelectable
= true;
3349 aOutRanges
->AppendElement(range
);
3351 if (preOrderIter
.IsDone()) {
3358 struct InnerTextAccumulator
{
3359 explicit InnerTextAccumulator(mozilla::dom::DOMString
& aValue
)
3360 : mString(aValue
.AsAString()), mRequiredLineBreakCount(0) {}
3361 void FlushLineBreaks() {
3362 while (mRequiredLineBreakCount
> 0) {
3363 // Required line breaks at the start of the text are suppressed.
3364 if (!mString
.IsEmpty()) {
3365 mString
.Append('\n');
3367 --mRequiredLineBreakCount
;
3370 void Append(char aCh
) { Append(nsAutoString(aCh
)); }
3371 void Append(const nsAString
& aString
) {
3372 if (aString
.IsEmpty()) {
3376 mString
.Append(aString
);
3378 void AddRequiredLineBreakCount(int8_t aCount
) {
3379 mRequiredLineBreakCount
= std::max(mRequiredLineBreakCount
, aCount
);
3383 int8_t mRequiredLineBreakCount
;
3386 static bool IsVisibleAndNotInReplacedElement(nsIFrame
* aFrame
) {
3387 if (!aFrame
|| !aFrame
->StyleVisibility()->IsVisible() ||
3388 aFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
3391 if (aFrame
->HidesContent()) {
3394 for (nsIFrame
* f
= aFrame
->GetParent(); f
; f
= f
->GetParent()) {
3395 if (f
->HidesContent()) {
3398 if (f
->IsReplaced() &&
3399 !f
->GetContent()->IsAnyOfHTMLElements(nsGkAtoms::button
,
3400 nsGkAtoms::select
) &&
3401 !f
->GetContent()->IsSVGElement()) {
3408 static void AppendTransformedText(InnerTextAccumulator
& aResult
,
3409 nsIContent
* aContainer
) {
3410 auto textNode
= static_cast<CharacterData
*>(aContainer
);
3412 nsIFrame
* frame
= textNode
->GetPrimaryFrame();
3413 if (!IsVisibleAndNotInReplacedElement(frame
)) {
3417 nsIFrame::RenderedText text
=
3418 frame
->GetRenderedText(0, aContainer
->GetChildCount());
3419 aResult
.Append(text
.mString
);
3423 * States for tree traversal. AT_NODE means that we are about to enter
3424 * the current DOM node. AFTER_NODE means that we have just finished traversing
3425 * the children of the current DOM node and are about to apply any
3426 * "after processing the node's children" steps before we finish visiting
3429 enum TreeTraversalState
{ AT_NODE
, AFTER_NODE
};
3431 static int8_t GetRequiredInnerTextLineBreakCount(nsIFrame
* aFrame
) {
3432 if (aFrame
->GetContent()->IsHTMLElement(nsGkAtoms::p
)) {
3435 const nsStyleDisplay
* styleDisplay
= aFrame
->StyleDisplay();
3436 if (styleDisplay
->IsBlockOutside(aFrame
) ||
3437 styleDisplay
->mDisplay
== StyleDisplay::TableCaption
) {
3443 static bool IsLastCellOfRow(nsIFrame
* aFrame
) {
3444 LayoutFrameType type
= aFrame
->Type();
3445 if (type
!= LayoutFrameType::TableCell
) {
3448 for (nsIFrame
* c
= aFrame
; c
; c
= c
->GetNextContinuation()) {
3449 if (c
->GetNextSibling()) {
3456 static bool IsLastRowOfRowGroup(nsIFrame
* aFrame
) {
3457 if (!aFrame
->IsTableRowFrame()) {
3460 for (nsIFrame
* c
= aFrame
; c
; c
= c
->GetNextContinuation()) {
3461 if (c
->GetNextSibling()) {
3468 static bool IsLastNonemptyRowGroupOfTable(nsIFrame
* aFrame
) {
3469 if (!aFrame
->IsTableRowGroupFrame()) {
3472 for (nsIFrame
* c
= aFrame
; c
; c
= c
->GetNextContinuation()) {
3473 for (nsIFrame
* next
= c
->GetNextSibling(); next
;
3474 next
= next
->GetNextSibling()) {
3475 if (next
->PrincipalChildList().FirstChild()) {
3483 void nsRange::GetInnerTextNoFlush(DOMString
& aValue
, ErrorResult
& aError
,
3484 nsIContent
* aContainer
) {
3485 InnerTextAccumulator
result(aValue
);
3487 if (aContainer
->IsText()) {
3488 AppendTransformedText(result
, aContainer
);
3492 nsIContent
* currentNode
= aContainer
;
3493 TreeTraversalState currentState
= AFTER_NODE
;
3495 nsIContent
* endNode
= aContainer
;
3496 TreeTraversalState endState
= AFTER_NODE
;
3498 nsIContent
* firstChild
= aContainer
->GetFirstChild();
3500 currentNode
= firstChild
;
3501 currentState
= AT_NODE
;
3504 while (currentNode
!= endNode
|| currentState
!= endState
) {
3505 nsIFrame
* f
= currentNode
->GetPrimaryFrame();
3506 bool isVisibleAndNotReplaced
= IsVisibleAndNotInReplacedElement(f
);
3507 if (currentState
== AT_NODE
) {
3508 bool isText
= currentNode
->IsText();
3509 if (isVisibleAndNotReplaced
) {
3510 result
.AddRequiredLineBreakCount(GetRequiredInnerTextLineBreakCount(f
));
3512 nsIFrame::RenderedText text
= f
->GetRenderedText();
3513 result
.Append(text
.mString
);
3516 nsIContent
* child
= currentNode
->GetFirstChild();
3518 currentNode
= child
;
3521 currentState
= AFTER_NODE
;
3523 if (currentNode
== endNode
&& currentState
== endState
) {
3526 if (isVisibleAndNotReplaced
) {
3527 if (currentNode
->IsHTMLElement(nsGkAtoms::br
)) {
3528 result
.Append('\n');
3530 switch (f
->StyleDisplay()->DisplayInside()) {
3531 case StyleDisplayInside::TableCell
:
3532 if (!IsLastCellOfRow(f
)) {
3533 result
.Append('\t');
3536 case StyleDisplayInside::TableRow
:
3537 if (!IsLastRowOfRowGroup(f
) ||
3538 !IsLastNonemptyRowGroupOfTable(f
->GetParent())) {
3539 result
.Append('\n');
3543 break; // Do nothing
3545 result
.AddRequiredLineBreakCount(GetRequiredInnerTextLineBreakCount(f
));
3547 nsIContent
* next
= currentNode
->GetNextSibling();
3550 currentState
= AT_NODE
;
3552 currentNode
= currentNode
->GetParent();
3556 // Do not flush trailing line breaks! Required breaks at the end of the text
3560 template <typename SPT
, typename SRT
, typename EPT
, typename ERT
>
3561 void nsRange::CreateOrUpdateCrossShadowBoundaryRangeIfNeeded(
3562 const mozilla::RangeBoundaryBase
<SPT
, SRT
>& aStartBoundary
,
3563 const mozilla::RangeBoundaryBase
<EPT
, ERT
>& aEndBoundary
) {
3564 if (!StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
3568 MOZ_ASSERT(aStartBoundary
.IsSetAndValid() && aEndBoundary
.IsSetAndValid());
3570 nsINode
* startNode
= aStartBoundary
.Container();
3571 nsINode
* endNode
= aEndBoundary
.Container();
3573 if (!startNode
&& !endNode
) {
3574 ResetCrossShadowBoundaryRange();
3578 // Nodes at least needs to be in the same document.
3579 if (startNode
&& endNode
&&
3580 startNode
->GetComposedDoc() != endNode
->GetComposedDoc()) {
3584 auto CanBecomeCrossShadowBoundaryPoint
= [](nsINode
* aContainer
) -> bool {
3589 // Unlike normal ranges, shadow cross ranges don't work
3590 // when the nodes aren't in document.
3591 if (!aContainer
->IsInComposedDoc()) {
3595 // AbstractRange::GetClosestCommonInclusiveAncestor only supports
3596 // Document and Content nodes.
3597 return aContainer
->IsDocument() || aContainer
->IsContent();
3600 if (!CanBecomeCrossShadowBoundaryPoint(startNode
) ||
3601 !CanBecomeCrossShadowBoundaryPoint(endNode
)) {
3602 ResetCrossShadowBoundaryRange();
3606 if (!mCrossShadowBoundaryRange
) {
3607 mCrossShadowBoundaryRange
=
3608 CrossShadowBoundaryRange::Create(aStartBoundary
, aEndBoundary
, this);
3612 mCrossShadowBoundaryRange
->SetStartAndEnd(aStartBoundary
, aEndBoundary
);
3615 RawRangeBoundary
nsRange::ComputeNewBoundaryWhenBoundaryInsideChangedText(
3616 const CharacterDataChangeInfo
& aInfo
, const RawRangeBoundary
& aBoundary
) {
3617 MOZ_ASSERT(aInfo
.mChangeStart
<
3619 RawRangeBoundary::OffsetFilter::kValidOrInvalidOffsets
));
3620 // If boundary is inside changed text, position it before change
3621 // else adjust start offset for the change in length.
3622 CheckedUint32 newOffset
{0};
3623 if (*aBoundary
.Offset(
3624 RawRangeBoundary::OffsetFilter::kValidOrInvalidOffsets
) <=
3626 newOffset
= aInfo
.mChangeStart
;
3628 newOffset
= *aBoundary
.Offset(
3629 RawRangeBoundary::OffsetFilter::kValidOrInvalidOffsets
);
3630 newOffset
-= aInfo
.LengthOfRemovedText();
3631 newOffset
+= aInfo
.mReplaceLength
;
3634 // newOffset.isValid() isn't checked explicitly here, because
3635 // newOffset.value() contains an assertion.
3636 return {aBoundary
.Container(), newOffset
.value()};