/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */
class nsCycleCollectionTraversalCallback; class nsRange; namespace mozilla { namespace dom { class Element; class Selection; class Text;
} // namespace dom
/** * A helper struct for saving/setting ranges.
*/ struct RangeItem final {
RangeItem() : mStartOffset(0), mEndOffset(0) {}
private: // Private destructor, to discourage deletion outside of Release():
~RangeItem() = default;
/** * mozilla::SelectionState * * Class for recording selection info. Stores selection as collection of * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store * ranges since dom gravity will possibly change the ranges.
*/
class SelectionState final { public:
SelectionState() = default; explicit SelectionState(const AutoClonedSelectionRangeArray& aRanges);
/** * Same as the API as dom::Selection
*/
[[nodiscard]] bool IsCollapsed() const { if (mArray.Length() != 1) { returnfalse;
} return mArray[0]->Collapsed();
}
/** * Saving all ranges of aSelection.
*/ void SaveSelection(dom::Selection& aSelection);
/** * Setting aSelection to have all ranges stored by this instance.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
RestoreSelection(dom::Selection& aSelection);
/** * Setting aRanges to have all ranges stored by this instance.
*/ void ApplyTo(AutoClonedSelectionRangeArray& aRanges);
/** * HasOnlyCollapsedRange() returns true only when there is a positioned range * which is collapsed. I.e., the selection represents a caret point.
*/
[[nodiscard]] bool HasOnlyCollapsedRange() const { if (mArray.Length() != 1) { returnfalse;
} if (!mArray[0]->IsPositioned() || !mArray[0]->Collapsed()) { returnfalse;
} returntrue;
}
/** * Equals() returns true only when there are same number of ranges and * all their containers and offsets are exactly same. This won't check * the validity of each range with the current DOM tree.
*/
[[nodiscard]] bool Equals(const SelectionState& aOther) const;
/** * Returns common root node of all ranges' start and end containers. * Some of them have different root nodes, this returns nullptr.
*/
[[nodiscard]] nsINode* GetCommonRootNode() const {
nsINode* rootNode = nullptr; for (const RefPtr<RangeItem>& rangeItem : mArray) {
nsINode* newRootNode = rangeItem->GetRoot(); if (!newRootNode || (rootNode && rootNode != newRootNode)) { return nullptr;
}
rootNode = newRootNode;
} return rootNode;
}
// editor selection gravity routines. Note that we can't always depend on // DOM Range gravity to do what we want to the "real" selection. For // instance, if you move a node, that corresponds to deleting it and // reinserting it. DOM Range gravity will promote the selection out of the // node on deletion, which is not what you want if you know you are // reinserting it. template <typename PT, typename CT>
nsresult SelAdjCreateNode(const EditorDOMPointBase<PT, CT>& aPoint); template <typename PT, typename CT>
nsresult SelAdjInsertNode(const EditorDOMPointBase<PT, CT>& aPoint); void SelAdjDeleteNode(nsINode& aNode);
/** * SelAdjSplitNode() is called immediately after spliting aOriginalNode * and inserted aNewContent into the DOM tree. * * @param aOriginalContent The node which was split. * @param aSplitOffset The old offset in aOriginalContent at splitting * it. * @param aNewContent The new content node which was inserted into * the DOM tree.
*/
nsresult SelAdjSplitNode(nsIContent& aOriginalContent, uint32_t aSplitOffset,
nsIContent& aNewContent);
/** * SelAdjJoinNodes() is called immediately after joining aRemovedContent and * the container of aStartOfRightContent. * * @param aStartOfRightContent The container is joined content node which * now has all children or text data which were * in aRemovedContent. And this points where * the joined position. * @param aRemovedContent The removed content. * @param aOldPointAtRightContent The point where the right content node was * before joining them. The offset must have * been initialized before the joining.
*/
nsresult SelAdjJoinNodes(const EditorRawDOMPoint& aStartOfRightContent, const nsIContent& aRemovedContent, const EditorDOMPoint& aOldPointAtRightContent); void SelAdjInsertText(const dom::Text& aTextNode, uint32_t aOffset,
uint32_t aInsertedLength); void SelAdjDeleteText(const dom::Text& aTextNode, uint32_t aOffset,
uint32_t aDeletedLength); void SelAdjReplaceText(const dom::Text& aTextNode, uint32_t aOffset,
uint32_t aReplacedLength, uint32_t aInsertedLength); // the following gravity routines need will/did sandwiches, because the other // gravity routines will be called inside of these sandwiches, but should be // ignored. void WillReplaceContainer() { // XXX Isn't this possible with mutation event listener?
NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
mLocked = true;
} void DidReplaceContainer(const dom::Element& aRemovedElement,
dom::Element& aInsertedElement); void WillRemoveContainer() { // XXX Isn't this possible with mutation event listener?
NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
mLocked = true;
} void DidRemoveContainer(const dom::Element& aRemovedElement,
nsINode& aRemovedElementContainerNode,
uint32_t aOldOffsetOfRemovedElement,
uint32_t aOldChildCountOfRemovedElement); void WillInsertContainer() { // XXX Isn't this possible with mutation event listener?
NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
mLocked = true;
} void DidInsertContainer() {
NS_WARNING_ASSERTION(mLocked, "Not locked");
mLocked = false;
} void DidMoveNode(const nsINode& aOldParent, uint32_t aOldOffset, const nsINode& aNewParent, uint32_t aNewOffset);
private: // TODO: A lot of loop in these methods check whether each item `nullptr` or // not. We should make it not nullable later.
nsTArray<RefPtr<RangeItem>> mArray; bool mLocked;
};
/** * Helper class for using SelectionState. Stack based class for doing * preservation of dom points across editor actions.
*/
class MOZ_STACK_CLASS AutoTrackDOMPoint final { public:
AutoTrackDOMPoint() = delete;
void FlushAndStopTracking() { if (!mIsTracking) { return;
}
mIsTracking = false; if (mPoint.isSome()) {
mRangeUpdater.DropRangeItem(mRangeItem); // Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()` // and the number of times may be too many. (E.g., 1533913.html hits // over 700 times!) We should just put warning instead. if (NS_WARN_IF(!mRangeItem->mStartContainer)) {
mPoint.ref()->Clear(); return;
} // If the node was removed from the original document, clear the instance // since the user should not keep handling the adopted or orphan node // anymore. if (NS_WARN_IF(mWasConnected &&
!mRangeItem->mStartContainer->IsInComposedDoc()) ||
NS_WARN_IF(mRangeItem->mStartContainer->OwnerDoc() != mDocument)) {
mPoint.ref()->Clear(); return;
} if (NS_WARN_IF(mRangeItem->mStartContainer->Length() <
mRangeItem->mStartOffset)) {
mPoint.ref()->SetToEndOf(mRangeItem->mStartContainer); return;
}
mPoint.ref()->Set(mRangeItem->mStartContainer, mRangeItem->mStartOffset); return;
}
mRangeUpdater.DropRangeItem(mRangeItem);
*mNode = mRangeItem->mStartContainer;
*mOffset = mRangeItem->mStartOffset; if (!(*mNode)) { return;
} // If the node was removed from the original document, clear the instances // since the user should not keep handling the adopted or orphan node // anymore. if (NS_WARN_IF(mWasConnected && !(*mNode)->IsInComposedDoc()) ||
NS_WARN_IF((*mNode)->OwnerDoc() != mDocument)) {
*mNode = nullptr;
*mOffset = 0;
}
}
void FlushAndStopTracking() { if (!mStartPointTracker && !mEndPointTracker) { return;
}
mStartPointTracker.reset();
mEndPointTracker.reset(); if (!mRangeRefPtr && !mRangeOwningNonNull) { // This must be created with EditorDOMRange or EditorDOMPoints. In the // cases, destroying mStartPointTracker and mEndPointTracker has done // everything which we need to do. return;
} // Otherwise, update the DOM ranges by ourselves. if (mRangeRefPtr) { if (!mStartPoint.IsSet() || !mEndPoint.IsSet()) {
(*mRangeRefPtr)->Reset(); return;
}
(*mRangeRefPtr)
->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
mEndPoint.ToRawRangeBoundary()); return;
} if (mRangeOwningNonNull) { if (!mStartPoint.IsSet() || !mEndPoint.IsSet()) {
(*mRangeOwningNonNull)->Reset(); return;
}
(*mRangeOwningNonNull)
->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
mEndPoint.ToRawRangeBoundary()); return;
}
}
void StopTracking() { if (mStartPointTracker) {
mStartPointTracker->StopTracking();
} if (mEndPointTracker) {
mEndPointTracker->StopTracking();
}
} void StopTrackingStartBoundary() {
MOZ_ASSERT(!mRangeRefPtr, "StopTrackingStartBoundary() is not available when tracking " "RefPtr");
MOZ_ASSERT(!mRangeOwningNonNull, "StopTrackingStartBoundary() is not available when tracking " "OwningNonNull"); if (!mStartPointTracker) { return;
}
mStartPointTracker->StopTracking();
} void StopTrackingEndBoundary() {
MOZ_ASSERT(!mRangeRefPtr, "StopTrackingEndBoundary() is not available when tracking " "RefPtr");
MOZ_ASSERT(!mRangeOwningNonNull, "StopTrackingEndBoundary() is not available when tracking " "OwningNonNull"); if (!mEndPointTracker) { return;
}
mEndPointTracker->StopTracking();
}
/** * Another helper class for SelectionState. Stack based class for doing * Will/DidReplaceContainer()
*/
class MOZ_STACK_CLASS AutoReplaceContainerSelNotify final { public:
AutoReplaceContainerSelNotify() = delete; // FYI: Marked as `MOZ_CAN_RUN_SCRIPT` for avoiding to use strong pointers // for the members.
MOZ_CAN_RUN_SCRIPT
AutoReplaceContainerSelNotify(RangeUpdater& aRangeUpdater,
dom::Element& aOriginalElement,
dom::Element& aNewElement)
: mRangeUpdater(aRangeUpdater),
mOriginalElement(aOriginalElement),
mNewElement(aNewElement) {
mRangeUpdater.WillReplaceContainer();
}
/** * Another helper class for SelectionState. Stack based class for doing * Will/DidInsertContainer() * XXX The lock state isn't useful if the edit action is triggered from * a mutation event listener so that looks like that we can remove * this class.
*/
class MOZ_STACK_CLASS AutoInsertContainerSelNotify final { private:
RangeUpdater& mRangeUpdater;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.