SelectionMovementUtils.h (10115B)
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 #ifndef mozilla_SelectionMovementUtils_h 8 #define mozilla_SelectionMovementUtils_h 9 10 #include "mozilla/Attributes.h" 11 #include "mozilla/EnumSet.h" 12 #include "mozilla/RangeBoundary.h" 13 #include "mozilla/Result.h" 14 #include "mozilla/intl/BidiEmbeddingLevel.h" 15 #include "nsIFrame.h" 16 17 struct nsPrevNextBidiLevels; 18 19 namespace mozilla { 20 21 class PresShell; 22 enum class PeekOffsetOption : uint16_t; 23 24 namespace intl { 25 class BidiEmbeddingLevel; 26 } 27 28 struct MOZ_STACK_CLASS FrameAndOffset { 29 [[nodiscard]] nsIContent* GetFrameContent() const { 30 return mFrame ? mFrame->GetContent() : nullptr; 31 } 32 33 operator nsIFrame*() const { return mFrame; } 34 35 explicit operator bool() const { return !!mFrame; } 36 [[nodiscard]] bool operator!() const { return !mFrame; } 37 38 nsIFrame* operator->() const { 39 MOZ_ASSERT(mFrame); 40 return mFrame; 41 } 42 43 nsIFrame* mFrame = nullptr; 44 // The offset in mFrame->GetContent(). 45 uint32_t mOffsetInFrameContent = 0; 46 }; 47 48 struct MOZ_STACK_CLASS PrimaryFrameData : public FrameAndOffset { 49 // Whether the caret should be put before or after the point. This is valid 50 // only when mFrame is not nullptr. 51 CaretAssociationHint mHint{0}; // Before 52 }; 53 54 struct MOZ_STACK_CLASS CaretFrameData : public PrimaryFrameData { 55 // The frame which is found only from a DOM point. This frame becomes 56 // different from mFrame when the point is around end of a line or 57 // at a bidi text boundary. 58 nsIFrame* mUnadjustedFrame = nullptr; 59 }; 60 61 enum class ForceEditableRegion : bool { No, Yes }; 62 63 class SelectionMovementUtils final { 64 public: 65 using PeekOffsetOptions = EnumSet<PeekOffsetOption>; 66 67 /** 68 * @brief Creates a new `RangeBoundary` which moves `aAmount` into 69 * `aDirection` from the input range boundary. 70 * 71 * @param aRangeBoundary The input range boundary. 72 * @param aDirection The direction into which the new boundary should be 73 * moved. 74 * @param aHint The `CaretAssociationHint` (is the caret before or 75 * after the boundary point) 76 * @param aCaretBidiLevel The `BidiEmbeddingLevel`. 77 * @param aAmount The amount which the range boundary should be 78 * moved. 79 * @param aOptions Additional options, see `PeekOffsetOption`. 80 * @param aAncestorLimiter The content node that limits where Selection may be 81 * expanded to. 82 * 83 * @return Returns a new `RangeBoundary` which is moved from `aRangeBoundary` 84 * by `aAmount` into `aDirection`. 85 */ 86 template <typename ParentType, typename RefType> 87 static Result<RangeBoundaryBase<ParentType, RefType>, nsresult> 88 MoveRangeBoundaryToSomewhere( 89 const RangeBoundaryBase<ParentType, RefType>& aRangeBoundary, 90 nsDirection aDirection, CaretAssociationHint aHint, 91 intl::BidiEmbeddingLevel aCaretBidiLevel, nsSelectionAmount aAmount, 92 PeekOffsetOptions aOptions, 93 const dom::Element* aAncestorLimiter = nullptr); 94 95 /** 96 * Given a node and its child offset, return the nsIFrame and the offset into 97 * that frame. 98 * 99 * @param aNode input parameter for the node to look at 100 * @param aOffset offset into above node. 101 */ 102 static FrameAndOffset GetFrameForNodeOffset(const nsIContent* aNode, 103 uint32_t aOffset, 104 CaretAssociationHint aHint); 105 106 /** 107 * Return the first visible point in or at a leaf node in aRange or the first 108 * unselectable content if aRange starts from a selectable container. E.g., 109 * return the start of the first visible `Text` or the position of the first 110 * visible leaf element. I.e., the result may be a good point to put a UI for 111 * showing something around the start boundary. 112 * 113 * NOTE: This won't return any boundary point in subtrees from the tree 114 * containing the start container of aRange due to ContentIteratorBase's 115 * limitation. See bug 2001511. 116 * 117 * @param aRange Must not be collapsed because this returns a point in aRange 118 * so that this requires the limitation of scanning forward. 119 * @return A position in a `Text` or a position at an element. 120 */ 121 [[nodiscard]] static RawRangeBoundary GetFirstVisiblePointAtLeaf( 122 const dom::AbstractRange& aRange); 123 124 /** 125 * Return the last visible point in or at a leaf node in aRange or the last 126 * unselectable content if aRange ends in a selectable container. E.g., return 127 * the end of the last visible `Text` or the position of the last visible leaf 128 * element. I.e., the result may be a good point to put a UI for showing 129 * something around the end boundary. 130 * 131 * NOTE: This won't return any boundary point in subtrees of the tree 132 * containing the end container of aRange due to ContentIteratorBase's 133 * limitation. See bug 2001511. 134 * 135 * @param aRange Must not be collapsed because this returns a point in aRange 136 * so that this requires the limitation of scanning forward. 137 * @return A position in a `Text` or a position at an element. 138 */ 139 [[nodiscard]] static RawRangeBoundary GetLastVisiblePointAtLeaf( 140 const dom::AbstractRange& aRange); 141 142 /** 143 * GetPrevNextBidiLevels will return the frames and associated Bidi levels of 144 * the characters logically before and after a (collapsed) selection. 145 * 146 * @param aNode is the node containing the selection 147 * @param aContentOffset is the offset of the selection in the node 148 * @param aJumpLines 149 * If true, look across line boundaries. 150 * If false, behave as if there were base-level frames at line edges. 151 * @param aAncestorLimiter If set, this refers only the descendants. 152 * 153 * @return A struct holding the before/after frame and the before/after 154 * level. 155 * 156 * At the beginning and end of each line there is assumed to be a frame with 157 * Bidi level equal to the paragraph embedding level. 158 * 159 * In these cases the before frame and after frame respectively will be 160 * nullptr. 161 */ 162 static nsPrevNextBidiLevels GetPrevNextBidiLevels( 163 nsIContent* aNode, uint32_t aContentOffset, CaretAssociationHint aHint, 164 bool aJumpLines, const dom::Element* aAncestorLimiter); 165 166 /** 167 * PeekOffsetForCaretMove() only peek offset for caret move from the specified 168 * point of the normal selection. I.e., won't change selection ranges nor 169 * bidi information. 170 */ 171 static Result<PeekOffsetStruct, nsresult> PeekOffsetForCaretMove( 172 nsIContent* aContent, uint32_t aOffset, nsDirection aDirection, 173 CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel, 174 const nsSelectionAmount aAmount, const nsPoint& aDesiredCaretPos, 175 PeekOffsetOptions aOptions, const dom::Element* aAncestorLimiter); 176 177 /** 178 * IsIntraLineCaretMove() is a helper method for PeekOffsetForCaretMove() 179 * and CreateRangeExtendedToSomwhereFromNormalSelection(). This returns 180 * whether aAmount is intra line move or is crossing hard line break. 181 * This returns error if aMount is not supported by the methods. 182 */ 183 static Result<bool, nsresult> IsIntraLineCaretMove( 184 nsSelectionAmount aAmount) { 185 switch (aAmount) { 186 case eSelectCharacter: 187 case eSelectCluster: 188 case eSelectWord: 189 case eSelectWordNoSpace: 190 case eSelectBeginLine: 191 case eSelectEndLine: 192 return true; 193 case eSelectLine: 194 return false; 195 default: 196 return Err(NS_ERROR_FAILURE); 197 } 198 } 199 200 /** 201 * Return a frame for considering caret geometry. 202 * 203 * @param aFrameSelection [optional] If this is specified and selection in 204 * aContent is not managed by the specified 205 * instance, return nullptr. 206 * @param aContentNode The content node where selection is collapsed. 207 * @param aOffset Collapsed position in aContentNode 208 * @param aFrameHint Caret association hint. 209 * @param aBidiLevel 210 * @param aForceEditableRegion Whether selection should be limited in 211 * editable region or not. 212 */ 213 static CaretFrameData GetCaretFrameForNodeOffset( 214 const nsFrameSelection* aFrameSelection, nsIContent* aContentNode, 215 uint32_t aOffset, CaretAssociationHint aFrameHint, 216 intl::BidiEmbeddingLevel aBidiLevel, 217 ForceEditableRegion aForceEditableRegion); 218 219 static bool AdjustFrameForLineStart(nsIFrame*& aFrame, 220 uint32_t& aFrameOffset); 221 222 /** 223 * Get primary frame and some other data for putting caret or extending 224 * selection at the point. 225 */ 226 static PrimaryFrameData GetPrimaryFrameForCaret( 227 nsIContent* aContent, uint32_t aOffset, bool aVisual, 228 CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel); 229 230 private: 231 /** 232 * GetFrameFromLevel will scan in a given direction 233 * until it finds a frame with a Bidi level less than or equal to a given 234 * level. It will return the last frame before this. 235 * 236 * @param aPresContext is the context to use 237 * @param aFrameIn is the frame to start from 238 * @param aDirection is the direction to scan 239 * @param aBidiLevel is the level to search for 240 */ 241 static Result<nsIFrame*, nsresult> GetFrameFromLevel( 242 nsIFrame* aFrameIn, nsDirection aDirection, 243 intl::BidiEmbeddingLevel aBidiLevel); 244 245 // This is helper method for GetPrimaryFrameForCaret. 246 // If aVisual is true, this returns caret frame. 247 // If false, this returns primary frame. 248 static PrimaryFrameData GetPrimaryOrCaretFrameForNodeOffset( 249 nsIContent* aContent, uint32_t aOffset, bool aVisual, 250 CaretAssociationHint aHint, intl::BidiEmbeddingLevel aCaretBidiLevel); 251 }; 252 253 } // namespace mozilla 254 255 #endif // #ifndef mozilla_SelectionMovementUtils_h