michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Implementation of the DOM nsIDOMRange object. michael@0: */ michael@0: michael@0: #ifndef nsRange_h___ michael@0: #define nsRange_h___ michael@0: michael@0: #include "nsIDOMRange.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsINode.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "prmon.h" michael@0: #include "nsStubMutationObserver.h" michael@0: #include "nsWrapperCache.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: namespace mozilla { michael@0: class ErrorResult; michael@0: namespace dom { michael@0: class DocumentFragment; michael@0: class DOMRect; michael@0: class DOMRectList; michael@0: } michael@0: } michael@0: michael@0: class nsRange MOZ_FINAL : public nsIDOMRange, michael@0: public nsStubMutationObserver, michael@0: public nsWrapperCache michael@0: { michael@0: typedef mozilla::ErrorResult ErrorResult; michael@0: typedef mozilla::dom::DOMRect DOMRect; michael@0: typedef mozilla::dom::DOMRectList DOMRectList; michael@0: michael@0: public: michael@0: nsRange(nsINode* aNode) michael@0: : mRoot(nullptr) michael@0: , mStartOffset(0) michael@0: , mEndOffset(0) michael@0: , mIsPositioned(false) michael@0: , mIsDetached(false) michael@0: , mMaySpanAnonymousSubtrees(false) michael@0: , mInSelection(false) michael@0: , mStartOffsetWasIncremented(false) michael@0: , mEndOffsetWasIncremented(false) michael@0: , mEnableGravitationOnElementRemoval(true) michael@0: #ifdef DEBUG michael@0: , mAssertNextInsertOrAppendIndex(-1) michael@0: , mAssertNextInsertOrAppendNode(nullptr) michael@0: #endif michael@0: { michael@0: SetIsDOMBinding(); michael@0: MOZ_ASSERT(aNode, "range isn't in a document!"); michael@0: mOwner = aNode->OwnerDoc(); michael@0: } michael@0: virtual ~nsRange(); michael@0: michael@0: static nsresult CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset, michael@0: nsIDOMNode* aEndParent, int32_t aEndOffset, michael@0: nsRange** aRange); michael@0: static nsresult CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset, michael@0: nsIDOMNode* aEndParent, int32_t aEndOffset, michael@0: nsIDOMRange** aRange); michael@0: static nsresult CreateRange(nsINode* aStartParent, int32_t aStartOffset, michael@0: nsINode* aEndParent, int32_t aEndOffset, michael@0: nsRange** aRange); michael@0: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsRange, nsIDOMRange) michael@0: michael@0: /** michael@0: * The DOM Range spec requires that when a node is removed from its parent, michael@0: * and the node's subtree contains the start or end point of a range, that michael@0: * start or end point is moved up to where the node was removed from its michael@0: * parent. michael@0: * For some internal uses of Ranges it's useful to disable that behavior, michael@0: * so that a range of children within a single parent is preserved even if michael@0: * that parent is removed from the document tree. michael@0: */ michael@0: void SetEnableGravitationOnElementRemoval(bool aEnable) michael@0: { michael@0: mEnableGravitationOnElementRemoval = aEnable; michael@0: } michael@0: michael@0: // nsIDOMRange interface michael@0: NS_DECL_NSIDOMRANGE michael@0: michael@0: nsINode* GetRoot() const michael@0: { michael@0: return mRoot; michael@0: } michael@0: michael@0: nsINode* GetStartParent() const michael@0: { michael@0: return mStartParent; michael@0: } michael@0: michael@0: nsINode* GetEndParent() const michael@0: { michael@0: return mEndParent; michael@0: } michael@0: michael@0: int32_t StartOffset() const michael@0: { michael@0: return mStartOffset; michael@0: } michael@0: michael@0: int32_t EndOffset() const michael@0: { michael@0: return mEndOffset; michael@0: } michael@0: michael@0: bool IsPositioned() const michael@0: { michael@0: return mIsPositioned; michael@0: } michael@0: michael@0: void SetMaySpanAnonymousSubtrees(bool aMaySpanAnonymousSubtrees) michael@0: { michael@0: mMaySpanAnonymousSubtrees = aMaySpanAnonymousSubtrees; michael@0: } michael@0: michael@0: /** michael@0: * Return true iff this range is part of at least one Selection object michael@0: * and isn't detached. michael@0: */ michael@0: bool IsInSelection() const michael@0: { michael@0: return mInSelection; michael@0: } michael@0: michael@0: /** michael@0: * Called when the range is added/removed from a Selection. michael@0: */ michael@0: void SetInSelection(bool aInSelection) michael@0: { michael@0: if (mInSelection == aInSelection) { michael@0: return; michael@0: } michael@0: mInSelection = aInSelection; michael@0: nsINode* commonAncestor = GetCommonAncestor(); michael@0: NS_ASSERTION(commonAncestor, "unexpected disconnected nodes"); michael@0: if (mInSelection) { michael@0: RegisterCommonAncestor(commonAncestor); michael@0: } else { michael@0: UnregisterCommonAncestor(commonAncestor); michael@0: } michael@0: } michael@0: michael@0: nsINode* GetCommonAncestor() const; michael@0: void Reset(); michael@0: nsresult SetStart(nsINode* aParent, int32_t aOffset); michael@0: nsresult SetEnd(nsINode* aParent, int32_t aOffset); michael@0: already_AddRefed CloneRange() const; michael@0: michael@0: nsresult Set(nsINode* aStartParent, int32_t aStartOffset, michael@0: nsINode* aEndParent, int32_t aEndOffset) michael@0: { michael@0: // If this starts being hot, we may be able to optimize this a bit, michael@0: // but for now just set start and end separately. michael@0: nsresult rv = SetStart(aStartParent, aStartOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return SetEnd(aEndParent, aEndOffset); michael@0: } michael@0: michael@0: NS_IMETHOD GetUsedFontFaces(nsIDOMFontFaceList** aResult); michael@0: michael@0: // nsIMutationObserver methods michael@0: NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED michael@0: NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED michael@0: NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED michael@0: NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED michael@0: NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED michael@0: michael@0: // WebIDL michael@0: static already_AddRefed michael@0: Constructor(const mozilla::dom::GlobalObject& global, michael@0: mozilla::ErrorResult& aRv); michael@0: michael@0: bool Collapsed() const michael@0: { michael@0: return mIsPositioned && mStartParent == mEndParent && michael@0: mStartOffset == mEndOffset; michael@0: } michael@0: already_AddRefed michael@0: CreateContextualFragment(const nsAString& aString, ErrorResult& aError); michael@0: already_AddRefed michael@0: CloneContents(ErrorResult& aErr); michael@0: int16_t CompareBoundaryPoints(uint16_t aHow, nsRange& aOther, michael@0: ErrorResult& aErr); michael@0: int16_t ComparePoint(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr); michael@0: void DeleteContents(ErrorResult& aRv); michael@0: already_AddRefed michael@0: ExtractContents(ErrorResult& aErr); michael@0: nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const; michael@0: nsINode* GetStartContainer(ErrorResult& aRv) const; michael@0: uint32_t GetStartOffset(ErrorResult& aRv) const; michael@0: nsINode* GetEndContainer(ErrorResult& aRv) const; michael@0: uint32_t GetEndOffset(ErrorResult& aRv) const; michael@0: void InsertNode(nsINode& aNode, ErrorResult& aErr); michael@0: bool IntersectsNode(nsINode& aNode, ErrorResult& aRv); michael@0: bool IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr); michael@0: void SelectNode(nsINode& aNode, ErrorResult& aErr); michael@0: void SelectNodeContents(nsINode& aNode, ErrorResult& aErr); michael@0: void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); michael@0: void SetEndAfter(nsINode& aNode, ErrorResult& aErr); michael@0: void SetEndBefore(nsINode& aNode, ErrorResult& aErr); michael@0: void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); michael@0: void SetStartAfter(nsINode& aNode, ErrorResult& aErr); michael@0: void SetStartBefore(nsINode& aNode, ErrorResult& aErr); michael@0: void SurroundContents(nsINode& aNode, ErrorResult& aErr); michael@0: already_AddRefed GetBoundingClientRect(); michael@0: already_AddRefed GetClientRects(); michael@0: michael@0: nsINode* GetParentObject() const { return mOwner; } michael@0: virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE MOZ_FINAL; michael@0: michael@0: private: michael@0: // no copy's or assigns michael@0: nsRange(const nsRange&); michael@0: nsRange& operator=(const nsRange&); michael@0: michael@0: /** michael@0: * Cut or delete the range's contents. michael@0: * michael@0: * @param aFragment nsIDOMDocumentFragment containing the nodes. michael@0: * May be null to indicate the caller doesn't want a fragment. michael@0: */ michael@0: nsresult CutContents(mozilla::dom::DocumentFragment** frag); michael@0: michael@0: static nsresult CloneParentsBetween(nsINode* aAncestor, michael@0: nsINode* aNode, michael@0: nsINode** aClosestAncestor, michael@0: nsINode** aFarthestAncestor); michael@0: michael@0: public: michael@0: /****************************************************************************** michael@0: * Utility routine to detect if a content node starts before a range and/or michael@0: * ends after a range. If neither it is contained inside the range. michael@0: * michael@0: * XXX - callers responsibility to ensure node in same doc as range! michael@0: * michael@0: *****************************************************************************/ michael@0: static nsresult CompareNodeToRange(nsINode* aNode, nsRange* aRange, michael@0: bool *outNodeBefore, michael@0: bool *outNodeAfter); michael@0: michael@0: static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset, michael@0: uint32_t aEndOffset); michael@0: michael@0: typedef nsTHashtable > RangeHashTable; michael@0: protected: michael@0: void RegisterCommonAncestor(nsINode* aNode); michael@0: void UnregisterCommonAncestor(nsINode* aNode); michael@0: nsINode* IsValidBoundary(nsINode* aNode); michael@0: michael@0: // CharacterDataChanged set aNotInsertedYet to true to disable an assertion michael@0: // and suppress re-registering a range common ancestor node since michael@0: // the new text node of a splitText hasn't been inserted yet. michael@0: // CharacterDataChanged does the re-registering when needed. michael@0: void DoSetRange(nsINode* aStartN, int32_t aStartOffset, michael@0: nsINode* aEndN, int32_t aEndOffset, michael@0: nsINode* aRoot, bool aNotInsertedYet = false); michael@0: michael@0: /** michael@0: * For a range for which IsInSelection() is true, return the common michael@0: * ancestor for the range. This method uses the selection bits and michael@0: * nsGkAtoms::range property on the nodes to quickly find the ancestor. michael@0: * That is, it's a faster version of GetCommonAncestor that only works michael@0: * for ranges in a Selection. The method will assert and the behavior michael@0: * is undefined if called on a range where IsInSelection() is false. michael@0: */ michael@0: nsINode* GetRegisteredCommonAncestor(); michael@0: michael@0: struct MOZ_STACK_CLASS AutoInvalidateSelection michael@0: { michael@0: AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) michael@0: { michael@0: #ifdef DEBUG michael@0: mWasInSelection = mRange->IsInSelection(); michael@0: #endif michael@0: if (!mRange->IsInSelection() || mIsNested) { michael@0: return; michael@0: } michael@0: mIsNested = true; michael@0: mCommonAncestor = mRange->GetRegisteredCommonAncestor(); michael@0: } michael@0: ~AutoInvalidateSelection(); michael@0: nsRange* mRange; michael@0: nsRefPtr mCommonAncestor; michael@0: #ifdef DEBUG michael@0: bool mWasInSelection; michael@0: #endif michael@0: static bool mIsNested; michael@0: }; michael@0: michael@0: nsCOMPtr mOwner; michael@0: nsCOMPtr mRoot; michael@0: nsCOMPtr mStartParent; michael@0: nsCOMPtr mEndParent; michael@0: int32_t mStartOffset; michael@0: int32_t mEndOffset; michael@0: michael@0: bool mIsPositioned; michael@0: bool mIsDetached; michael@0: bool mMaySpanAnonymousSubtrees; michael@0: bool mInSelection; michael@0: bool mStartOffsetWasIncremented; michael@0: bool mEndOffsetWasIncremented; michael@0: bool mEnableGravitationOnElementRemoval; michael@0: #ifdef DEBUG michael@0: int32_t mAssertNextInsertOrAppendIndex; michael@0: nsINode* mAssertNextInsertOrAppendNode; michael@0: #endif michael@0: }; michael@0: michael@0: inline nsISupports* michael@0: ToCanonicalSupports(nsRange* aRange) michael@0: { michael@0: return static_cast(aRange); michael@0: } michael@0: michael@0: inline nsISupports* michael@0: ToSupports(nsRange* aRange) michael@0: { michael@0: return static_cast(aRange); michael@0: } michael@0: michael@0: #endif /* nsRange_h___ */