AbstractRange.h (9621B)
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 #ifndef mozilla_dom_AbstractRange_h 8 #define mozilla_dom_AbstractRange_h 9 10 #include <cstdint> 11 #include <ostream> 12 13 #include "ErrorList.h" 14 #include "js/RootingAPI.h" 15 #include "mozilla/RangeBoundary.h" 16 #include "mozilla/RefPtr.h" 17 #include "mozilla/WeakPtr.h" 18 #include "nsCycleCollectionParticipant.h" 19 #include "nsISupports.h" 20 #include "nsWrapperCache.h" 21 22 class JSObject; 23 class nsIContent; 24 class nsINode; 25 class nsRange; 26 struct JSContext; 27 28 namespace mozilla::dom { 29 class Document; 30 class Selection; 31 class StaticRange; 32 class HTMLSlotElement; 33 34 enum class AllowRangeCrossShadowBoundary : bool { No, Yes }; 35 36 class AbstractRange : public nsISupports, 37 public nsWrapperCache, 38 // For linking together selection-associated ranges. 39 public mozilla::LinkedListElement<AbstractRange> { 40 using AllowRangeCrossShadowBoundary = 41 mozilla::dom::AllowRangeCrossShadowBoundary; 42 43 protected: 44 explicit AbstractRange(nsINode* aNode, bool aIsDynamicRange, 45 TreeKind aBoundaryTreeKind); 46 virtual ~AbstractRange(); 47 48 public: 49 enum class IsUnlinking : bool { No, Yes }; 50 51 AbstractRange() = delete; 52 explicit AbstractRange(const AbstractRange& aOther) = delete; 53 54 /** 55 * Called when the process is shutting down. 56 */ 57 static void Shutdown(); 58 59 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 60 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AbstractRange) 61 62 /** 63 * All of the MayCrossShadowBoundary* methods are used to get the boundary 64 * endpoints that cross shadow boundaries. They would return 65 * the same value as the non-MayCrossShadowBoundary* methods if the range 66 * boundaries don't cross shadow boundaries. 67 */ 68 const RangeBoundary& StartRef() const { return mStart; } 69 const RangeBoundary& MayCrossShadowBoundaryStartRef() const; 70 71 const RangeBoundary& EndRef() const { return mEnd; } 72 const RangeBoundary& MayCrossShadowBoundaryEndRef() const; 73 74 nsIContent* GetChildAtStartOffset() const { 75 return mStart.GetChildAtOffset(); 76 } 77 nsIContent* GetMayCrossShadowBoundaryChildAtStartOffset() const; 78 79 nsIContent* GetChildAtEndOffset() const { return mEnd.GetChildAtOffset(); } 80 nsIContent* GetMayCrossShadowBoundaryChildAtEndOffset() const; 81 82 bool IsPositioned() const { return mIsPositioned; } 83 /** 84 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor 85 */ 86 nsINode* GetClosestCommonInclusiveAncestor( 87 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary = 88 AllowRangeCrossShadowBoundary::No) const; 89 90 // WebIDL 91 92 // If Range is created from JS, it's initialized with Document.createRange() 93 // and it collaps the range to start of the Document. Therefore, the 94 // following WebIDL methods are called only when `mIsPositioned` is true. 95 // So, it does not make sense to take `ErrorResult` as their parameter 96 // since its destruction cost may appear in profile. If you create range 97 // object from C++ and needs to check whether it's positioned, should call 98 // `IsPositioned()` directly. 99 100 nsINode* GetStartContainer() const { return mStart.GetContainer(); } 101 nsINode* GetMayCrossShadowBoundaryStartContainer() const; 102 103 nsINode* GetEndContainer() const { return mEnd.GetContainer(); } 104 nsINode* GetMayCrossShadowBoundaryEndContainer() const; 105 106 /** 107 * Return GetStartContainer() and GetEndContainer() if this is positioned. 108 */ 109 [[nodiscard]] bool IsPositionedAndSameContainer() const { 110 return MOZ_LIKELY(mIsPositioned) && 111 mStart.GetContainer() == mEnd.GetContainer(); 112 } 113 /** 114 * Return GetMayCrossShadowBoundaryStartContainer() and 115 * GetMayCrossShadowBoundaryEndContainer() if this is positioned. 116 */ 117 [[nodiscard]] bool IsPositionedAndSameContainerMayCrossShadowBoundary() 118 const { 119 return MOZ_LIKELY(mIsPositioned) && 120 GetMayCrossShadowBoundaryStartContainer() == 121 GetMayCrossShadowBoundaryEndContainer(); 122 } 123 124 bool MayCrossShadowBoundary() const; 125 126 Document* GetComposedDocOfContainers() const { 127 return mStart.GetComposedDoc(); 128 } 129 130 // FYI: Returns 0 if it's not positioned. 131 uint32_t StartOffset() const { 132 return static_cast<uint32_t>( 133 *mStart.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)); 134 } 135 uint32_t MayCrossShadowBoundaryStartOffset() const; 136 137 // FYI: Returns 0 if it's not positioned. 138 uint32_t EndOffset() const { 139 return static_cast<uint32_t>( 140 *mEnd.Offset(RangeBoundary::OffsetFilter::kValidOrInvalidOffsets)); 141 } 142 uint32_t MayCrossShadowBoundaryEndOffset() const; 143 144 bool Collapsed() const { 145 return !mIsPositioned || (mStart.GetContainer() == mEnd.GetContainer() && 146 StartOffset() == EndOffset()); 147 } 148 149 bool AreNormalRangeAndCrossShadowBoundaryRangeCollapsed() const; 150 151 nsINode* GetParentObject() const; 152 virtual JSObject* WrapObject(JSContext* aCx, 153 JS::Handle<JSObject*> aGivenProto) override; 154 155 bool HasEqualBoundaries(const AbstractRange& aOther) const { 156 return (mStart == aOther.mStart) && (mEnd == aOther.mEnd); 157 } 158 bool IsDynamicRange() const { return mIsDynamicRange; } 159 bool IsStaticRange() const { return !mIsDynamicRange; } 160 inline nsRange* AsDynamicRange(); 161 inline const nsRange* AsDynamicRange() const; 162 inline StaticRange* AsStaticRange(); 163 inline const StaticRange* AsStaticRange() const; 164 165 /** 166 * Return true if this range is part of a Selection object 167 * and isn't detached. 168 */ 169 bool IsInAnySelection() const { return !mSelections.IsEmpty(); } 170 171 void RegisterSelection(mozilla::dom::Selection& aSelection); 172 173 void UnregisterSelection(const mozilla::dom::Selection& aSelection, 174 IsUnlinking aIsUnlinking = IsUnlinking::No); 175 176 /** 177 * Returns a list of all Selections the range is associated with. 178 */ 179 const nsTArray<WeakPtr<Selection>>& GetSelections() const; 180 181 /** 182 * Return true if this range is in |aSelection|. 183 */ 184 bool IsInSelection(const mozilla::dom::Selection& aSelection) const; 185 186 /** 187 * Return true if aRoot is a UA shadow root. 188 */ 189 static bool IsRootUAWidget(const nsINode* aRoot); 190 191 /** 192 * Return a shrunken range computed by 193 * SelectionMoveUtils::GetFirstVisiblePointAtLeaf() and 194 * SelectionMoveUtils::GetLastVisiblePointAtLeaf(). 195 */ 196 already_AddRefed<StaticRange> GetShrunkenRangeToVisibleLeaves() const; 197 198 protected: 199 template <typename SPT, typename SRT, typename EPT, typename ERT, 200 typename RangeType> 201 static nsresult SetStartAndEndInternal( 202 const RangeBoundaryBase<SPT, SRT>& aStartBoundary, 203 const RangeBoundaryBase<EPT, ERT>& aEndBoundary, RangeType* aRange, 204 AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary = 205 AllowRangeCrossShadowBoundary::No); 206 207 template <class RangeType> 208 static bool MaybeCacheToReuse(RangeType& aInstance); 209 210 void Init(nsINode* aNode); 211 212 friend std::ostream& operator<<(std::ostream& aStream, 213 const AbstractRange& aRange) { 214 if (aRange.Collapsed()) { 215 aStream << "{ mStart=mEnd=" << aRange.mStart; 216 } else { 217 aStream << "{ mStart=" << aRange.mStart << ", mEnd=" << aRange.mEnd; 218 } 219 return aStream << ", mIsGenerated=" 220 << (aRange.mIsGenerated ? "true" : "false") 221 << ", mCalledByJS=" 222 << (aRange.mIsPositioned ? "true" : "false") 223 << ", mIsDynamicRange=" 224 << (aRange.mIsDynamicRange ? "true" : "false") << " }"; 225 } 226 227 /** 228 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor 229 */ 230 void RegisterClosestCommonInclusiveAncestor(nsINode* aNode); 231 /** 232 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor 233 */ 234 void UnregisterClosestCommonInclusiveAncestor( 235 IsUnlinking aIsUnlinking = IsUnlinking::No); 236 237 void UpdateCommonAncestorIfNecessary(); 238 239 static void MarkDescendants(nsINode& aNode); 240 static void UnmarkDescendants(nsINode& aNode); 241 242 static void UpdateDescendantsInFlattenedTree(nsINode& aNode, 243 bool aMarkDescendants); 244 friend void mozilla::SlotAssignedNodeAdded(dom::HTMLSlotElement* aSlot, 245 nsIContent& aAssignedNode); 246 friend void mozilla::SlotAssignedNodeRemoved(dom::HTMLSlotElement* aSlot, 247 nsIContent& aUnassignedNode); 248 249 private: 250 void ClearForReuse(); 251 252 protected: 253 RefPtr<Document> mOwner; 254 RangeBoundary mStart; 255 RangeBoundary mEnd; 256 257 // A Range can be part of multiple |Selection|s. This is a very rare use case. 258 AutoTArray<WeakPtr<Selection>, 1> mSelections; 259 // mRegisteredClosestCommonInclusiveAncestor is only non-null when the range 260 // IsInAnySelection(). 261 nsCOMPtr<nsINode> mRegisteredClosestCommonInclusiveAncestor; 262 263 // `true` if `mStart` and `mEnd` are set for StaticRange or set and valid 264 // for nsRange. 265 bool mIsPositioned; 266 267 // Used by nsRange, but this should have this for minimizing the size. 268 bool mIsGenerated; 269 // Used by nsRange, but this should have this for minimizing the size. 270 bool mCalledByJS; 271 272 // true if this is an `nsRange` object. 273 const bool mIsDynamicRange; 274 275 static bool sHasShutDown; 276 }; 277 278 } // namespace mozilla::dom 279 280 #endif // #ifndef mozilla_dom_AbstractRange_h