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 /* 8 * Implementation of the DOM Range object. 9 */ 10 11 #ifndef nsRange_h___ 12 #define nsRange_h___ 13 14 #include "nsCOMPtr.h" 15 #include "mozilla/dom/AbstractRange.h" 16 #include "prmon.h" 17 #include "nsStubMutationObserver.h" 18 #include "nsWrapperCache.h" 19 #include "mozilla/Attributes.h" 20 #include "mozilla/ErrorResult.h" 21 #include "mozilla/LinkedList.h" 22 #include "mozilla/RangeBoundary.h" 23 #include "mozilla/WeakPtr.h" 24 25 namespace mozilla { 26 class RectCallback; 27 namespace dom { 28 struct ClientRectsAndTexts; 29 class DocGroup; 30 class DocumentFragment; 31 class DOMRect; 32 class DOMRectList; 33 class InspectorFontFace; 34 class Selection; 35 } // namespace dom 36 } // namespace mozilla 37 38 class nsRange final : public mozilla::dom::AbstractRange, 39 public nsStubMutationObserver, 40 // For linking together selection-associated ranges. 41 public mozilla::LinkedListElement<nsRange> { 42 using ErrorResult = mozilla::ErrorResult; 43 using AbstractRange = mozilla::dom::AbstractRange; 44 using DocGroup = mozilla::dom::DocGroup; 45 using DOMRect = mozilla::dom::DOMRect; 46 using DOMRectList = mozilla::dom::DOMRectList; 47 using RangeBoundary = mozilla::RangeBoundary; 48 using RawRangeBoundary = mozilla::RawRangeBoundary; 49 50 virtual ~nsRange(); 51 explicit nsRange(nsINode* aNode); 52 53 public: 54 /** 55 * The following Create() returns `nsRange` instance which is initialized 56 * only with aNode. The result is never positioned. 57 */ 58 static already_AddRefed<nsRange> Create(nsINode* aNode); 59 60 /** 61 * The following Create() may return `nsRange` instance which is initialized 62 * with given range or points. If it fails initializing new range with the 63 * arguments, returns `nullptr`. `ErrorResult` is set to an error only 64 * when this returns `nullptr`. The error code indicates the reason why 65 * it couldn't initialize the instance. 66 */ Create(const AbstractRange * aAbstractRange,ErrorResult & aRv)67 static already_AddRefed<nsRange> Create(const AbstractRange* aAbstractRange, 68 ErrorResult& aRv) { 69 return nsRange::Create(aAbstractRange->StartRef(), aAbstractRange->EndRef(), 70 aRv); 71 } Create(nsINode * aStartContainer,uint32_t aStartOffset,nsINode * aEndContainer,uint32_t aEndOffset,ErrorResult & aRv)72 static already_AddRefed<nsRange> Create(nsINode* aStartContainer, 73 uint32_t aStartOffset, 74 nsINode* aEndContainer, 75 uint32_t aEndOffset, 76 ErrorResult& aRv) { 77 return nsRange::Create(RawRangeBoundary(aStartContainer, aStartOffset), 78 RawRangeBoundary(aEndContainer, aEndOffset), aRv); 79 } 80 template <typename SPT, typename SRT, typename EPT, typename ERT> 81 static already_AddRefed<nsRange> Create( 82 const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary, 83 const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary, 84 ErrorResult& aRv); 85 86 NS_DECL_ISUPPORTS_INHERITED 87 NS_IMETHODIMP_(void) DeleteCycleCollectable(void) override; NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsRange,AbstractRange)88 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsRange, AbstractRange) 89 90 nsrefcnt GetRefCount() const { return mRefCnt; } 91 GetRoot()92 nsINode* GetRoot() const { return mRoot; } 93 94 /** 95 * Return true iff this range is part of a Selection object 96 * and isn't detached. 97 */ IsInSelection()98 bool IsInSelection() const { return !!mSelection; } 99 100 MOZ_CAN_RUN_SCRIPT void RegisterSelection( 101 mozilla::dom::Selection& aSelection); 102 103 void UnregisterSelection(); 104 105 /** 106 * Returns pointer to a Selection if the range is associated with a Selection. 107 */ 108 mozilla::dom::Selection* GetSelection() const; 109 110 /** 111 * Return true if this range was generated. 112 * @see SetIsGenerated 113 */ IsGenerated()114 bool IsGenerated() const { return mIsGenerated; } 115 116 /** 117 * Mark this range as being generated or not. 118 * Currently it is used for marking ranges that are created when splitting up 119 * a range to exclude a -moz-user-select:none region. 120 * @see Selection::AddRangesForSelectableNodes 121 * @see ExcludeNonSelectableNodes 122 */ SetIsGenerated(bool aIsGenerated)123 void SetIsGenerated(bool aIsGenerated) { mIsGenerated = aIsGenerated; } 124 125 void Reset(); 126 127 /** 128 * SetStart() and SetEnd() sets start point or end point separately. 129 * However, this is expensive especially when it's a range of Selection. 130 * When you set both start and end of a range, you should use 131 * SetStartAndEnd() instead. 132 */ SetStart(nsINode * aContainer,uint32_t aOffset)133 nsresult SetStart(nsINode* aContainer, uint32_t aOffset) { 134 ErrorResult error; 135 SetStart(RawRangeBoundary(aContainer, aOffset), error); 136 return error.StealNSResult(); 137 } SetEnd(nsINode * aContainer,uint32_t aOffset)138 nsresult SetEnd(nsINode* aContainer, uint32_t aOffset) { 139 ErrorResult error; 140 SetEnd(RawRangeBoundary(aContainer, aOffset), error); 141 return error.StealNSResult(); 142 } 143 144 already_AddRefed<nsRange> CloneRange() const; 145 146 /** 147 * SetStartAndEnd() works similar to call both SetStart() and SetEnd(). 148 * Different from calls them separately, this does nothing if either 149 * the start point or the end point is invalid point. 150 * If the specified start point is after the end point, the range will be 151 * collapsed at the end point. Similarly, if they are in different root, 152 * the range will be collapsed at the end point. 153 */ SetStartAndEnd(nsINode * aStartContainer,uint32_t aStartOffset,nsINode * aEndContainer,uint32_t aEndOffset)154 nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset, 155 nsINode* aEndContainer, uint32_t aEndOffset) { 156 return SetStartAndEnd(RawRangeBoundary(aStartContainer, aStartOffset), 157 RawRangeBoundary(aEndContainer, aEndOffset)); 158 } 159 template <typename SPT, typename SRT, typename EPT, typename ERT> SetStartAndEnd(const mozilla::RangeBoundaryBase<SPT,SRT> & aStartBoundary,const mozilla::RangeBoundaryBase<EPT,ERT> & aEndBoundary)160 nsresult SetStartAndEnd( 161 const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary, 162 const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary) { 163 return AbstractRange::SetStartAndEndInternal(aStartBoundary, aEndBoundary, 164 this); 165 } 166 167 /** 168 * Adds all nodes between |aStartContent| and |aEndContent| to the range. 169 * The start offset will be set before |aStartContent|, 170 * while the end offset will be set immediately after |aEndContent|. 171 * 172 * Caller must guarantee both nodes are non null and 173 * children of |aContainer| and that |aEndContent| is after |aStartContent|. 174 */ 175 void SelectNodesInContainer(nsINode* aContainer, nsIContent* aStartContent, 176 nsIContent* aEndContent); 177 178 /** 179 * CollapseTo() works similar to call both SetStart() and SetEnd() with 180 * same node and offset. This just calls SetStartAndParent() to set 181 * collapsed range at aContainer and aOffset. 182 */ CollapseTo(nsINode * aContainer,uint32_t aOffset)183 nsresult CollapseTo(nsINode* aContainer, uint32_t aOffset) { 184 return CollapseTo(RawRangeBoundary(aContainer, aOffset)); 185 } CollapseTo(const RawRangeBoundary & aPoint)186 nsresult CollapseTo(const RawRangeBoundary& aPoint) { 187 return SetStartAndEnd(aPoint, aPoint); 188 } 189 190 // aMaxRanges is the maximum number of text ranges to record for each face 191 // (pass 0 to just get the list of faces, without recording exact ranges 192 // where each face was used). 193 nsresult GetUsedFontFaces( 194 nsTArray<mozilla::UniquePtr<mozilla::dom::InspectorFontFace>>& aResult, 195 uint32_t aMaxRanges, bool aSkipCollapsedWhitespace); 196 197 // nsIMutationObserver methods 198 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED 199 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED 200 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED 201 NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED 202 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED 203 204 // WebIDL 205 static already_AddRefed<nsRange> Constructor( 206 const mozilla::dom::GlobalObject& global, mozilla::ErrorResult& aRv); 207 208 already_AddRefed<mozilla::dom::DocumentFragment> CreateContextualFragment( 209 const nsAString& aString, ErrorResult& aError) const; 210 already_AddRefed<mozilla::dom::DocumentFragment> CloneContents( 211 ErrorResult& aErr); 212 int16_t CompareBoundaryPoints(uint16_t aHow, const nsRange& aOtherRange, 213 ErrorResult& aRv); 214 int16_t ComparePoint(const nsINode& aContainer, uint32_t aOffset, 215 ErrorResult& aRv) const; 216 void DeleteContents(ErrorResult& aRv); 217 already_AddRefed<mozilla::dom::DocumentFragment> ExtractContents( 218 ErrorResult& aErr); GetCommonAncestorContainer(ErrorResult & aRv)219 nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const { 220 if (!mIsPositioned) { 221 aRv.Throw(NS_ERROR_NOT_INITIALIZED); 222 return nullptr; 223 } 224 return GetClosestCommonInclusiveAncestor(); 225 } 226 void InsertNode(nsINode& aNode, ErrorResult& aErr); 227 bool IntersectsNode(nsINode& aNode, ErrorResult& aRv); 228 bool IsPointInRange(const nsINode& aContainer, uint32_t aOffset, 229 ErrorResult& aRv) const; 230 void ToString(nsAString& aReturn, ErrorResult& aErr); 231 void Detach(); 232 233 // *JS() methods are mapped to Range.*() of DOM. 234 // They may move focus only when the range represents normal selection. 235 // These methods shouldn't be used from internal. 236 void CollapseJS(bool aToStart); 237 void SelectNodeJS(nsINode& aNode, ErrorResult& aErr); 238 void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr); 239 void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); 240 void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr); 241 void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr); 242 void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); 243 void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr); 244 void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr); 245 246 void SurroundContents(nsINode& aNode, ErrorResult& aErr); 247 already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true, 248 bool aFlushLayout = true); 249 already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true, 250 bool aFlushLayout = true); 251 void GetClientRectsAndTexts(mozilla::dom::ClientRectsAndTexts& aResult, 252 ErrorResult& aErr); 253 254 // Following methods should be used for internal use instead of *JS(). 255 void SelectNode(nsINode& aNode, ErrorResult& aErr); 256 void SelectNodeContents(nsINode& aNode, ErrorResult& aErr); 257 void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); 258 void SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aErr); 259 void SetEndAfter(nsINode& aNode, ErrorResult& aErr); 260 void SetEndBefore(nsINode& aNode, ErrorResult& aErr); 261 void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); 262 void SetStart(const RawRangeBoundary& aPoint, ErrorResult& aErr); 263 void SetStartAfter(nsINode& aNode, ErrorResult& aErr); 264 void SetStartBefore(nsINode& aNode, ErrorResult& aErr); 265 void Collapse(bool aToStart); 266 267 static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue, 268 mozilla::ErrorResult& aError, 269 nsIContent* aContainer); 270 271 virtual JSObject* WrapObject(JSContext* cx, 272 JS::Handle<JSObject*> aGivenProto) final; 273 DocGroup* GetDocGroup() const; 274 275 private: 276 // no copy's or assigns 277 nsRange(const nsRange&); 278 nsRange& operator=(const nsRange&); 279 280 template <typename SPT, typename SRT, typename EPT, typename ERT> 281 static void AssertIfMismatchRootAndRangeBoundaries( 282 const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary, 283 const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary, 284 const nsINode* aRootNode, bool aNotInsertedYet = false); 285 286 /** 287 * Cut or delete the range's contents. 288 * 289 * @param aFragment DocumentFragment containing the nodes. 290 * May be null to indicate the caller doesn't want a 291 * fragment. 292 * @param aRv The error if any. 293 */ 294 void CutContents(mozilla::dom::DocumentFragment** aFragment, 295 ErrorResult& aRv); 296 297 static nsresult CloneParentsBetween(nsINode* aAncestor, nsINode* aNode, 298 nsINode** aClosestAncestor, 299 nsINode** aFarthestAncestor); 300 301 /** 302 * Returns whether a node is safe to be accessed by the current caller. 303 */ 304 bool CanAccess(const nsINode&) const; 305 306 void AdjustNextRefsOnCharacterDataSplit(const nsIContent& aContent, 307 const CharacterDataChangeInfo& aInfo); 308 309 struct RangeBoundariesAndRoot { 310 RawRangeBoundary mStart; 311 RawRangeBoundary mEnd; 312 nsINode* mRoot = nullptr; 313 }; 314 315 /** 316 * @param aContent Must be non-nullptr. 317 */ 318 RangeBoundariesAndRoot DetermineNewRangeBoundariesAndRootOnCharacterDataMerge( 319 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const; 320 321 // @return true iff the range is positioned, aContainer belongs to the same 322 // document as the range, aContainer is a DOCUMENT_TYPE_NODE and 323 // aOffset doesn't exceed aContainer's length. 324 bool IsPointComparableToRange(const nsINode& aContainer, uint32_t aOffset, 325 ErrorResult& aErrorResult) const; 326 327 public: 328 /** 329 * This helper function gets rects and correlated text for the given range. 330 * @param aTextList optional where nullptr = don't retrieve text 331 */ 332 static void CollectClientRectsAndText( 333 mozilla::RectCallback* aCollector, 334 mozilla::dom::Sequence<nsString>* aTextList, nsRange* aRange, 335 nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer, 336 uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout); 337 338 /** 339 * Scan this range for -moz-user-select:none nodes and split it up into 340 * multiple ranges to exclude those nodes. The resulting ranges are put 341 * in aOutRanges. If no -moz-user-select:none node is found in the range 342 * then |this| is unmodified and is the only range in aOutRanges. 343 * Otherwise, |this| will be modified so that it ends before the first 344 * -moz-user-select:none node and additional ranges may also be created. 345 * If all nodes in the range are -moz-user-select:none then aOutRanges 346 * will be empty. 347 * @param aOutRanges the resulting set of ranges 348 */ 349 void ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges); 350 351 /** 352 * Notify the selection listeners after a range has been modified. 353 */ 354 MOZ_CAN_RUN_SCRIPT void NotifySelectionListenersAfterRangeSet(); 355 356 protected: 357 /** 358 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor 359 */ 360 void RegisterClosestCommonInclusiveAncestor(nsINode* aNode); 361 /** 362 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor 363 */ 364 void UnregisterClosestCommonInclusiveAncestor(nsINode* aNode, 365 bool aIsUnlinking); 366 367 /** 368 * DoSetRange() is called when `AbstractRange::SetStartAndEndInternal()` sets 369 * mStart and mEnd, or some other internal methods modify `mStart` and/or 370 * `mEnd`. Therefore, this shouldn't be a virtual method. 371 * 372 * @param aStartBoundary Computed start point. This must equals or be 373 * before aEndBoundary in the DOM tree order. 374 * @param aEndBoundary Computed end point. 375 * @param aRootNode The root node. 376 * @param aNotInsertedYet true if this is called by CharacterDataChanged() 377 * to disable assertion and suppress re-registering 378 * a range common ancestor node since the new text 379 * node of a splitText hasn't been inserted yet. 380 * CharacterDataChanged() does the re-registering 381 * when needed. Otherwise, false. 382 */ 383 template <typename SPT, typename SRT, typename EPT, typename ERT> 384 MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoSetRange( 385 const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary, 386 const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary, 387 nsINode* aRootNode, bool aNotInsertedYet = false); 388 389 /** 390 * For a range for which IsInSelection() is true, return the closest common 391 * inclusive ancestor 392 * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor) 393 * for the range, which we had to compute when the common ancestor changed or 394 * IsInSelection became true, so we could register with it. That is, it's a 395 * faster version of GetClosestCommonInclusiveAncestor that only works for 396 * ranges in a Selection. The method will assert and the behavior is undefined 397 * if called on a range where IsInSelection() is false. 398 */ 399 nsINode* GetRegisteredClosestCommonInclusiveAncestor(); 400 401 // Assume that this is guaranteed that this is held by the caller when 402 // this is used. (Note that we cannot use AutoRestore for mCalledByJS 403 // due to a bit field.) 404 class MOZ_RAII AutoCalledByJSRestore final { 405 private: 406 nsRange& mRange; 407 bool mOldValue; 408 409 public: AutoCalledByJSRestore(nsRange & aRange)410 explicit AutoCalledByJSRestore(nsRange& aRange) 411 : mRange(aRange), mOldValue(aRange.mCalledByJS) {} ~AutoCalledByJSRestore()412 ~AutoCalledByJSRestore() { mRange.mCalledByJS = mOldValue; } SavedValue()413 bool SavedValue() const { return mOldValue; } 414 }; 415 416 struct MOZ_STACK_CLASS AutoInvalidateSelection { AutoInvalidateSelectionAutoInvalidateSelection417 explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) { 418 if (!mRange->IsInSelection() || sIsNested) { 419 return; 420 } 421 sIsNested = true; 422 mCommonAncestor = mRange->GetRegisteredClosestCommonInclusiveAncestor(); 423 } 424 ~AutoInvalidateSelection(); 425 nsRange* mRange; 426 RefPtr<nsINode> mCommonAncestor; 427 static bool sIsNested; 428 }; 429 430 bool MaybeInterruptLastRelease(); 431 432 #ifdef DEBUG IsCleared()433 bool IsCleared() const { 434 return !mRoot && !mRegisteredClosestCommonInclusiveAncestor && 435 !mSelection && !mNextStartRef && !mNextEndRef; 436 } 437 #endif // #ifdef DEBUG 438 439 nsCOMPtr<nsINode> mRoot; 440 // mRegisteredClosestCommonInclusiveAncestor is only non-null when the range 441 // IsInSelection(). It's kept alive via mStart/mEnd, 442 // because we update it any time those could become disconnected from it. 443 nsINode* MOZ_NON_OWNING_REF mRegisteredClosestCommonInclusiveAncestor; 444 mozilla::WeakPtr<mozilla::dom::Selection> mSelection; 445 446 // These raw pointers are used to remember a child that is about 447 // to be inserted between a CharacterData call and a subsequent 448 // ContentInserted or ContentAppended call. It is safe to store 449 // these refs because the caller is guaranteed to trigger both 450 // notifications while holding a strong reference to the new child. 451 nsIContent* MOZ_NON_OWNING_REF mNextStartRef; 452 nsIContent* MOZ_NON_OWNING_REF mNextEndRef; 453 454 static nsTArray<RefPtr<nsRange>>* sCachedRanges; 455 456 friend class mozilla::dom::AbstractRange; 457 }; 458 459 #endif /* nsRange_h___ */ 460