summaryrefslogtreecommitdiffstats
path: root/dom/base/nsRange.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/base/nsRange.h439
1 files changed, 439 insertions, 0 deletions
diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h
new file mode 100644
index 0000000000..97756d3afc
--- /dev/null
+++ b/dom/base/nsRange.h
@@ -0,0 +1,439 @@
+/* -*- 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 Range object.
+ */
+
+#ifndef nsRange_h___
+#define nsRange_h___
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/AbstractRange.h"
+#include "prmon.h"
+#include "nsStubMutationObserver.h"
+#include "nsWrapperCache.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/RangeBoundary.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+class RectCallback;
+namespace dom {
+struct ClientRectsAndTexts;
+class DocGroup;
+class DocumentFragment;
+class DOMRect;
+class DOMRectList;
+class InspectorFontFace;
+class Selection;
+} // namespace dom
+} // namespace mozilla
+
+class nsRange final : public mozilla::dom::AbstractRange,
+ public nsStubMutationObserver {
+ using ErrorResult = mozilla::ErrorResult;
+ using AbstractRange = mozilla::dom::AbstractRange;
+ using DocGroup = mozilla::dom::DocGroup;
+ using DOMRect = mozilla::dom::DOMRect;
+ using DOMRectList = mozilla::dom::DOMRectList;
+ using RangeBoundary = mozilla::RangeBoundary;
+ using RawRangeBoundary = mozilla::RawRangeBoundary;
+
+ virtual ~nsRange();
+ explicit nsRange(nsINode* aNode);
+
+ public:
+ /**
+ * The following Create() returns `nsRange` instance which is initialized
+ * only with aNode. The result is never positioned.
+ */
+ static already_AddRefed<nsRange> Create(nsINode* aNode);
+
+ /**
+ * The following Create() may return `nsRange` instance which is initialized
+ * with given range or points. If it fails initializing new range with the
+ * arguments, returns `nullptr`. `ErrorResult` is set to an error only
+ * when this returns `nullptr`. The error code indicates the reason why
+ * it couldn't initialize the instance.
+ */
+ static already_AddRefed<nsRange> Create(const AbstractRange* aAbstractRange,
+ ErrorResult& aRv) {
+ return nsRange::Create(aAbstractRange->StartRef(), aAbstractRange->EndRef(),
+ aRv);
+ }
+ static already_AddRefed<nsRange> Create(nsINode* aStartContainer,
+ uint32_t aStartOffset,
+ nsINode* aEndContainer,
+ uint32_t aEndOffset,
+ ErrorResult& aRv) {
+ return nsRange::Create(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset), aRv);
+ }
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static already_AddRefed<nsRange> Create(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHODIMP_(void) DeleteCycleCollectable(void) override;
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsRange, AbstractRange)
+
+ nsrefcnt GetRefCount() const { return mRefCnt; }
+
+ nsINode* GetRoot() const { return mRoot; }
+
+ /**
+ * 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::AddRangesForSelectableNodes
+ * @see ExcludeNonSelectableNodes
+ */
+ void SetIsGenerated(bool aIsGenerated) { mIsGenerated = aIsGenerated; }
+
+ void Reset();
+
+ /**
+ * SetStart() and SetEnd() sets start point or end point separately.
+ * However, this is expensive especially when it's a range of Selection.
+ * When you set both start and end of a range, you should use
+ * SetStartAndEnd() instead.
+ */
+ nsresult SetStart(nsINode* aContainer, uint32_t aOffset) {
+ ErrorResult error;
+ SetStart(RawRangeBoundary(aContainer, aOffset), error);
+ return error.StealNSResult();
+ }
+ nsresult SetEnd(nsINode* aContainer, uint32_t aOffset) {
+ ErrorResult error;
+ SetEnd(RawRangeBoundary(aContainer, aOffset), error);
+ return error.StealNSResult();
+ }
+
+ already_AddRefed<nsRange> CloneRange() const;
+
+ /**
+ * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
+ * Different from calls them separately, this does nothing if either
+ * the start point or the end point is invalid point.
+ * If the specified start point is after the end point, the range will be
+ * collapsed at the end point. Similarly, if they are in different root,
+ * the range will be collapsed at the end point.
+ */
+ nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
+ nsINode* aEndContainer, uint32_t aEndOffset) {
+ return SetStartAndEnd(RawRangeBoundary(aStartContainer, aStartOffset),
+ RawRangeBoundary(aEndContainer, aEndOffset));
+ }
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ nsresult SetStartAndEnd(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
+ return AbstractRange::SetStartAndEndInternal(aStartBoundary, aEndBoundary,
+ this);
+ }
+
+ /**
+ * Adds all nodes between |aStartContent| and |aEndContent| to the range.
+ * The start offset will be set before |aStartContent|,
+ * while the end offset will be set immediately after |aEndContent|.
+ *
+ * Caller must guarantee both nodes are non null and
+ * children of |aContainer| and that |aEndContent| is after |aStartContent|.
+ */
+ void SelectNodesInContainer(nsINode* aContainer, nsIContent* aStartContent,
+ nsIContent* aEndContent);
+
+ /**
+ * CollapseTo() works similar to call both SetStart() and SetEnd() with
+ * same node and offset. This just calls SetStartAndParent() to set
+ * collapsed range at aContainer and aOffset.
+ */
+ nsresult CollapseTo(nsINode* aContainer, uint32_t aOffset) {
+ return CollapseTo(RawRangeBoundary(aContainer, aOffset));
+ }
+ nsresult CollapseTo(const RawRangeBoundary& aPoint) {
+ return SetStartAndEnd(aPoint, aPoint);
+ }
+
+ // aMaxRanges is the maximum number of text ranges to record for each face
+ // (pass 0 to just get the list of faces, without recording exact ranges
+ // where each face was used).
+ nsresult GetUsedFontFaces(
+ nsTArray<mozilla::UniquePtr<mozilla::dom::InspectorFontFace>>& aResult,
+ uint32_t aMaxRanges, bool aSkipCollapsedWhitespace);
+
+ // 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);
+
+ already_AddRefed<mozilla::dom::DocumentFragment> CreateContextualFragment(
+ const nsAString& aString, ErrorResult& aError) const;
+ already_AddRefed<mozilla::dom::DocumentFragment> CloneContents(
+ ErrorResult& aErr);
+ int16_t CompareBoundaryPoints(uint16_t aHow, const nsRange& aOtherRange,
+ ErrorResult& aRv);
+ int16_t ComparePoint(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) const;
+ void DeleteContents(ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::DocumentFragment> ExtractContents(
+ ErrorResult& aErr);
+ nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const {
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ return GetClosestCommonInclusiveAncestor();
+ }
+ void InsertNode(nsINode& aNode, ErrorResult& aErr);
+ bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
+ bool IsPointInRange(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aRv) const;
+ void ToString(nsAString& aReturn, ErrorResult& aErr);
+ void Detach();
+
+ // *JS() methods are mapped to Range.*() of DOM.
+ // They may move focus only when the range represents normal selection.
+ // These methods shouldn't be used from internal.
+ void CollapseJS(bool aToStart);
+ void SelectNodeJS(nsINode& aNode, ErrorResult& aErr);
+ void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr);
+ void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr);
+ void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr);
+ void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr);
+ void SetStartBeforeJS(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);
+
+ // Following methods should be used for internal use instead of *JS().
+ void SelectNode(nsINode& aNode, ErrorResult& aErr);
+ void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
+ void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aErr);
+ void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
+ void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
+ void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetStart(const RawRangeBoundary& aPoint, ErrorResult& aErr);
+ void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
+ void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
+ void Collapse(bool aToStart);
+
+ static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
+ mozilla::ErrorResult& aError,
+ nsIContent* aContainer);
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) final;
+ DocGroup* GetDocGroup() const;
+
+ private:
+ // no copy's or assigns
+ nsRange(const nsRange&);
+ nsRange& operator=(const nsRange&);
+
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ static void AssertIfMismatchRootAndRangeBoundaries(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ const nsINode* aRootNode, bool aNotInsertedYet = false);
+
+ /**
+ * Cut or delete the range's contents.
+ *
+ * @param aFragment DocumentFragment containing the nodes.
+ * May be null to indicate the caller doesn't want a
+ * fragment.
+ * @param aRv The error if any.
+ */
+ void CutContents(mozilla::dom::DocumentFragment** aFragment,
+ ErrorResult& aRv);
+
+ static nsresult CloneParentsBetween(nsINode* aAncestor, nsINode* aNode,
+ nsINode** aClosestAncestor,
+ nsINode** aFarthestAncestor);
+
+ /**
+ * Returns whether a node is safe to be accessed by the current caller.
+ */
+ bool CanAccess(const nsINode&) const;
+
+ void AdjustNextRefsOnCharacterDataSplit(const nsIContent& aContent,
+ const CharacterDataChangeInfo& aInfo);
+
+ struct RangeBoundariesAndRoot {
+ RawRangeBoundary mStart;
+ RawRangeBoundary mEnd;
+ nsINode* mRoot = nullptr;
+ };
+
+ /**
+ * @param aContent Must be non-nullptr.
+ */
+ RangeBoundariesAndRoot DetermineNewRangeBoundariesAndRootOnCharacterDataMerge(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const;
+
+ // @return true iff the range is positioned, aContainer belongs to the same
+ // document as the range, aContainer is a DOCUMENT_TYPE_NODE and
+ // aOffset doesn't exceed aContainer's length.
+ bool IsPointComparableToRange(const nsINode& aContainer, uint32_t aOffset,
+ ErrorResult& aErrorResult) const;
+
+ /**
+ * @brief Returns true if the range is part of exactly one |Selection|.
+ */
+ bool IsPartOfOneSelectionOnly() const { return mSelections.Length() == 1; };
+
+ public:
+ /**
+ * This helper function gets rects and correlated text for the given range.
+ * @param aTextList optional where nullptr = don't retrieve text
+ */
+ static void CollectClientRectsAndText(
+ mozilla::RectCallback* aCollector,
+ mozilla::dom::Sequence<nsString>* aTextList, nsRange* aRange,
+ nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
+ uint32_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);
+
+ /**
+ * Notify the selection listeners after a range has been modified.
+ */
+ MOZ_CAN_RUN_SCRIPT void NotifySelectionListenersAfterRangeSet();
+
+ /**
+ * For a range for which IsInSelection() is true, return the closest common
+ * inclusive ancestor
+ * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor)
+ * for the range, which we had to compute when the common ancestor changed or
+ * IsInSelection became true, so we could register with it. That is, it's a
+ * faster version of GetClosestCommonInclusiveAncestor 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* GetRegisteredClosestCommonInclusiveAncestor();
+
+ protected:
+ /**
+ * DoSetRange() is called when `AbstractRange::SetStartAndEndInternal()` sets
+ * mStart and mEnd, or some other internal methods modify `mStart` and/or
+ * `mEnd`. Therefore, this shouldn't be a virtual method.
+ *
+ * @param aStartBoundary Computed start point. This must equals or be
+ * before aEndBoundary in the DOM tree order.
+ * @param aEndBoundary Computed end point.
+ * @param aRootNode The root node.
+ * @param aNotInsertedYet true if this is called by CharacterDataChanged()
+ * to disable 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. Otherwise, false.
+ */
+ template <typename SPT, typename SRT, typename EPT, typename ERT>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoSetRange(
+ const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
+ const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
+ nsINode* aRootNode, bool aNotInsertedYet = false);
+
+ // Assume that this is guaranteed that this is held by the caller when
+ // this is used. (Note that we cannot use AutoRestore for mCalledByJS
+ // due to a bit field.)
+ class MOZ_RAII AutoCalledByJSRestore final {
+ private:
+ nsRange& mRange;
+ bool mOldValue;
+
+ public:
+ explicit AutoCalledByJSRestore(nsRange& aRange)
+ : mRange(aRange), mOldValue(aRange.mCalledByJS) {}
+ ~AutoCalledByJSRestore() { mRange.mCalledByJS = mOldValue; }
+ bool SavedValue() const { return mOldValue; }
+ };
+
+ struct MOZ_STACK_CLASS AutoInvalidateSelection {
+ explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) {
+ if (!mRange->IsInAnySelection() || sIsNested) {
+ return;
+ }
+ sIsNested = true;
+ mCommonAncestor = mRange->GetRegisteredClosestCommonInclusiveAncestor();
+ }
+ ~AutoInvalidateSelection();
+ nsRange* mRange;
+ RefPtr<nsINode> mCommonAncestor;
+ static bool sIsNested;
+ };
+
+ bool MaybeInterruptLastRelease();
+
+#ifdef DEBUG
+ bool IsCleared() const {
+ return !mRoot && !mRegisteredClosestCommonInclusiveAncestor &&
+ mSelections.IsEmpty() && !mNextStartRef && !mNextEndRef;
+ }
+#endif // #ifdef DEBUG
+
+ nsCOMPtr<nsINode> mRoot;
+
+ // These raw pointers are used to remember a child that is about
+ // to be inserted between a CharacterData call and a subsequent
+ // ContentInserted or ContentAppended call. It is safe to store
+ // these refs because the caller is guaranteed to trigger both
+ // notifications while holding a strong reference to the new child.
+ nsIContent* MOZ_NON_OWNING_REF mNextStartRef;
+ nsIContent* MOZ_NON_OWNING_REF mNextEndRef;
+
+ static nsTArray<RefPtr<nsRange>>* sCachedRanges;
+
+ friend class mozilla::dom::AbstractRange;
+};
+namespace mozilla::dom {
+inline nsRange* AbstractRange::AsDynamicRange() {
+ MOZ_ASSERT(IsDynamicRange());
+ return static_cast<nsRange*>(this);
+}
+inline const nsRange* AbstractRange::AsDynamicRange() const {
+ MOZ_ASSERT(IsDynamicRange());
+ return static_cast<const nsRange*>(this);
+}
+} // namespace mozilla::dom
+#endif /* nsRange_h___ */