tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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