diff options
Diffstat (limited to 'dom/base/nsRange.h')
-rw-r--r-- | dom/base/nsRange.h | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h new file mode 100644 index 000000000..4b35c749a --- /dev/null +++ b/dom/base/nsRange.h @@ -0,0 +1,380 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Implementation of the DOM nsIDOMRange object. + */ + +#ifndef nsRange_h___ +#define nsRange_h___ + +#include "nsIDOMRange.h" +#include "nsCOMPtr.h" +#include "nsINode.h" +#include "nsIDocument.h" +#include "nsIDOMNode.h" +#include "nsLayoutUtils.h" +#include "prmon.h" +#include "nsStubMutationObserver.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +class ErrorResult; +namespace dom { +struct ClientRectsAndTexts; +class DocumentFragment; +class DOMRect; +class DOMRectList; +class Selection; +} // namespace dom +} // namespace mozilla + +class nsRange final : public nsIDOMRange, + public nsStubMutationObserver, + public nsWrapperCache +{ + typedef mozilla::ErrorResult ErrorResult; + typedef mozilla::dom::DOMRect DOMRect; + typedef mozilla::dom::DOMRectList DOMRectList; + + virtual ~nsRange(); + +public: + explicit nsRange(nsINode* aNode); + + static nsresult CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset, + nsIDOMNode* aEndParent, int32_t aEndOffset, + nsRange** aRange); + static nsresult CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset, + nsIDOMNode* aEndParent, int32_t aEndOffset, + nsIDOMRange** aRange); + static nsresult CreateRange(nsINode* aStartParent, int32_t aStartOffset, + nsINode* aEndParent, int32_t aEndOffset, + nsRange** aRange); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsRange, nsIDOMRange) + + /** + * The DOM Range spec requires that when a node is removed from its parent, + * and the node's subtree contains the start or end point of a range, that + * start or end point is moved up to where the node was removed from its + * parent. + * For some internal uses of Ranges it's useful to disable that behavior, + * so that a range of children within a single parent is preserved even if + * that parent is removed from the document tree. + */ + void SetEnableGravitationOnElementRemoval(bool aEnable) + { + mEnableGravitationOnElementRemoval = aEnable; + } + + // nsIDOMRange interface + NS_DECL_NSIDOMRANGE + + nsINode* GetRoot() const + { + return mRoot; + } + + nsINode* GetStartParent() const + { + return mStartParent; + } + + nsINode* GetEndParent() const + { + return mEndParent; + } + + int32_t StartOffset() const + { + return mStartOffset; + } + + int32_t EndOffset() const + { + return mEndOffset; + } + + bool IsPositioned() const + { + return mIsPositioned; + } + + void SetMaySpanAnonymousSubtrees(bool aMaySpanAnonymousSubtrees) + { + mMaySpanAnonymousSubtrees = aMaySpanAnonymousSubtrees; + } + + /** + * Return true iff this range is part of a Selection object + * and isn't detached. + */ + bool IsInSelection() const + { + return !!mSelection; + } + + /** + * Called when the range is added/removed from a Selection. + */ + void SetSelection(mozilla::dom::Selection* aSelection); + + /** + * Return true if this range was generated. + * @see SetIsGenerated + */ + bool IsGenerated() const + { + return mIsGenerated; + } + + /** + * Mark this range as being generated or not. + * Currently it is used for marking ranges that are created when splitting up + * a range to exclude a -moz-user-select:none region. + * @see Selection::AddItem + * @see ExcludeNonSelectableNodes + */ + void SetIsGenerated(bool aIsGenerated) + { + mIsGenerated = aIsGenerated; + } + + nsINode* GetCommonAncestor() const; + void Reset(); + nsresult SetStart(nsINode* aParent, int32_t aOffset); + nsresult SetEnd(nsINode* aParent, int32_t aOffset); + already_AddRefed<nsRange> CloneRange() const; + + nsresult Set(nsINode* aStartParent, int32_t aStartOffset, + nsINode* aEndParent, int32_t aEndOffset) + { + // If this starts being hot, we may be able to optimize this a bit, + // but for now just set start and end separately. + nsresult rv = SetStart(aStartParent, aStartOffset); + NS_ENSURE_SUCCESS(rv, rv); + + return SetEnd(aEndParent, aEndOffset); + } + + NS_IMETHOD GetUsedFontFaces(nsIDOMFontFaceList** aResult); + + // nsIMutationObserver methods + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + + // WebIDL + static already_AddRefed<nsRange> + Constructor(const mozilla::dom::GlobalObject& global, + mozilla::ErrorResult& aRv); + + bool Collapsed() const + { + return mIsPositioned && mStartParent == mEndParent && + mStartOffset == mEndOffset; + } + already_AddRefed<mozilla::dom::DocumentFragment> + CreateContextualFragment(const nsAString& aString, ErrorResult& aError); + already_AddRefed<mozilla::dom::DocumentFragment> + CloneContents(ErrorResult& aErr); + int16_t CompareBoundaryPoints(uint16_t aHow, nsRange& aOther, + ErrorResult& aErr); + int16_t ComparePoint(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr); + void DeleteContents(ErrorResult& aRv); + already_AddRefed<mozilla::dom::DocumentFragment> + ExtractContents(ErrorResult& aErr); + nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const; + nsINode* GetStartContainer(ErrorResult& aRv) const; + uint32_t GetStartOffset(ErrorResult& aRv) const; + nsINode* GetEndContainer(ErrorResult& aRv) const; + uint32_t GetEndOffset(ErrorResult& aRv) const; + void InsertNode(nsINode& aNode, ErrorResult& aErr); + bool IntersectsNode(nsINode& aNode, ErrorResult& aRv); + bool IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr); + void SelectNode(nsINode& aNode, ErrorResult& aErr); + void SelectNodeContents(nsINode& aNode, ErrorResult& aErr); + void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); + void SetEndAfter(nsINode& aNode, ErrorResult& aErr); + void SetEndBefore(nsINode& aNode, ErrorResult& aErr); + void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr); + void SetStartAfter(nsINode& aNode, ErrorResult& aErr); + void SetStartBefore(nsINode& aNode, ErrorResult& aErr); + void SurroundContents(nsINode& aNode, ErrorResult& aErr); + already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true, + bool aFlushLayout = true); + already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true, + bool aFlushLayout = true); + void GetClientRectsAndTexts( + mozilla::dom::ClientRectsAndTexts& aResult, + ErrorResult& aErr); + static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue, + mozilla::ErrorResult& aError, + nsIContent* aStartParent, + uint32_t aStartOffset, + nsIContent* aEndParent, + uint32_t aEndOffset); + + nsINode* GetParentObject() const { return mOwner; } + virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override final; + +private: + // no copy's or assigns + nsRange(const nsRange&); + nsRange& operator=(const nsRange&); + + /** + * Cut or delete the range's contents. + * + * @param aFragment nsIDOMDocumentFragment containing the nodes. + * May be null to indicate the caller doesn't want a fragment. + */ + nsresult CutContents(mozilla::dom::DocumentFragment** frag); + + static nsresult CloneParentsBetween(nsINode* aAncestor, + nsINode* aNode, + nsINode** aClosestAncestor, + nsINode** aFarthestAncestor); + +public: +/****************************************************************************** + * Utility routine to detect if a content node starts before a range and/or + * ends after a range. If neither it is contained inside the range. + * + * XXX - callers responsibility to ensure node in same doc as range! + * + *****************************************************************************/ + static nsresult CompareNodeToRange(nsINode* aNode, nsRange* aRange, + bool *outNodeBefore, + bool *outNodeAfter); + + /** + * Return true if any part of (aNode, aStartOffset) .. (aNode, aEndOffset) + * overlaps any nsRange in aNode's GetNextRangeCommonAncestor ranges (i.e. + * where aNode is a descendant of a range's common ancestor node). + * If a nsRange starts in (aNode, aEndOffset) or if it ends in + * (aNode, aStartOffset) then it is non-overlapping and the result is false + * for that nsRange. Collapsed ranges always counts as non-overlapping. + */ + static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset, + uint32_t aEndOffset); + + /** + * This helper function gets rects and correlated text for the given range. + * @param aTextList optional where nullptr = don't retrieve text + */ + static void CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector, + mozilla::dom::DOMStringList* aTextList, + nsRange* aRange, + nsINode* aStartParent, int32_t aStartOffset, + nsINode* aEndParent, int32_t aEndOffset, + bool aClampToEdge, bool aFlushLayout); + + /** + * Scan this range for -moz-user-select:none nodes and split it up into + * multiple ranges to exclude those nodes. The resulting ranges are put + * in aOutRanges. If no -moz-user-select:none node is found in the range + * then |this| is unmodified and is the only range in aOutRanges. + * Otherwise, |this| will be modified so that it ends before the first + * -moz-user-select:none node and additional ranges may also be created. + * If all nodes in the range are -moz-user-select:none then aOutRanges + * will be empty. + * @param aOutRanges the resulting set of ranges + */ + void ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges); + + typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable; +protected: + void RegisterCommonAncestor(nsINode* aNode); + void UnregisterCommonAncestor(nsINode* aNode); + nsINode* IsValidBoundary(nsINode* aNode); + + // CharacterDataChanged set aNotInsertedYet to true to disable an assertion + // and suppress re-registering a range common ancestor node since + // the new text node of a splitText hasn't been inserted yet. + // CharacterDataChanged does the re-registering when needed. + void DoSetRange(nsINode* aStartN, int32_t aStartOffset, + nsINode* aEndN, int32_t aEndOffset, + nsINode* aRoot, bool aNotInsertedYet = false); + + /** + * For a range for which IsInSelection() is true, return the common + * ancestor for the range. This method uses the selection bits and + * nsGkAtoms::range property on the nodes to quickly find the ancestor. + * That is, it's a faster version of GetCommonAncestor that only works + * for ranges in a Selection. The method will assert and the behavior + * is undefined if called on a range where IsInSelection() is false. + */ + nsINode* GetRegisteredCommonAncestor(); + + // Helper to IsNodeSelected. + static bool IsNodeInSortedRanges(nsINode* aNode, + uint32_t aStartOffset, + uint32_t aEndOffset, + const nsTArray<const nsRange*>& aRanges, + size_t aRangeStart, + size_t aRangeEnd); + + struct MOZ_STACK_CLASS AutoInvalidateSelection + { + explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) + { +#ifdef DEBUG + mWasInSelection = mRange->IsInSelection(); +#endif + if (!mRange->IsInSelection() || mIsNested) { + return; + } + mIsNested = true; + mCommonAncestor = mRange->GetRegisteredCommonAncestor(); + } + ~AutoInvalidateSelection(); + nsRange* mRange; + RefPtr<nsINode> mCommonAncestor; +#ifdef DEBUG + bool mWasInSelection; +#endif + static bool mIsNested; + }; + + nsCOMPtr<nsIDocument> mOwner; + nsCOMPtr<nsINode> mRoot; + nsCOMPtr<nsINode> mStartParent; + nsCOMPtr<nsINode> mEndParent; + RefPtr<mozilla::dom::Selection> mSelection; + int32_t mStartOffset; + int32_t mEndOffset; + + bool mIsPositioned : 1; + bool mMaySpanAnonymousSubtrees : 1; + bool mIsGenerated : 1; + bool mStartOffsetWasIncremented : 1; + bool mEndOffsetWasIncremented : 1; + bool mEnableGravitationOnElementRemoval : 1; +#ifdef DEBUG + int32_t mAssertNextInsertOrAppendIndex; + nsINode* mAssertNextInsertOrAppendNode; +#endif +}; + +inline nsISupports* +ToCanonicalSupports(nsRange* aRange) +{ + return static_cast<nsIDOMRange*>(aRange); +} + +inline nsISupports* +ToSupports(nsRange* aRange) +{ + return static_cast<nsIDOMRange*>(aRange); +} + +#endif /* nsRange_h___ */ |