mozInlineSpellChecker.h (13872B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #ifndef mozilla_mozInlineSpellChecker_h 7 #define mozilla_mozInlineSpellChecker_h 8 9 #include "nsCycleCollectionParticipant.h" 10 #include "nsIDOMEventListener.h" 11 #include "nsIEditorSpellCheck.h" 12 #include "nsIInlineSpellChecker.h" 13 #include "mozInlineSpellWordUtil.h" 14 #include "mozilla/EditorDOMPoint.h" 15 #include "mozilla/Result.h" 16 #include "nsRange.h" 17 #include "nsWeakReference.h" 18 19 class InitEditorSpellCheckCallback; 20 class mozInlineSpellChecker; 21 class mozInlineSpellResume; 22 class UpdateCurrentDictionaryCallback; 23 24 namespace mozilla { 25 class EditorBase; 26 class EditorSpellCheck; 27 enum class EditSubAction : int32_t; 28 enum class JoinNodesDirection; 29 30 namespace dom { 31 class Event; 32 } // namespace dom 33 } // namespace mozilla 34 35 class mozInlineSpellStatus { 36 public: 37 static mozilla::Result<mozilla::UniquePtr<mozInlineSpellStatus>, nsresult> 38 CreateForEditorChange(mozInlineSpellChecker& aSpellChecker, 39 mozilla::EditSubAction aEditSubAction, 40 nsINode* aAnchorNode, uint32_t aAnchorOffset, 41 nsINode* aPreviousNode, uint32_t aPreviousOffset, 42 nsINode* aStartNode, uint32_t aStartOffset, 43 nsINode* aEndNode, uint32_t aEndOffset); 44 45 static mozilla::Result<mozilla::UniquePtr<mozInlineSpellStatus>, nsresult> 46 CreateForNavigation(mozInlineSpellChecker& aSpellChecker, bool aForceCheck, 47 int32_t aNewPositionOffset, nsINode* aOldAnchorNode, 48 uint32_t aOldAnchorOffset, nsINode* aNewAnchorNode, 49 uint32_t aNewAnchorOffset, bool* aContinue); 50 51 static mozilla::UniquePtr<mozInlineSpellStatus> CreateForSelection( 52 mozInlineSpellChecker& aSpellChecker); 53 54 static mozilla::UniquePtr<mozInlineSpellStatus> CreateForRange( 55 mozInlineSpellChecker& aSpellChecker, nsRange* aRange); 56 57 nsresult FinishInitOnEvent(mozInlineSpellWordUtil& aWordUtil); 58 59 // Return true if we plan to spell-check everything 60 bool IsFullSpellCheck() const { return mOp == eOpChange && !mRange; } 61 62 const RefPtr<mozInlineSpellChecker> mSpellChecker; 63 64 enum Operation { 65 eOpChange, // for SpellCheckAfterEditorChange except 66 // deleteSelection 67 eOpChangeDelete, // for SpellCheckAfterEditorChange with 68 // deleteSelection 69 eOpNavigation, // for HandleNavigationEvent 70 eOpSelection, // re-check all misspelled words 71 eOpResume 72 }; 73 74 // See `mOp`. 75 Operation GetOperation() const { return mOp; } 76 77 // Used for events where we have already computed the range to use. It can 78 // also be nullptr in these cases where we need to check the entire range. 79 RefPtr<nsRange> mRange; 80 81 // See `mCreatedRange`. 82 const nsRange* GetCreatedRange() const { return mCreatedRange; } 83 84 // See `mNoCheckRange`. 85 const nsRange* GetNoCheckRange() const { return mNoCheckRange; } 86 87 private: 88 // @param aSpellChecker must be non-nullptr. 89 // @param aOp see mOp. 90 // @param aRange see mRange. 91 // @param aCreatedRange see mCreatedRange. 92 // @param aAnchorRange see mAnchorRange. 93 // @param aForceNavigationWordCheck see mForceNavigationWordCheck. 94 // @param aNewNavigationPositionOffset see mNewNavigationPositionOffset. 95 explicit mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker, 96 Operation aOp, RefPtr<nsRange>&& aRange, 97 RefPtr<nsRange>&& aCreatedRange, 98 RefPtr<nsRange>&& aAnchorRange, 99 bool aForceNavigationWordCheck, 100 int32_t aNewNavigationPositionOffset); 101 102 // For resuming a previously started check. 103 const Operation mOp; 104 105 // 106 // If we happen to know something was inserted, this is that range. 107 // Can be nullptr (this only allows an optimization, so not setting doesn't 108 // hurt) 109 const RefPtr<const nsRange> mCreatedRange; 110 111 // Contains the range computed for the current word. Can be nullptr. 112 RefPtr<nsRange> mNoCheckRange; 113 114 // Indicates the position of the cursor for the event (so we can compute 115 // mNoCheckRange). It can be nullptr if we don't care about the cursor 116 // position (such as for the intial check of everything). 117 // 118 // For mOp == eOpNavigation, this is the NEW position of the cursor 119 const RefPtr<const nsRange> mAnchorRange; 120 121 // ----- 122 // The following members are only for navigation events and are only 123 // stored for FinishNavigationEvent to initialize the other members. 124 // ----- 125 126 // this is the OLD position of the cursor 127 RefPtr<nsRange> mOldNavigationAnchorRange; 128 129 // Set when we should force checking the current word. See 130 // mozInlineSpellChecker::HandleNavigationEvent for a description of why we 131 // have this. 132 const bool mForceNavigationWordCheck; 133 134 // Contains the offset passed in to HandleNavigationEvent 135 const int32_t mNewNavigationPositionOffset; 136 137 nsresult FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil); 138 139 nsresult FillNoCheckRangeFromAnchor(mozInlineSpellWordUtil& aWordUtil); 140 141 mozilla::dom::Document* GetDocument() const; 142 static already_AddRefed<nsRange> PositionToCollapsedRange(nsINode* aNode, 143 uint32_t aOffset); 144 }; 145 146 class mozInlineSpellChecker final : public nsIInlineSpellChecker, 147 public nsIDOMEventListener, 148 public nsSupportsWeakReference { 149 private: 150 friend class mozInlineSpellStatus; 151 friend class InitEditorSpellCheckCallback; 152 friend class UpdateCurrentDictionaryCallback; 153 friend class AutoChangeNumPendingSpellChecks; 154 155 // Access with CanEnableInlineSpellChecking 156 enum SpellCheckingState { 157 SpellCheck_Uninitialized = -1, 158 SpellCheck_NotAvailable = 0, 159 SpellCheck_Available = 1 160 }; 161 static SpellCheckingState gCanEnableSpellChecking; 162 163 RefPtr<mozilla::EditorBase> mEditorBase; 164 RefPtr<mozilla::EditorSpellCheck> mSpellCheck; 165 RefPtr<mozilla::EditorSpellCheck> mPendingSpellCheck; 166 167 int32_t mNumWordsInSpellSelection; 168 const int32_t mMaxNumWordsInSpellSelection; 169 170 // we need to keep track of the current text position in the document 171 // so we can spell check the old word when the user clicks around the 172 // document. 173 nsCOMPtr<nsINode> mCurrentSelectionAnchorNode; 174 uint32_t mCurrentSelectionOffset; 175 176 // Tracks the number of pending spell checks *and* async operations that may 177 // lead to spell checks, like updating the current dictionary. This is 178 // necessary so that observers can know when to wait for spell check to 179 // complete. 180 int32_t mNumPendingSpellChecks; 181 182 // The number of calls to UpdateCurrentDictionary that haven't finished yet. 183 int32_t mNumPendingUpdateCurrentDictionary; 184 185 // This number is incremented each time the spell checker is disabled so that 186 // pending scheduled spell checks and UpdateCurrentDictionary calls can be 187 // ignored when they finish. 188 uint32_t mDisabledAsyncToken; 189 190 // When mPendingSpellCheck is non-null, this is the callback passed when 191 // it was initialized. 192 RefPtr<InitEditorSpellCheckCallback> mPendingInitEditorSpellCheckCallback; 193 194 // Set when we have spellchecked after the last edit operation. See the 195 // commment at the top of the .cpp file for more info. 196 bool mNeedsCheckAfterNavigation; 197 198 // Set when we have a pending mozInlineSpellResume which will check 199 // the whole document. 200 bool mFullSpellCheckScheduled; 201 202 // Set to true when this instance needs to listen to edit actions of 203 // the editor. 204 bool mIsListeningToEditSubActions; 205 206 class SpellCheckerSlice; 207 208 public: 209 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 210 NS_DECL_NSIINLINESPELLCHECKER 211 NS_DECL_NSIDOMEVENTLISTENER 212 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(mozInlineSpellChecker, 213 nsIDOMEventListener) 214 215 mozilla::EditorSpellCheck* GetEditorSpellCheck(); 216 217 // See `mDisabledAsyncToken`. 218 uint32_t GetDisabledAsyncToken() const { return mDisabledAsyncToken; } 219 220 // returns true if there are any spell checking dictionaries available 221 static bool CanEnableInlineSpellChecking(); 222 // update the cached value whenever the list of available dictionaries changes 223 static void UpdateCanEnableInlineSpellChecking(); 224 225 mozInlineSpellChecker(); 226 227 // spell checks all of the words between two nodes 228 nsresult SpellCheckBetweenNodes(nsINode* aStartNode, int32_t aStartOffset, 229 nsINode* aEndNode, int32_t aEndOffset); 230 231 // examines the dom node in question and returns true if the inline spell 232 // checker should skip the node (i.e. the text is inside of a block quote 233 // or an e-mail signature...) 234 static bool ShouldSpellCheckNode(mozilla::EditorBase* aEditorBase, 235 nsINode* aNode); 236 237 // spell check the text contained within aRange, potentially scheduling 238 // another check in the future if the time threshold is reached 239 nsresult ScheduleSpellCheck( 240 mozilla::UniquePtr<mozInlineSpellStatus>&& aStatus); 241 242 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult 243 DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil, 244 mozilla::dom::Selection* aSpellCheckSelection); 245 246 nsresult DoSpellCheck(mozInlineSpellWordUtil& aWordUtil, 247 mozilla::dom::Selection* aSpellCheckSelection, 248 const mozilla::UniquePtr<mozInlineSpellStatus>& aStatus, 249 bool* aDoneChecking); 250 251 // helper routine to determine if a point is inside of the passed in 252 // selection. 253 static nsresult IsPointInSelection(mozilla::dom::Selection& aSelection, 254 nsINode* aNode, uint32_t aOffset, 255 nsRange** aRange); 256 257 nsresult CleanupRangesInSelection(mozilla::dom::Selection* aSelection); 258 259 /** 260 * @param aRange needs to be kept alive by the caller. 261 */ 262 // TODO: annotate with `MOZ_CAN_RUN_SCRIPT` instead 263 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1620540). 264 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult 265 RemoveRange(mozilla::dom::Selection* aSpellCheckSelection, nsRange* aRange); 266 267 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult 268 AddRange(mozilla::dom::Selection* aSpellCheckSelection, nsRange* aRange); 269 bool IsSpellCheckSelectionFull() const { 270 return mNumWordsInSpellSelection >= mMaxNumWordsInSpellSelection; 271 } 272 273 nsresult MakeSpellCheckRange(nsINode* aStartNode, int32_t aStartOffset, 274 nsINode* aEndNode, int32_t aEndOffset, 275 nsRange** aRange) const; 276 277 // DOM and editor event registration helper routines 278 nsresult RegisterEventListeners(); 279 nsresult UnregisterEventListeners(); 280 nsresult HandleNavigationEvent(bool aForceWordSpellCheck, 281 int32_t aNewPositionOffset = 0); 282 283 already_AddRefed<mozilla::dom::Selection> GetSpellCheckSelection(); 284 nsresult SaveCurrentSelectionPosition(); 285 286 nsresult ResumeCheck(mozilla::UniquePtr<mozInlineSpellStatus>&& aStatus); 287 288 nsresult SpellCheckAfterEditorChange(mozilla::EditSubAction aEditSubAction, 289 mozilla::dom::Selection& aSelection, 290 nsINode* aPreviousSelectedNode, 291 uint32_t aPreviousSelectedOffset, 292 nsINode* aStartNode, 293 uint32_t aStartOffset, nsINode* aEndNode, 294 uint32_t aEndOffset); 295 296 protected: 297 virtual ~mozInlineSpellChecker(); 298 299 struct CompareRangeAndNodeOffsetRange; 300 301 // Ensures that all misspelled words have corresponding ranges in 302 // aSpellCheckerSelection. Reuses those of the old ranges, which still 303 // correspond to misspelled words and adds new ranges for those misspelled 304 // words for which no corresponding old range exists. 305 // Removes the old ranges which aren't reused from aSpellCheckerSelection. 306 // 307 // @param aNodeOffsetRangesForWords corresponds to aIsMisspelled. 308 // `aNodeOffsetRangesForWords.Length() == 309 // aIsMisspelled.Length()`. 310 // @param aOldRangesForSomeWords ranges belonging to aSpellCheckerSelection. 311 // Its length may differ from 312 // `aNodeOffsetRangesForWords.Length()`. 313 // @param aIsMisspelled indicates which words are misspelled. 314 MOZ_CAN_RUN_SCRIPT_BOUNDARY void UpdateRangesForMisspelledWords( 315 const nsTArray<NodeOffsetRange>& aNodeOffsetRangesForWords, 316 const nsTArray<RefPtr<nsRange>>& aOldRangesForSomeWords, 317 const nsTArray<bool>& aIsMisspelled, 318 mozilla::dom::Selection& aSpellCheckerSelection); 319 320 // called when async nsIEditorSpellCheck methods complete 321 nsresult EditorSpellCheckInited(); 322 nsresult CurrentDictionaryUpdated(); 323 324 // track the number of pending spell checks and async operations that may lead 325 // to spell checks, notifying observers accordingly 326 void ChangeNumPendingSpellChecks(int32_t aDelta, 327 mozilla::EditorBase* aEditorBase = nullptr); 328 void NotifyObservers(const char* aTopic, mozilla::EditorBase* aEditorBase); 329 330 void StartToListenToEditSubActions() { mIsListeningToEditSubActions = true; } 331 void EndListeningToEditSubActions() { mIsListeningToEditSubActions = false; } 332 333 void OnBlur(mozilla::dom::Event& aEvent); 334 void OnPointerClick(mozilla::dom::Event& aPointerEvent); 335 void OnKeyDown(mozilla::dom::Event& aKeyEvent); 336 }; 337 338 #endif // #ifndef mozilla_mozInlineSpellChecker_h