CrossShadowBoundaryRange.cpp (10995B)
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/. */ 6 7 #include "mozilla/dom/CrossShadowBoundaryRange.h" 8 9 #include "nsContentUtils.h" 10 #include "nsIContentInlines.h" 11 #include "nsINode.h" 12 #include "nsRange.h" 13 14 namespace mozilla::dom { 15 template already_AddRefed<CrossShadowBoundaryRange> 16 CrossShadowBoundaryRange::Create(const RangeBoundary& aStartBoundary, 17 const RangeBoundary& aEndBoundary, 18 nsRange* aOwner); 19 template already_AddRefed<CrossShadowBoundaryRange> 20 CrossShadowBoundaryRange::Create(const RangeBoundary& aStartBoundary, 21 const RawRangeBoundary& aEndBoundary, 22 nsRange* aOwner); 23 template already_AddRefed<CrossShadowBoundaryRange> 24 CrossShadowBoundaryRange::Create(const RawRangeBoundary& aStartBoundary, 25 const RangeBoundary& aEndBoundary, 26 nsRange* aOwner); 27 template already_AddRefed<CrossShadowBoundaryRange> 28 CrossShadowBoundaryRange::Create(const RawRangeBoundary& aStartBoundary, 29 const RawRangeBoundary& aEndBoundary, 30 nsRange* aOwner); 31 32 template void CrossShadowBoundaryRange::DoSetRange( 33 const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary, 34 nsINode* aRootNode, nsRange* aOwner); 35 template void CrossShadowBoundaryRange::DoSetRange( 36 const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary, 37 nsINode* aRootNode, nsRange* aOwner); 38 template void CrossShadowBoundaryRange::DoSetRange( 39 const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary, 40 nsINode* aRootNode, nsRange* aOwner); 41 template void CrossShadowBoundaryRange::DoSetRange( 42 const RawRangeBoundary& aStartBoundary, 43 const RawRangeBoundary& aEndBoundary, nsINode* aRootNode, nsRange* aOwner); 44 45 template nsresult CrossShadowBoundaryRange::SetStartAndEnd( 46 const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary); 47 template nsresult CrossShadowBoundaryRange::SetStartAndEnd( 48 const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary); 49 template nsresult CrossShadowBoundaryRange::SetStartAndEnd( 50 const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary); 51 template nsresult CrossShadowBoundaryRange::SetStartAndEnd( 52 const RawRangeBoundary& aStartBoundary, 53 const RawRangeBoundary& aEndBoundary); 54 55 nsTArray<RefPtr<CrossShadowBoundaryRange>>* 56 CrossShadowBoundaryRange::sCachedRanges = nullptr; 57 58 NS_IMPL_CYCLE_COLLECTING_ADDREF(CrossShadowBoundaryRange) 59 60 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE( 61 CrossShadowBoundaryRange, 62 DoSetRange(RawRangeBoundary(TreeKind::Flat), 63 RawRangeBoundary(TreeKind::Flat), nullptr, nullptr), 64 AbstractRange::MaybeCacheToReuse(*this)) 65 66 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CrossShadowBoundaryRange) 67 NS_INTERFACE_MAP_END_INHERITING(CrossShadowBoundaryRange) 68 69 NS_IMPL_CYCLE_COLLECTION_CLASS(CrossShadowBoundaryRange) 70 71 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CrossShadowBoundaryRange, 72 StaticRange) 73 if (tmp->mCommonAncestor) { 74 tmp->mCommonAncestor->RemoveMutationObserver(tmp); 75 } 76 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommonAncestor) 77 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 78 79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CrossShadowBoundaryRange, 80 StaticRange) 81 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommonAncestor) 82 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 83 84 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CrossShadowBoundaryRange, 85 StaticRange) 86 NS_IMPL_CYCLE_COLLECTION_TRACE_END 87 88 /* static */ 89 template <typename SPT, typename SRT, typename EPT, typename ERT> 90 already_AddRefed<CrossShadowBoundaryRange> CrossShadowBoundaryRange::Create( 91 const RangeBoundaryBase<SPT, SRT>& aStartBoundary, 92 const RangeBoundaryBase<EPT, ERT>& aEndBoundary, nsRange* aOwner) { 93 RefPtr<CrossShadowBoundaryRange> range; 94 if (!sCachedRanges || sCachedRanges->IsEmpty()) { 95 range = new CrossShadowBoundaryRange(aStartBoundary.GetContainer(), aOwner); 96 } else { 97 range = sCachedRanges->PopLastElement().forget(); 98 } 99 100 range->Init(aStartBoundary.GetContainer()); 101 range->DoSetRange(aStartBoundary, aEndBoundary, nullptr, aOwner); 102 return range.forget(); 103 } 104 105 template <typename SPT, typename SRT, typename EPT, typename ERT> 106 void CrossShadowBoundaryRange::DoSetRange( 107 const RangeBoundaryBase<SPT, SRT>& aStartBoundary, 108 const RangeBoundaryBase<EPT, ERT>& aEndBoundary, nsINode* aRootNode, 109 nsRange* aOwner) { 110 // aRootNode is useless to CrossShadowBoundaryRange because aStartBoundary 111 // and aEndBoundary could have different roots. 112 StaticRange::DoSetRange(aStartBoundary, aEndBoundary, nullptr); 113 114 nsINode* startRoot = RangeUtils::ComputeRootNode(mStart.GetContainer()); 115 nsINode* endRoot = RangeUtils::ComputeRootNode(mEnd.GetContainer()); 116 117 nsINode* previousCommonAncestor = mCommonAncestor; 118 mCommonAncestor = 119 startRoot == endRoot 120 ? startRoot 121 : nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor( 122 mStart.GetContainer(), mEnd.GetContainer()); 123 MOZ_ASSERT_IF(mOwner, mOwner == aOwner || !aOwner); 124 mOwner = aOwner; 125 126 if (previousCommonAncestor != mCommonAncestor) { 127 if (previousCommonAncestor) { 128 previousCommonAncestor->RemoveMutationObserver(this); 129 } 130 if (mCommonAncestor) { 131 mCommonAncestor->AddMutationObserver(this); 132 } 133 } 134 } 135 void CrossShadowBoundaryRange::ContentWillBeRemoved(nsIContent* aChild, 136 const ContentRemoveInfo&) { 137 // It's unclear from the spec about what should the selection be after 138 // DOM mutation. See https://github.com/w3c/selection-api/issues/168 139 // 140 // For now, we just clear the selection if the removed node is related 141 // to mStart or mEnd. 142 MOZ_DIAGNOSTIC_ASSERT(mOwner); 143 MOZ_DIAGNOSTIC_ASSERT(mOwner->GetCrossShadowBoundaryRange() == this); 144 145 RefPtr<CrossShadowBoundaryRange> kungFuDeathGrip(this); 146 147 const nsINode* startContainer = mStart.GetContainer(); 148 const nsINode* endContainer = mEnd.GetContainer(); 149 MOZ_ASSERT(startContainer && endContainer); 150 151 if (startContainer == aChild || endContainer == aChild) { 152 mOwner->ResetCrossShadowBoundaryRange(); 153 return; 154 } 155 156 // This is a special case that the startContainer and endContainer could 157 // anonymous contents created by the frame of aChild, and they are 158 // unbounded from the document now. 159 if (!startContainer->IsInComposedDoc() || !endContainer->IsInComposedDoc()) { 160 mOwner->ResetCrossShadowBoundaryRange(); 161 return; 162 } 163 164 if (const auto* shadowRoot = aChild->GetShadowRoot()) { 165 if (startContainer == shadowRoot || endContainer == shadowRoot) { 166 mOwner->ResetCrossShadowBoundaryRange(); 167 return; 168 } 169 } 170 171 if (startContainer->IsShadowIncludingInclusiveDescendantOf(aChild) || 172 endContainer->IsShadowIncludingInclusiveDescendantOf(aChild)) { 173 mOwner->ResetCrossShadowBoundaryRange(); 174 return; 175 } 176 177 nsINode* container = aChild->GetParentNode(); 178 179 auto MaybeCreateNewBoundary = 180 [container, aChild]( 181 const nsINode* aContainer, 182 const RangeBoundary& aBoundary) -> Maybe<RawRangeBoundary> { 183 if (container == aContainer) { 184 // We're only interested if our boundary reference was removed, otherwise 185 // we can just invalidate the offset. 186 if (aChild == aBoundary.Ref()) { 187 return Some<RawRangeBoundary>( 188 {container, aChild->GetPreviousSibling(), TreeKind::Flat}); 189 } 190 RawRangeBoundary newBoundary(TreeKind::Flat); 191 newBoundary.CopyFrom(aBoundary, RangeBoundaryIsMutationObserved::Yes); 192 newBoundary.InvalidateOffset(); 193 return Some(newBoundary); 194 } 195 return Nothing(); 196 }; 197 198 const Maybe<RawRangeBoundary> newStartBoundary = 199 MaybeCreateNewBoundary(startContainer, mStart); 200 const Maybe<RawRangeBoundary> newEndBoundary = 201 MaybeCreateNewBoundary(endContainer, mEnd); 202 203 if (newStartBoundary || newEndBoundary) { 204 DoSetRange(newStartBoundary ? newStartBoundary.ref() : mStart.AsRaw(), 205 newEndBoundary ? newEndBoundary.ref() : mEnd.AsRaw(), nullptr, 206 mOwner); 207 } 208 } 209 210 // For now CrossShadowBoundaryRange::CharacterDataChanged is only meant 211 // to handle the character removal initiated by nsRange::CutContents. 212 void CrossShadowBoundaryRange::CharacterDataChanged( 213 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { 214 // When aInfo.mDetails is present, it means the character data was 215 // changed due to splitText() or normalize(), which shouldn't be the 216 // case for nsRange::CutContents, so we return early. 217 if (aInfo.mDetails) { 218 return; 219 } 220 MOZ_ASSERT(aContent); 221 MOZ_ASSERT(mIsPositioned); 222 223 auto MaybeCreateNewBoundary = 224 [aContent, 225 &aInfo](const RangeBoundary& aBoundary) -> Maybe<RawRangeBoundary> { 226 // If the changed node contains our start boundary and the change starts 227 // before the boundary we'll need to adjust the offset. 228 if (aContent == aBoundary.GetContainer() && 229 // aInfo.mChangeStart is the offset where the change starts, if it's 230 // smaller than the offset of aBoundary, it means the characters 231 // before the selected content is changed (i.e, removed), so the 232 // offset of aBoundary needs to be adjusted. 233 aInfo.mChangeStart < 234 *aBoundary.Offset( 235 RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)) { 236 RawRangeBoundary newStart = 237 nsRange::ComputeNewBoundaryWhenBoundaryInsideChangedText( 238 aInfo, aBoundary.AsRaw()); 239 return Some(newStart.AsRangeBoundaryInFlatTree()); 240 } 241 return Nothing(); 242 }; 243 244 const Maybe<RawRangeBoundary> newStartBoundary = 245 MaybeCreateNewBoundary(mStart); 246 const Maybe<RawRangeBoundary> newEndBoundary = MaybeCreateNewBoundary(mEnd); 247 248 if (newStartBoundary || newEndBoundary) { 249 DoSetRange(newStartBoundary ? newStartBoundary.ref() : mStart.AsRaw(), 250 newEndBoundary ? newEndBoundary.ref() : mEnd.AsRaw(), nullptr, 251 mOwner); 252 } 253 } 254 255 // DOM mutation for shadow-crossing selection is not specified. 256 // Spec issue: https://github.com/w3c/selection-api/issues/168 257 void CrossShadowBoundaryRange::ParentChainChanged(nsIContent* aContent) { 258 MOZ_DIAGNOSTIC_ASSERT(mCommonAncestor == aContent, 259 "Wrong ParentChainChanged notification"); 260 MOZ_DIAGNOSTIC_ASSERT(mOwner); 261 mOwner->ResetCrossShadowBoundaryRange(); 262 } 263 } // namespace mozilla::dom