diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /extensions/spellcheck/src/mozInlineSpellChecker.h | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extensions/spellcheck/src/mozInlineSpellChecker.h')
-rw-r--r-- | extensions/spellcheck/src/mozInlineSpellChecker.h | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/extensions/spellcheck/src/mozInlineSpellChecker.h b/extensions/spellcheck/src/mozInlineSpellChecker.h new file mode 100644 index 0000000000..0e304c93a2 --- /dev/null +++ b/extensions/spellcheck/src/mozInlineSpellChecker.h @@ -0,0 +1,338 @@ +/* -*- 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/. */ + +#ifndef mozilla_mozInlineSpellChecker_h +#define mozilla_mozInlineSpellChecker_h + +#include "nsCycleCollectionParticipant.h" +#include "nsIDOMEventListener.h" +#include "nsIEditorSpellCheck.h" +#include "nsIInlineSpellChecker.h" +#include "mozInlineSpellWordUtil.h" +#include "mozilla/EditorDOMPoint.h" +#include "mozilla/Result.h" +#include "nsRange.h" +#include "nsWeakReference.h" + +class InitEditorSpellCheckCallback; +class mozInlineSpellChecker; +class mozInlineSpellResume; +class UpdateCurrentDictionaryCallback; + +namespace mozilla { +class EditorBase; +class EditorSpellCheck; +enum class EditSubAction : int32_t; +enum class JoinNodesDirection; + +namespace dom { +class Event; +} // namespace dom +} // namespace mozilla + +class mozInlineSpellStatus { + public: + static mozilla::Result<mozilla::UniquePtr<mozInlineSpellStatus>, nsresult> + CreateForEditorChange(mozInlineSpellChecker& aSpellChecker, + mozilla::EditSubAction aEditSubAction, + nsINode* aAnchorNode, uint32_t aAnchorOffset, + nsINode* aPreviousNode, uint32_t aPreviousOffset, + nsINode* aStartNode, uint32_t aStartOffset, + nsINode* aEndNode, uint32_t aEndOffset); + + static mozilla::Result<mozilla::UniquePtr<mozInlineSpellStatus>, nsresult> + CreateForNavigation(mozInlineSpellChecker& aSpellChecker, bool aForceCheck, + int32_t aNewPositionOffset, nsINode* aOldAnchorNode, + uint32_t aOldAnchorOffset, nsINode* aNewAnchorNode, + uint32_t aNewAnchorOffset, bool* aContinue); + + static mozilla::UniquePtr<mozInlineSpellStatus> CreateForSelection( + mozInlineSpellChecker& aSpellChecker); + + static mozilla::UniquePtr<mozInlineSpellStatus> CreateForRange( + mozInlineSpellChecker& aSpellChecker, nsRange* aRange); + + nsresult FinishInitOnEvent(mozInlineSpellWordUtil& aWordUtil); + + // Return true if we plan to spell-check everything + bool IsFullSpellCheck() const { return mOp == eOpChange && !mRange; } + + const RefPtr<mozInlineSpellChecker> mSpellChecker; + + enum Operation { + eOpChange, // for SpellCheckAfterEditorChange except + // deleteSelection + eOpChangeDelete, // for SpellCheckAfterEditorChange with + // deleteSelection + eOpNavigation, // for HandleNavigationEvent + eOpSelection, // re-check all misspelled words + eOpResume + }; + + // See `mOp`. + Operation GetOperation() const { return mOp; } + + // Used for events where we have already computed the range to use. It can + // also be nullptr in these cases where we need to check the entire range. + RefPtr<nsRange> mRange; + + // See `mCreatedRange`. + const nsRange* GetCreatedRange() const { return mCreatedRange; } + + // See `mNoCheckRange`. + const nsRange* GetNoCheckRange() const { return mNoCheckRange; } + + private: + // @param aSpellChecker must be non-nullptr. + // @param aOp see mOp. + // @param aRange see mRange. + // @param aCreatedRange see mCreatedRange. + // @param aAnchorRange see mAnchorRange. + // @param aForceNavigationWordCheck see mForceNavigationWordCheck. + // @param aNewNavigationPositionOffset see mNewNavigationPositionOffset. + explicit mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker, + Operation aOp, RefPtr<nsRange>&& aRange, + RefPtr<nsRange>&& aCreatedRange, + RefPtr<nsRange>&& aAnchorRange, + bool aForceNavigationWordCheck, + int32_t aNewNavigationPositionOffset); + + // For resuming a previously started check. + const Operation mOp; + + // + // If we happen to know something was inserted, this is that range. + // Can be nullptr (this only allows an optimization, so not setting doesn't + // hurt) + const RefPtr<const nsRange> mCreatedRange; + + // Contains the range computed for the current word. Can be nullptr. + RefPtr<nsRange> mNoCheckRange; + + // Indicates the position of the cursor for the event (so we can compute + // mNoCheckRange). It can be nullptr if we don't care about the cursor + // position (such as for the intial check of everything). + // + // For mOp == eOpNavigation, this is the NEW position of the cursor + const RefPtr<const nsRange> mAnchorRange; + + // ----- + // The following members are only for navigation events and are only + // stored for FinishNavigationEvent to initialize the other members. + // ----- + + // this is the OLD position of the cursor + RefPtr<nsRange> mOldNavigationAnchorRange; + + // Set when we should force checking the current word. See + // mozInlineSpellChecker::HandleNavigationEvent for a description of why we + // have this. + const bool mForceNavigationWordCheck; + + // Contains the offset passed in to HandleNavigationEvent + const int32_t mNewNavigationPositionOffset; + + nsresult FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil); + + nsresult FillNoCheckRangeFromAnchor(mozInlineSpellWordUtil& aWordUtil); + + mozilla::dom::Document* GetDocument() const; + static already_AddRefed<nsRange> PositionToCollapsedRange(nsINode* aNode, + uint32_t aOffset); +}; + +class mozInlineSpellChecker final : public nsIInlineSpellChecker, + public nsIDOMEventListener, + public nsSupportsWeakReference { + private: + friend class mozInlineSpellStatus; + friend class InitEditorSpellCheckCallback; + friend class UpdateCurrentDictionaryCallback; + friend class AutoChangeNumPendingSpellChecks; + + // Access with CanEnableInlineSpellChecking + enum SpellCheckingState { + SpellCheck_Uninitialized = -1, + SpellCheck_NotAvailable = 0, + SpellCheck_Available = 1 + }; + static SpellCheckingState gCanEnableSpellChecking; + + RefPtr<mozilla::EditorBase> mEditorBase; + RefPtr<mozilla::EditorSpellCheck> mSpellCheck; + RefPtr<mozilla::EditorSpellCheck> mPendingSpellCheck; + + int32_t mNumWordsInSpellSelection; + const int32_t mMaxNumWordsInSpellSelection; + + // we need to keep track of the current text position in the document + // so we can spell check the old word when the user clicks around the + // document. + nsCOMPtr<nsINode> mCurrentSelectionAnchorNode; + uint32_t mCurrentSelectionOffset; + + // Tracks the number of pending spell checks *and* async operations that may + // lead to spell checks, like updating the current dictionary. This is + // necessary so that observers can know when to wait for spell check to + // complete. + int32_t mNumPendingSpellChecks; + + // The number of calls to UpdateCurrentDictionary that haven't finished yet. + int32_t mNumPendingUpdateCurrentDictionary; + + // This number is incremented each time the spell checker is disabled so that + // pending scheduled spell checks and UpdateCurrentDictionary calls can be + // ignored when they finish. + uint32_t mDisabledAsyncToken; + + // When mPendingSpellCheck is non-null, this is the callback passed when + // it was initialized. + RefPtr<InitEditorSpellCheckCallback> mPendingInitEditorSpellCheckCallback; + + // Set when we have spellchecked after the last edit operation. See the + // commment at the top of the .cpp file for more info. + bool mNeedsCheckAfterNavigation; + + // Set when we have a pending mozInlineSpellResume which will check + // the whole document. + bool mFullSpellCheckScheduled; + + // Set to true when this instance needs to listen to edit actions of + // the editor. + bool mIsListeningToEditSubActions; + + class SpellCheckerSlice; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIINLINESPELLCHECKER + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(mozInlineSpellChecker, + nsIDOMEventListener) + + mozilla::EditorSpellCheck* GetEditorSpellCheck(); + + // See `mDisabledAsyncToken`. + uint32_t GetDisabledAsyncToken() const { return mDisabledAsyncToken; } + + // returns true if there are any spell checking dictionaries available + static bool CanEnableInlineSpellChecking(); + // update the cached value whenever the list of available dictionaries changes + static void UpdateCanEnableInlineSpellChecking(); + + mozInlineSpellChecker(); + + // spell checks all of the words between two nodes + nsresult SpellCheckBetweenNodes(nsINode* aStartNode, int32_t aStartOffset, + nsINode* aEndNode, int32_t aEndOffset); + + // examines the dom node in question and returns true if the inline spell + // checker should skip the node (i.e. the text is inside of a block quote + // or an e-mail signature...) + static bool ShouldSpellCheckNode(mozilla::EditorBase* aEditorBase, + nsINode* aNode); + + // spell check the text contained within aRange, potentially scheduling + // another check in the future if the time threshold is reached + nsresult ScheduleSpellCheck( + mozilla::UniquePtr<mozInlineSpellStatus>&& aStatus); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult + DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil, + mozilla::dom::Selection* aSpellCheckSelection); + + nsresult DoSpellCheck(mozInlineSpellWordUtil& aWordUtil, + mozilla::dom::Selection* aSpellCheckSelection, + const mozilla::UniquePtr<mozInlineSpellStatus>& aStatus, + bool* aDoneChecking); + + // helper routine to determine if a point is inside of the passed in + // selection. + static nsresult IsPointInSelection(mozilla::dom::Selection& aSelection, + nsINode* aNode, uint32_t aOffset, + nsRange** aRange); + + nsresult CleanupRangesInSelection(mozilla::dom::Selection* aSelection); + + /** + * @param aRange needs to be kept alive by the caller. + */ + // TODO: annotate with `MOZ_CAN_RUN_SCRIPT` instead + // (https://bugzilla.mozilla.org/show_bug.cgi?id=1620540). + MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult + RemoveRange(mozilla::dom::Selection* aSpellCheckSelection, nsRange* aRange); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult + AddRange(mozilla::dom::Selection* aSpellCheckSelection, nsRange* aRange); + bool IsSpellCheckSelectionFull() const { + return mNumWordsInSpellSelection >= mMaxNumWordsInSpellSelection; + } + + nsresult MakeSpellCheckRange(nsINode* aStartNode, int32_t aStartOffset, + nsINode* aEndNode, int32_t aEndOffset, + nsRange** aRange) const; + + // DOM and editor event registration helper routines + nsresult RegisterEventListeners(); + nsresult UnregisterEventListeners(); + nsresult HandleNavigationEvent(bool aForceWordSpellCheck, + int32_t aNewPositionOffset = 0); + + already_AddRefed<mozilla::dom::Selection> GetSpellCheckSelection(); + nsresult SaveCurrentSelectionPosition(); + + nsresult ResumeCheck(mozilla::UniquePtr<mozInlineSpellStatus>&& aStatus); + + nsresult SpellCheckAfterEditorChange(mozilla::EditSubAction aEditSubAction, + mozilla::dom::Selection& aSelection, + nsINode* aPreviousSelectedNode, + uint32_t aPreviousSelectedOffset, + nsINode* aStartNode, + uint32_t aStartOffset, nsINode* aEndNode, + uint32_t aEndOffset); + + protected: + virtual ~mozInlineSpellChecker(); + + struct CompareRangeAndNodeOffsetRange; + + // Ensures that all misspelled words have corresponding ranges in + // aSpellCheckerSelection. Reuses those of the old ranges, which still + // correspond to misspelled words and adds new ranges for those misspelled + // words for which no corresponding old range exists. + // Removes the old ranges which aren't reused from aSpellCheckerSelection. + // + // @param aNodeOffsetRangesForWords corresponds to aIsMisspelled. + // `aNodeOffsetRangesForWords.Length() == + // aIsMisspelled.Length()`. + // @param aOldRangesForSomeWords ranges belonging to aSpellCheckerSelection. + // Its length may differ from + // `aNodeOffsetRangesForWords.Length()`. + // @param aIsMisspelled indicates which words are misspelled. + MOZ_CAN_RUN_SCRIPT_BOUNDARY void UpdateRangesForMisspelledWords( + const nsTArray<NodeOffsetRange>& aNodeOffsetRangesForWords, + const nsTArray<RefPtr<nsRange>>& aOldRangesForSomeWords, + const nsTArray<bool>& aIsMisspelled, + mozilla::dom::Selection& aSpellCheckerSelection); + + // called when async nsIEditorSpellCheck methods complete + nsresult EditorSpellCheckInited(); + nsresult CurrentDictionaryUpdated(); + + // track the number of pending spell checks and async operations that may lead + // to spell checks, notifying observers accordingly + void ChangeNumPendingSpellChecks(int32_t aDelta, + mozilla::EditorBase* aEditorBase = nullptr); + void NotifyObservers(const char* aTopic, mozilla::EditorBase* aEditorBase); + + void StartToListenToEditSubActions() { mIsListeningToEditSubActions = true; } + void EndListeningToEditSubActions() { mIsListeningToEditSubActions = false; } + + void OnBlur(mozilla::dom::Event& aEvent); + void OnMouseClick(mozilla::dom::Event& aMouseEvent); + void OnKeyDown(mozilla::dom::Event& aKeyEvent); +}; + +#endif // #ifndef mozilla_mozInlineSpellChecker_h |