Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | /** |
michael@0 | 7 | * This class is called by the editor to handle spellchecking after various |
michael@0 | 8 | * events. The main entrypoint is SpellCheckAfterEditorChange, which is called |
michael@0 | 9 | * when the text is changed. |
michael@0 | 10 | * |
michael@0 | 11 | * It is VERY IMPORTANT that we do NOT do any operations that might cause DOM |
michael@0 | 12 | * notifications to be flushed when we are called from the editor. This is |
michael@0 | 13 | * because the call might originate from a frame, and flushing the |
michael@0 | 14 | * notifications might cause that frame to be deleted. |
michael@0 | 15 | * |
michael@0 | 16 | * Using the WordUtil class to find words causes DOM notifications to be |
michael@0 | 17 | * flushed because it asks for style information. As a result, we post an event |
michael@0 | 18 | * and do all of the spellchecking in that event handler, which occurs later. |
michael@0 | 19 | * We store all DOM pointers in ranges because they are kept up-to-date with |
michael@0 | 20 | * DOM changes that may have happened while the event was on the queue. |
michael@0 | 21 | * |
michael@0 | 22 | * We also allow the spellcheck to be suspended and resumed later. This makes |
michael@0 | 23 | * large pastes or initializations with a lot of text not hang the browser UI. |
michael@0 | 24 | * |
michael@0 | 25 | * An optimization is the mNeedsCheckAfterNavigation flag. This is set to |
michael@0 | 26 | * true when we get any change, and false once there is no possibility |
michael@0 | 27 | * something changed that we need to check on navigation. Navigation events |
michael@0 | 28 | * tend to be a little tricky because we want to check the current word on |
michael@0 | 29 | * exit if something has changed. If we navigate inside the word, we don't want |
michael@0 | 30 | * to do anything. As a result, this flag is cleared in FinishNavigationEvent |
michael@0 | 31 | * when we know that we are checking as a result of navigation. |
michael@0 | 32 | */ |
michael@0 | 33 | |
michael@0 | 34 | #include "mozInlineSpellChecker.h" |
michael@0 | 35 | #include "mozInlineSpellWordUtil.h" |
michael@0 | 36 | #include "mozISpellI18NManager.h" |
michael@0 | 37 | #include "nsCOMPtr.h" |
michael@0 | 38 | #include "nsCRT.h" |
michael@0 | 39 | #include "nsIDOMNode.h" |
michael@0 | 40 | #include "nsIDOMDocument.h" |
michael@0 | 41 | #include "nsIDOMElement.h" |
michael@0 | 42 | #include "nsIDOMHTMLElement.h" |
michael@0 | 43 | #include "nsIDOMMouseEvent.h" |
michael@0 | 44 | #include "nsIDOMKeyEvent.h" |
michael@0 | 45 | #include "nsIDOMNode.h" |
michael@0 | 46 | #include "nsIDOMNodeList.h" |
michael@0 | 47 | #include "nsRange.h" |
michael@0 | 48 | #include "nsIPlaintextEditor.h" |
michael@0 | 49 | #include "nsIPrefBranch.h" |
michael@0 | 50 | #include "nsIPrefService.h" |
michael@0 | 51 | #include "nsIRunnable.h" |
michael@0 | 52 | #include "nsISelection.h" |
michael@0 | 53 | #include "nsISelectionPrivate.h" |
michael@0 | 54 | #include "nsISelectionController.h" |
michael@0 | 55 | #include "nsIServiceManager.h" |
michael@0 | 56 | #include "nsITextServicesFilter.h" |
michael@0 | 57 | #include "nsString.h" |
michael@0 | 58 | #include "nsThreadUtils.h" |
michael@0 | 59 | #include "nsUnicharUtils.h" |
michael@0 | 60 | #include "nsIContent.h" |
michael@0 | 61 | #include "nsRange.h" |
michael@0 | 62 | #include "nsContentUtils.h" |
michael@0 | 63 | #include "nsEditor.h" |
michael@0 | 64 | #include "mozilla/Services.h" |
michael@0 | 65 | #include "nsIObserverService.h" |
michael@0 | 66 | #include "nsITextControlElement.h" |
michael@0 | 67 | #include "prtime.h" |
michael@0 | 68 | |
michael@0 | 69 | using namespace mozilla::dom; |
michael@0 | 70 | |
michael@0 | 71 | // Set to spew messages to the console about what is happening. |
michael@0 | 72 | //#define DEBUG_INLINESPELL |
michael@0 | 73 | |
michael@0 | 74 | // the number of milliseconds that we will take at once to do spellchecking |
michael@0 | 75 | #define INLINESPELL_CHECK_TIMEOUT 50 |
michael@0 | 76 | |
michael@0 | 77 | // The number of words to check before we look at the time to see if |
michael@0 | 78 | // INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from spending |
michael@0 | 79 | // too much time checking the clock. Note that misspelled words count for |
michael@0 | 80 | // more than one word in this calculation. |
michael@0 | 81 | #define INLINESPELL_TIMEOUT_CHECK_FREQUENCY 50 |
michael@0 | 82 | |
michael@0 | 83 | // This number is the number of checked words a misspelled word counts for |
michael@0 | 84 | // when we're checking the time to see if the alloted time is up for |
michael@0 | 85 | // spellchecking. Misspelled words take longer to process since we have to |
michael@0 | 86 | // create a range, so they count more. The exact number isn't very important |
michael@0 | 87 | // since this just controls how often we check the current time. |
michael@0 | 88 | #define MISSPELLED_WORD_COUNT_PENALTY 4 |
michael@0 | 89 | |
michael@0 | 90 | // These notifications are broadcast when spell check starts and ends. STARTED |
michael@0 | 91 | // must always be followed by ENDED. |
michael@0 | 92 | #define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started" |
michael@0 | 93 | #define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended" |
michael@0 | 94 | |
michael@0 | 95 | static bool ContentIsDescendantOf(nsINode* aPossibleDescendant, |
michael@0 | 96 | nsINode* aPossibleAncestor); |
michael@0 | 97 | |
michael@0 | 98 | static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings"; |
michael@0 | 99 | |
michael@0 | 100 | mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker) |
michael@0 | 101 | : mSpellChecker(aSpellChecker), mWordCount(0) |
michael@0 | 102 | { |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | // mozInlineSpellStatus::InitForEditorChange |
michael@0 | 106 | // |
michael@0 | 107 | // This is the most complicated case. For changes, we need to compute the |
michael@0 | 108 | // range of stuff that changed based on the old and new caret positions, |
michael@0 | 109 | // as well as use a range possibly provided by the editor (start and end, |
michael@0 | 110 | // which are usually nullptr) to get a range with the union of these. |
michael@0 | 111 | |
michael@0 | 112 | nsresult |
michael@0 | 113 | mozInlineSpellStatus::InitForEditorChange( |
michael@0 | 114 | EditAction aAction, |
michael@0 | 115 | nsIDOMNode* aAnchorNode, int32_t aAnchorOffset, |
michael@0 | 116 | nsIDOMNode* aPreviousNode, int32_t aPreviousOffset, |
michael@0 | 117 | nsIDOMNode* aStartNode, int32_t aStartOffset, |
michael@0 | 118 | nsIDOMNode* aEndNode, int32_t aEndOffset) |
michael@0 | 119 | { |
michael@0 | 120 | nsresult rv; |
michael@0 | 121 | |
michael@0 | 122 | nsCOMPtr<nsIDOMDocument> doc; |
michael@0 | 123 | rv = GetDocument(getter_AddRefs(doc)); |
michael@0 | 124 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 125 | |
michael@0 | 126 | // save the anchor point as a range so we can find the current word later |
michael@0 | 127 | rv = PositionToCollapsedRange(doc, aAnchorNode, aAnchorOffset, |
michael@0 | 128 | getter_AddRefs(mAnchorRange)); |
michael@0 | 129 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 130 | |
michael@0 | 131 | if (aAction == EditAction::deleteSelection) { |
michael@0 | 132 | // Deletes are easy, the range is just the current anchor. We set the range |
michael@0 | 133 | // to check to be empty, FinishInitOnEvent will fill in the range to be |
michael@0 | 134 | // the current word. |
michael@0 | 135 | mOp = eOpChangeDelete; |
michael@0 | 136 | mRange = nullptr; |
michael@0 | 137 | return NS_OK; |
michael@0 | 138 | } |
michael@0 | 139 | |
michael@0 | 140 | mOp = eOpChange; |
michael@0 | 141 | |
michael@0 | 142 | // range to check |
michael@0 | 143 | nsCOMPtr<nsINode> prevNode = do_QueryInterface(aPreviousNode); |
michael@0 | 144 | NS_ENSURE_STATE(prevNode); |
michael@0 | 145 | |
michael@0 | 146 | mRange = new nsRange(prevNode); |
michael@0 | 147 | |
michael@0 | 148 | // ...we need to put the start and end in the correct order |
michael@0 | 149 | int16_t cmpResult; |
michael@0 | 150 | rv = mAnchorRange->ComparePoint(aPreviousNode, aPreviousOffset, &cmpResult); |
michael@0 | 151 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 152 | if (cmpResult < 0) { |
michael@0 | 153 | // previous anchor node is before the current anchor |
michael@0 | 154 | rv = mRange->SetStart(aPreviousNode, aPreviousOffset); |
michael@0 | 155 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 156 | rv = mRange->SetEnd(aAnchorNode, aAnchorOffset); |
michael@0 | 157 | } else { |
michael@0 | 158 | // previous anchor node is after (or the same as) the current anchor |
michael@0 | 159 | rv = mRange->SetStart(aAnchorNode, aAnchorOffset); |
michael@0 | 160 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 161 | rv = mRange->SetEnd(aPreviousNode, aPreviousOffset); |
michael@0 | 162 | } |
michael@0 | 163 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 164 | |
michael@0 | 165 | // On insert save this range: DoSpellCheck optimizes things in this range. |
michael@0 | 166 | // Otherwise, just leave this nullptr. |
michael@0 | 167 | if (aAction == EditAction::insertText) |
michael@0 | 168 | mCreatedRange = mRange; |
michael@0 | 169 | |
michael@0 | 170 | // if we were given a range, we need to expand our range to encompass it |
michael@0 | 171 | if (aStartNode && aEndNode) { |
michael@0 | 172 | rv = mRange->ComparePoint(aStartNode, aStartOffset, &cmpResult); |
michael@0 | 173 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 174 | if (cmpResult < 0) { // given range starts before |
michael@0 | 175 | rv = mRange->SetStart(aStartNode, aStartOffset); |
michael@0 | 176 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | rv = mRange->ComparePoint(aEndNode, aEndOffset, &cmpResult); |
michael@0 | 180 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 181 | if (cmpResult > 0) { // given range ends after |
michael@0 | 182 | rv = mRange->SetEnd(aEndNode, aEndOffset); |
michael@0 | 183 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 184 | } |
michael@0 | 185 | } |
michael@0 | 186 | |
michael@0 | 187 | return NS_OK; |
michael@0 | 188 | } |
michael@0 | 189 | |
michael@0 | 190 | // mozInlineSpellStatis::InitForNavigation |
michael@0 | 191 | // |
michael@0 | 192 | // For navigation events, we just need to store the new and old positions. |
michael@0 | 193 | // |
michael@0 | 194 | // In some cases, we detect that we shouldn't check. If this event should |
michael@0 | 195 | // not be processed, *aContinue will be false. |
michael@0 | 196 | |
michael@0 | 197 | nsresult |
michael@0 | 198 | mozInlineSpellStatus::InitForNavigation( |
michael@0 | 199 | bool aForceCheck, int32_t aNewPositionOffset, |
michael@0 | 200 | nsIDOMNode* aOldAnchorNode, int32_t aOldAnchorOffset, |
michael@0 | 201 | nsIDOMNode* aNewAnchorNode, int32_t aNewAnchorOffset, |
michael@0 | 202 | bool* aContinue) |
michael@0 | 203 | { |
michael@0 | 204 | nsresult rv; |
michael@0 | 205 | mOp = eOpNavigation; |
michael@0 | 206 | |
michael@0 | 207 | mForceNavigationWordCheck = aForceCheck; |
michael@0 | 208 | mNewNavigationPositionOffset = aNewPositionOffset; |
michael@0 | 209 | |
michael@0 | 210 | // get the root node for checking |
michael@0 | 211 | nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv); |
michael@0 | 212 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 213 | nsCOMPtr<nsIDOMElement> rootElt; |
michael@0 | 214 | rv = editor->GetRootElement(getter_AddRefs(rootElt)); |
michael@0 | 215 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 216 | |
michael@0 | 217 | // the anchor node might not be in the DOM anymore, check |
michael@0 | 218 | nsCOMPtr<nsINode> root = do_QueryInterface(rootElt, &rv); |
michael@0 | 219 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 220 | nsCOMPtr<nsINode> currentAnchor = do_QueryInterface(aOldAnchorNode, &rv); |
michael@0 | 221 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 222 | if (root && currentAnchor && ! ContentIsDescendantOf(currentAnchor, root)) { |
michael@0 | 223 | *aContinue = false; |
michael@0 | 224 | return NS_OK; |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | nsCOMPtr<nsIDOMDocument> doc; |
michael@0 | 228 | rv = GetDocument(getter_AddRefs(doc)); |
michael@0 | 229 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 230 | |
michael@0 | 231 | rv = PositionToCollapsedRange(doc, aOldAnchorNode, aOldAnchorOffset, |
michael@0 | 232 | getter_AddRefs(mOldNavigationAnchorRange)); |
michael@0 | 233 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 234 | rv = PositionToCollapsedRange(doc, aNewAnchorNode, aNewAnchorOffset, |
michael@0 | 235 | getter_AddRefs(mAnchorRange)); |
michael@0 | 236 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 237 | |
michael@0 | 238 | *aContinue = true; |
michael@0 | 239 | return NS_OK; |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | // mozInlineSpellStatus::InitForSelection |
michael@0 | 243 | // |
michael@0 | 244 | // It is easy for selections since we always re-check the spellcheck |
michael@0 | 245 | // selection. |
michael@0 | 246 | |
michael@0 | 247 | nsresult |
michael@0 | 248 | mozInlineSpellStatus::InitForSelection() |
michael@0 | 249 | { |
michael@0 | 250 | mOp = eOpSelection; |
michael@0 | 251 | return NS_OK; |
michael@0 | 252 | } |
michael@0 | 253 | |
michael@0 | 254 | // mozInlineSpellStatus::InitForRange |
michael@0 | 255 | // |
michael@0 | 256 | // Called to cause the spellcheck of the given range. This will look like |
michael@0 | 257 | // a change operation over the given range. |
michael@0 | 258 | |
michael@0 | 259 | nsresult |
michael@0 | 260 | mozInlineSpellStatus::InitForRange(nsRange* aRange) |
michael@0 | 261 | { |
michael@0 | 262 | mOp = eOpChange; |
michael@0 | 263 | mRange = aRange; |
michael@0 | 264 | return NS_OK; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | // mozInlineSpellStatus::FinishInitOnEvent |
michael@0 | 268 | // |
michael@0 | 269 | // Called when the event is triggered to complete initialization that |
michael@0 | 270 | // might require the WordUtil. This calls to the operation-specific |
michael@0 | 271 | // initializer, and also sets the range to be the entire element if it |
michael@0 | 272 | // is nullptr. |
michael@0 | 273 | // |
michael@0 | 274 | // Watch out: the range might still be nullptr if there is nothing to do, |
michael@0 | 275 | // the caller will have to check for this. |
michael@0 | 276 | |
michael@0 | 277 | nsresult |
michael@0 | 278 | mozInlineSpellStatus::FinishInitOnEvent(mozInlineSpellWordUtil& aWordUtil) |
michael@0 | 279 | { |
michael@0 | 280 | nsresult rv; |
michael@0 | 281 | if (! mRange) { |
michael@0 | 282 | rv = mSpellChecker->MakeSpellCheckRange(nullptr, 0, nullptr, 0, |
michael@0 | 283 | getter_AddRefs(mRange)); |
michael@0 | 284 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 285 | } |
michael@0 | 286 | |
michael@0 | 287 | switch (mOp) { |
michael@0 | 288 | case eOpChange: |
michael@0 | 289 | if (mAnchorRange) |
michael@0 | 290 | return FillNoCheckRangeFromAnchor(aWordUtil); |
michael@0 | 291 | break; |
michael@0 | 292 | case eOpChangeDelete: |
michael@0 | 293 | if (mAnchorRange) { |
michael@0 | 294 | rv = FillNoCheckRangeFromAnchor(aWordUtil); |
michael@0 | 295 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 296 | } |
michael@0 | 297 | // Delete events will have no range for the changed text (because it was |
michael@0 | 298 | // deleted), and InitForEditorChange will set it to nullptr. Here, we select |
michael@0 | 299 | // the entire word to cause any underlining to be removed. |
michael@0 | 300 | mRange = mNoCheckRange; |
michael@0 | 301 | break; |
michael@0 | 302 | case eOpNavigation: |
michael@0 | 303 | return FinishNavigationEvent(aWordUtil); |
michael@0 | 304 | case eOpSelection: |
michael@0 | 305 | // this gets special handling in ResumeCheck |
michael@0 | 306 | break; |
michael@0 | 307 | case eOpResume: |
michael@0 | 308 | // everything should be initialized already in this case |
michael@0 | 309 | break; |
michael@0 | 310 | default: |
michael@0 | 311 | NS_NOTREACHED("Bad operation"); |
michael@0 | 312 | return NS_ERROR_NOT_INITIALIZED; |
michael@0 | 313 | } |
michael@0 | 314 | return NS_OK; |
michael@0 | 315 | } |
michael@0 | 316 | |
michael@0 | 317 | // mozInlineSpellStatus::FinishNavigationEvent |
michael@0 | 318 | // |
michael@0 | 319 | // This verifies that we need to check the word at the previous caret |
michael@0 | 320 | // position. Now that we have the word util, we can find the word belonging |
michael@0 | 321 | // to the previous caret position. If the new position is inside that word, |
michael@0 | 322 | // we don't want to do anything. In this case, we'll nullptr out mRange so |
michael@0 | 323 | // that the caller will know not to continue. |
michael@0 | 324 | // |
michael@0 | 325 | // Notice that we don't set mNoCheckRange. We check here whether the cursor |
michael@0 | 326 | // is in the word that needs checking, so it isn't necessary. Plus, the |
michael@0 | 327 | // spellchecker isn't guaranteed to only check the given word, and it could |
michael@0 | 328 | // remove the underline from the new word under the cursor. |
michael@0 | 329 | |
michael@0 | 330 | nsresult |
michael@0 | 331 | mozInlineSpellStatus::FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil) |
michael@0 | 332 | { |
michael@0 | 333 | nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor); |
michael@0 | 334 | if (! editor) |
michael@0 | 335 | return NS_ERROR_FAILURE; // editor is gone |
michael@0 | 336 | |
michael@0 | 337 | NS_ASSERTION(mAnchorRange, "No anchor for navigation!"); |
michael@0 | 338 | nsCOMPtr<nsIDOMNode> newAnchorNode, oldAnchorNode; |
michael@0 | 339 | int32_t newAnchorOffset, oldAnchorOffset; |
michael@0 | 340 | |
michael@0 | 341 | // get the DOM position of the old caret, the range should be collapsed |
michael@0 | 342 | nsresult rv = mOldNavigationAnchorRange->GetStartContainer( |
michael@0 | 343 | getter_AddRefs(oldAnchorNode)); |
michael@0 | 344 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 345 | rv = mOldNavigationAnchorRange->GetStartOffset(&oldAnchorOffset); |
michael@0 | 346 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 347 | |
michael@0 | 348 | // find the word on the old caret position, this is the one that we MAY need |
michael@0 | 349 | // to check |
michael@0 | 350 | nsRefPtr<nsRange> oldWord; |
michael@0 | 351 | rv = aWordUtil.GetRangeForWord(oldAnchorNode, oldAnchorOffset, |
michael@0 | 352 | getter_AddRefs(oldWord)); |
michael@0 | 353 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 354 | |
michael@0 | 355 | // aWordUtil.GetRangeForWord flushes pending notifications, check editor again. |
michael@0 | 356 | editor = do_QueryReferent(mSpellChecker->mEditor); |
michael@0 | 357 | if (! editor) |
michael@0 | 358 | return NS_ERROR_FAILURE; // editor is gone |
michael@0 | 359 | |
michael@0 | 360 | // get the DOM position of the new caret, the range should be collapsed |
michael@0 | 361 | rv = mAnchorRange->GetStartContainer(getter_AddRefs(newAnchorNode)); |
michael@0 | 362 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 363 | rv = mAnchorRange->GetStartOffset(&newAnchorOffset); |
michael@0 | 364 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 365 | |
michael@0 | 366 | // see if the new cursor position is in the word of the old cursor position |
michael@0 | 367 | bool isInRange = false; |
michael@0 | 368 | if (! mForceNavigationWordCheck) { |
michael@0 | 369 | rv = oldWord->IsPointInRange(newAnchorNode, |
michael@0 | 370 | newAnchorOffset + mNewNavigationPositionOffset, |
michael@0 | 371 | &isInRange); |
michael@0 | 372 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | if (isInRange) { |
michael@0 | 376 | // caller should give up |
michael@0 | 377 | mRange = nullptr; |
michael@0 | 378 | } else { |
michael@0 | 379 | // check the old word |
michael@0 | 380 | mRange = oldWord; |
michael@0 | 381 | |
michael@0 | 382 | // Once we've spellchecked the current word, we don't need to spellcheck |
michael@0 | 383 | // for any more navigation events. |
michael@0 | 384 | mSpellChecker->mNeedsCheckAfterNavigation = false; |
michael@0 | 385 | } |
michael@0 | 386 | return NS_OK; |
michael@0 | 387 | } |
michael@0 | 388 | |
michael@0 | 389 | // mozInlineSpellStatus::FillNoCheckRangeFromAnchor |
michael@0 | 390 | // |
michael@0 | 391 | // Given the mAnchorRange object, computes the range of the word it is on |
michael@0 | 392 | // (if any) and fills that range into mNoCheckRange. This is used for |
michael@0 | 393 | // change and navigation events to know which word we should skip spell |
michael@0 | 394 | // checking on |
michael@0 | 395 | |
michael@0 | 396 | nsresult |
michael@0 | 397 | mozInlineSpellStatus::FillNoCheckRangeFromAnchor( |
michael@0 | 398 | mozInlineSpellWordUtil& aWordUtil) |
michael@0 | 399 | { |
michael@0 | 400 | nsCOMPtr<nsIDOMNode> anchorNode; |
michael@0 | 401 | nsresult rv = mAnchorRange->GetStartContainer(getter_AddRefs(anchorNode)); |
michael@0 | 402 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 403 | |
michael@0 | 404 | int32_t anchorOffset; |
michael@0 | 405 | rv = mAnchorRange->GetStartOffset(&anchorOffset); |
michael@0 | 406 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 407 | |
michael@0 | 408 | return aWordUtil.GetRangeForWord(anchorNode, anchorOffset, |
michael@0 | 409 | getter_AddRefs(mNoCheckRange)); |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | // mozInlineSpellStatus::GetDocument |
michael@0 | 413 | // |
michael@0 | 414 | // Returns the nsIDOMDocument object for the document for the |
michael@0 | 415 | // current spellchecker. |
michael@0 | 416 | |
michael@0 | 417 | nsresult |
michael@0 | 418 | mozInlineSpellStatus::GetDocument(nsIDOMDocument** aDocument) |
michael@0 | 419 | { |
michael@0 | 420 | nsresult rv; |
michael@0 | 421 | *aDocument = nullptr; |
michael@0 | 422 | if (! mSpellChecker->mEditor) |
michael@0 | 423 | return NS_ERROR_UNEXPECTED; |
michael@0 | 424 | |
michael@0 | 425 | nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv); |
michael@0 | 426 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 427 | |
michael@0 | 428 | nsCOMPtr<nsIDOMDocument> domDoc; |
michael@0 | 429 | rv = editor->GetDocument(getter_AddRefs(domDoc)); |
michael@0 | 430 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 431 | NS_ENSURE_TRUE(domDoc, NS_ERROR_NULL_POINTER); |
michael@0 | 432 | domDoc.forget(aDocument); |
michael@0 | 433 | return NS_OK; |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | // mozInlineSpellStatus::PositionToCollapsedRange |
michael@0 | 437 | // |
michael@0 | 438 | // Converts a given DOM position to a collapsed range covering that |
michael@0 | 439 | // position. We use ranges to store DOM positions becuase they stay |
michael@0 | 440 | // updated as the DOM is changed. |
michael@0 | 441 | |
michael@0 | 442 | nsresult |
michael@0 | 443 | mozInlineSpellStatus::PositionToCollapsedRange(nsIDOMDocument* aDocument, |
michael@0 | 444 | nsIDOMNode* aNode, int32_t aOffset, nsIDOMRange** aRange) |
michael@0 | 445 | { |
michael@0 | 446 | *aRange = nullptr; |
michael@0 | 447 | nsCOMPtr<nsIDOMRange> range; |
michael@0 | 448 | nsresult rv = aDocument->CreateRange(getter_AddRefs(range)); |
michael@0 | 449 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 450 | |
michael@0 | 451 | rv = range->SetStart(aNode, aOffset); |
michael@0 | 452 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 453 | rv = range->SetEnd(aNode, aOffset); |
michael@0 | 454 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 455 | |
michael@0 | 456 | range.swap(*aRange); |
michael@0 | 457 | return NS_OK; |
michael@0 | 458 | } |
michael@0 | 459 | |
michael@0 | 460 | // mozInlineSpellResume |
michael@0 | 461 | |
michael@0 | 462 | class mozInlineSpellResume : public nsRunnable |
michael@0 | 463 | { |
michael@0 | 464 | public: |
michael@0 | 465 | mozInlineSpellResume(const mozInlineSpellStatus& aStatus, |
michael@0 | 466 | uint32_t aDisabledAsyncToken) |
michael@0 | 467 | : mDisabledAsyncToken(aDisabledAsyncToken), mStatus(aStatus) {} |
michael@0 | 468 | |
michael@0 | 469 | nsresult Post() |
michael@0 | 470 | { |
michael@0 | 471 | return NS_DispatchToMainThread(this); |
michael@0 | 472 | } |
michael@0 | 473 | |
michael@0 | 474 | NS_IMETHOD Run() |
michael@0 | 475 | { |
michael@0 | 476 | // Discard the resumption if the spell checker was disabled after the |
michael@0 | 477 | // resumption was scheduled. |
michael@0 | 478 | if (mDisabledAsyncToken == mStatus.mSpellChecker->mDisabledAsyncToken) { |
michael@0 | 479 | mStatus.mSpellChecker->ResumeCheck(&mStatus); |
michael@0 | 480 | } |
michael@0 | 481 | return NS_OK; |
michael@0 | 482 | } |
michael@0 | 483 | |
michael@0 | 484 | private: |
michael@0 | 485 | uint32_t mDisabledAsyncToken; |
michael@0 | 486 | mozInlineSpellStatus mStatus; |
michael@0 | 487 | }; |
michael@0 | 488 | |
michael@0 | 489 | // Used as the nsIEditorSpellCheck::InitSpellChecker callback. |
michael@0 | 490 | class InitEditorSpellCheckCallback MOZ_FINAL : public nsIEditorSpellCheckCallback |
michael@0 | 491 | { |
michael@0 | 492 | public: |
michael@0 | 493 | NS_DECL_ISUPPORTS |
michael@0 | 494 | |
michael@0 | 495 | explicit InitEditorSpellCheckCallback(mozInlineSpellChecker* aSpellChecker) |
michael@0 | 496 | : mSpellChecker(aSpellChecker) {} |
michael@0 | 497 | |
michael@0 | 498 | NS_IMETHOD EditorSpellCheckDone() |
michael@0 | 499 | { |
michael@0 | 500 | return mSpellChecker ? mSpellChecker->EditorSpellCheckInited() : NS_OK; |
michael@0 | 501 | } |
michael@0 | 502 | |
michael@0 | 503 | void Cancel() |
michael@0 | 504 | { |
michael@0 | 505 | mSpellChecker = nullptr; |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | private: |
michael@0 | 509 | nsRefPtr<mozInlineSpellChecker> mSpellChecker; |
michael@0 | 510 | }; |
michael@0 | 511 | NS_IMPL_ISUPPORTS(InitEditorSpellCheckCallback, nsIEditorSpellCheckCallback) |
michael@0 | 512 | |
michael@0 | 513 | |
michael@0 | 514 | NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker) |
michael@0 | 515 | NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker) |
michael@0 | 516 | NS_INTERFACE_MAP_ENTRY(nsIEditActionListener) |
michael@0 | 517 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
michael@0 | 518 | NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) |
michael@0 | 519 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) |
michael@0 | 520 | NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozInlineSpellChecker) |
michael@0 | 521 | NS_INTERFACE_MAP_END |
michael@0 | 522 | |
michael@0 | 523 | NS_IMPL_CYCLE_COLLECTING_ADDREF(mozInlineSpellChecker) |
michael@0 | 524 | NS_IMPL_CYCLE_COLLECTING_RELEASE(mozInlineSpellChecker) |
michael@0 | 525 | |
michael@0 | 526 | NS_IMPL_CYCLE_COLLECTION(mozInlineSpellChecker, |
michael@0 | 527 | mSpellCheck, |
michael@0 | 528 | mTreeWalker, |
michael@0 | 529 | mCurrentSelectionAnchorNode) |
michael@0 | 530 | |
michael@0 | 531 | mozInlineSpellChecker::SpellCheckingState |
michael@0 | 532 | mozInlineSpellChecker::gCanEnableSpellChecking = |
michael@0 | 533 | mozInlineSpellChecker::SpellCheck_Uninitialized; |
michael@0 | 534 | |
michael@0 | 535 | mozInlineSpellChecker::mozInlineSpellChecker() : |
michael@0 | 536 | mNumWordsInSpellSelection(0), |
michael@0 | 537 | mMaxNumWordsInSpellSelection(250), |
michael@0 | 538 | mNumPendingSpellChecks(0), |
michael@0 | 539 | mNumPendingUpdateCurrentDictionary(0), |
michael@0 | 540 | mDisabledAsyncToken(0), |
michael@0 | 541 | mNeedsCheckAfterNavigation(false), |
michael@0 | 542 | mFullSpellCheckScheduled(false) |
michael@0 | 543 | { |
michael@0 | 544 | nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
michael@0 | 545 | if (prefs) |
michael@0 | 546 | prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection); |
michael@0 | 547 | mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4; |
michael@0 | 548 | } |
michael@0 | 549 | |
michael@0 | 550 | mozInlineSpellChecker::~mozInlineSpellChecker() |
michael@0 | 551 | { |
michael@0 | 552 | } |
michael@0 | 553 | |
michael@0 | 554 | NS_IMETHODIMP |
michael@0 | 555 | mozInlineSpellChecker::GetSpellChecker(nsIEditorSpellCheck **aSpellCheck) |
michael@0 | 556 | { |
michael@0 | 557 | *aSpellCheck = mSpellCheck; |
michael@0 | 558 | NS_IF_ADDREF(*aSpellCheck); |
michael@0 | 559 | return NS_OK; |
michael@0 | 560 | } |
michael@0 | 561 | |
michael@0 | 562 | NS_IMETHODIMP |
michael@0 | 563 | mozInlineSpellChecker::Init(nsIEditor *aEditor) |
michael@0 | 564 | { |
michael@0 | 565 | mEditor = do_GetWeakReference(aEditor); |
michael@0 | 566 | return NS_OK; |
michael@0 | 567 | } |
michael@0 | 568 | |
michael@0 | 569 | // mozInlineSpellChecker::Cleanup |
michael@0 | 570 | // |
michael@0 | 571 | // Called by the editor when the editor is going away. This is important |
michael@0 | 572 | // because we remove listeners. We do NOT clean up anything else in this |
michael@0 | 573 | // function, because it can get called while DoSpellCheck is running! |
michael@0 | 574 | // |
michael@0 | 575 | // Getting the style information there can cause DOM notifications to be |
michael@0 | 576 | // flushed, which can cause editors to go away which will bring us here. |
michael@0 | 577 | // We can not do anything that will cause DoSpellCheck to freak out. |
michael@0 | 578 | |
michael@0 | 579 | nsresult mozInlineSpellChecker::Cleanup(bool aDestroyingFrames) |
michael@0 | 580 | { |
michael@0 | 581 | mNumWordsInSpellSelection = 0; |
michael@0 | 582 | nsCOMPtr<nsISelection> spellCheckSelection; |
michael@0 | 583 | nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); |
michael@0 | 584 | if (NS_FAILED(rv)) { |
michael@0 | 585 | // Ensure we still unregister event listeners (but return a failure code) |
michael@0 | 586 | UnregisterEventListeners(); |
michael@0 | 587 | } else { |
michael@0 | 588 | if (!aDestroyingFrames) { |
michael@0 | 589 | spellCheckSelection->RemoveAllRanges(); |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | rv = UnregisterEventListeners(); |
michael@0 | 593 | } |
michael@0 | 594 | |
michael@0 | 595 | // Notify ENDED observers now. If we wait to notify as we normally do when |
michael@0 | 596 | // these async operations finish, then in the meantime the editor may create |
michael@0 | 597 | // another inline spell checker and cause more STARTED and ENDED |
michael@0 | 598 | // notifications to be broadcast. Interleaved notifications for the same |
michael@0 | 599 | // editor but different inline spell checkers could easily confuse |
michael@0 | 600 | // observers. They may receive two consecutive STARTED notifications for |
michael@0 | 601 | // example, which we guarantee will not happen. |
michael@0 | 602 | |
michael@0 | 603 | nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor); |
michael@0 | 604 | if (mPendingSpellCheck) { |
michael@0 | 605 | // Cancel the pending editor spell checker initialization. |
michael@0 | 606 | mPendingSpellCheck = nullptr; |
michael@0 | 607 | mPendingInitEditorSpellCheckCallback->Cancel(); |
michael@0 | 608 | mPendingInitEditorSpellCheckCallback = nullptr; |
michael@0 | 609 | ChangeNumPendingSpellChecks(-1, editor); |
michael@0 | 610 | } |
michael@0 | 611 | |
michael@0 | 612 | // Increment this token so that pending UpdateCurrentDictionary calls and |
michael@0 | 613 | // scheduled spell checks are discarded when they finish. |
michael@0 | 614 | mDisabledAsyncToken++; |
michael@0 | 615 | |
michael@0 | 616 | if (mNumPendingUpdateCurrentDictionary > 0) { |
michael@0 | 617 | // Account for pending UpdateCurrentDictionary calls. |
michael@0 | 618 | ChangeNumPendingSpellChecks(-mNumPendingUpdateCurrentDictionary, editor); |
michael@0 | 619 | mNumPendingUpdateCurrentDictionary = 0; |
michael@0 | 620 | } |
michael@0 | 621 | if (mNumPendingSpellChecks > 0) { |
michael@0 | 622 | // If mNumPendingSpellChecks is still > 0 at this point, the remainder is |
michael@0 | 623 | // pending scheduled spell checks. |
michael@0 | 624 | ChangeNumPendingSpellChecks(-mNumPendingSpellChecks, editor); |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | mEditor = nullptr; |
michael@0 | 628 | mFullSpellCheckScheduled = false; |
michael@0 | 629 | |
michael@0 | 630 | return rv; |
michael@0 | 631 | } |
michael@0 | 632 | |
michael@0 | 633 | // mozInlineSpellChecker::CanEnableInlineSpellChecking |
michael@0 | 634 | // |
michael@0 | 635 | // This function can be called to see if it seems likely that we can enable |
michael@0 | 636 | // spellchecking before actually creating the InlineSpellChecking objects. |
michael@0 | 637 | // |
michael@0 | 638 | // The problem is that we can't get the dictionary list without actually |
michael@0 | 639 | // creating a whole bunch of spellchecking objects. This function tries to |
michael@0 | 640 | // do that and caches the result so we don't have to keep allocating those |
michael@0 | 641 | // objects if there are no dictionaries or spellchecking. |
michael@0 | 642 | // |
michael@0 | 643 | // Whenever dictionaries are added or removed at runtime, this value must be |
michael@0 | 644 | // updated before an observer notification is sent out about the change, to |
michael@0 | 645 | // avoid editors getting a wrong cached result. |
michael@0 | 646 | |
michael@0 | 647 | bool // static |
michael@0 | 648 | mozInlineSpellChecker::CanEnableInlineSpellChecking() |
michael@0 | 649 | { |
michael@0 | 650 | nsresult rv; |
michael@0 | 651 | if (gCanEnableSpellChecking == SpellCheck_Uninitialized) { |
michael@0 | 652 | gCanEnableSpellChecking = SpellCheck_NotAvailable; |
michael@0 | 653 | |
michael@0 | 654 | nsCOMPtr<nsIEditorSpellCheck> spellchecker = |
michael@0 | 655 | do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv); |
michael@0 | 656 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 657 | |
michael@0 | 658 | bool canSpellCheck = false; |
michael@0 | 659 | rv = spellchecker->CanSpellCheck(&canSpellCheck); |
michael@0 | 660 | NS_ENSURE_SUCCESS(rv, false); |
michael@0 | 661 | |
michael@0 | 662 | if (canSpellCheck) |
michael@0 | 663 | gCanEnableSpellChecking = SpellCheck_Available; |
michael@0 | 664 | } |
michael@0 | 665 | return (gCanEnableSpellChecking == SpellCheck_Available); |
michael@0 | 666 | } |
michael@0 | 667 | |
michael@0 | 668 | void // static |
michael@0 | 669 | mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking() |
michael@0 | 670 | { |
michael@0 | 671 | gCanEnableSpellChecking = SpellCheck_Uninitialized; |
michael@0 | 672 | } |
michael@0 | 673 | |
michael@0 | 674 | // mozInlineSpellChecker::RegisterEventListeners |
michael@0 | 675 | // |
michael@0 | 676 | // The inline spell checker listens to mouse events and keyboard navigation+ // events. |
michael@0 | 677 | |
michael@0 | 678 | nsresult |
michael@0 | 679 | mozInlineSpellChecker::RegisterEventListeners() |
michael@0 | 680 | { |
michael@0 | 681 | nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); |
michael@0 | 682 | NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); |
michael@0 | 683 | |
michael@0 | 684 | editor->AddEditActionListener(this); |
michael@0 | 685 | |
michael@0 | 686 | nsCOMPtr<nsIDOMDocument> doc; |
michael@0 | 687 | nsresult rv = editor->GetDocument(getter_AddRefs(doc)); |
michael@0 | 688 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 689 | |
michael@0 | 690 | nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc, &rv); |
michael@0 | 691 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 692 | |
michael@0 | 693 | piTarget->AddEventListener(NS_LITERAL_STRING("blur"), this, |
michael@0 | 694 | true, false); |
michael@0 | 695 | piTarget->AddEventListener(NS_LITERAL_STRING("click"), this, |
michael@0 | 696 | false, false); |
michael@0 | 697 | piTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, |
michael@0 | 698 | false, false); |
michael@0 | 699 | return NS_OK; |
michael@0 | 700 | } |
michael@0 | 701 | |
michael@0 | 702 | // mozInlineSpellChecker::UnregisterEventListeners |
michael@0 | 703 | |
michael@0 | 704 | nsresult |
michael@0 | 705 | mozInlineSpellChecker::UnregisterEventListeners() |
michael@0 | 706 | { |
michael@0 | 707 | nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); |
michael@0 | 708 | NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); |
michael@0 | 709 | |
michael@0 | 710 | editor->RemoveEditActionListener(this); |
michael@0 | 711 | |
michael@0 | 712 | nsCOMPtr<nsIDOMDocument> doc; |
michael@0 | 713 | editor->GetDocument(getter_AddRefs(doc)); |
michael@0 | 714 | NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER); |
michael@0 | 715 | |
michael@0 | 716 | nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc); |
michael@0 | 717 | NS_ENSURE_TRUE(piTarget, NS_ERROR_NULL_POINTER); |
michael@0 | 718 | |
michael@0 | 719 | piTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true); |
michael@0 | 720 | piTarget->RemoveEventListener(NS_LITERAL_STRING("click"), this, false); |
michael@0 | 721 | piTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, false); |
michael@0 | 722 | return NS_OK; |
michael@0 | 723 | } |
michael@0 | 724 | |
michael@0 | 725 | // mozInlineSpellChecker::GetEnableRealTimeSpell |
michael@0 | 726 | |
michael@0 | 727 | NS_IMETHODIMP |
michael@0 | 728 | mozInlineSpellChecker::GetEnableRealTimeSpell(bool* aEnabled) |
michael@0 | 729 | { |
michael@0 | 730 | NS_ENSURE_ARG_POINTER(aEnabled); |
michael@0 | 731 | *aEnabled = mSpellCheck != nullptr || mPendingSpellCheck != nullptr; |
michael@0 | 732 | return NS_OK; |
michael@0 | 733 | } |
michael@0 | 734 | |
michael@0 | 735 | // mozInlineSpellChecker::SetEnableRealTimeSpell |
michael@0 | 736 | |
michael@0 | 737 | NS_IMETHODIMP |
michael@0 | 738 | mozInlineSpellChecker::SetEnableRealTimeSpell(bool aEnabled) |
michael@0 | 739 | { |
michael@0 | 740 | if (!aEnabled) { |
michael@0 | 741 | mSpellCheck = nullptr; |
michael@0 | 742 | return Cleanup(false); |
michael@0 | 743 | } |
michael@0 | 744 | |
michael@0 | 745 | if (mSpellCheck) { |
michael@0 | 746 | // spellcheck the current contents. SpellCheckRange doesn't supply a created |
michael@0 | 747 | // range to DoSpellCheck, which in our case is the entire range. But this |
michael@0 | 748 | // optimization doesn't matter because there is nothing in the spellcheck |
michael@0 | 749 | // selection when starting, which triggers a better optimization. |
michael@0 | 750 | return SpellCheckRange(nullptr); |
michael@0 | 751 | } |
michael@0 | 752 | |
michael@0 | 753 | if (mPendingSpellCheck) { |
michael@0 | 754 | // The editor spell checker is already being initialized. |
michael@0 | 755 | return NS_OK; |
michael@0 | 756 | } |
michael@0 | 757 | |
michael@0 | 758 | mPendingSpellCheck = |
michael@0 | 759 | do_CreateInstance("@mozilla.org/editor/editorspellchecker;1"); |
michael@0 | 760 | NS_ENSURE_STATE(mPendingSpellCheck); |
michael@0 | 761 | |
michael@0 | 762 | nsCOMPtr<nsITextServicesFilter> filter = |
michael@0 | 763 | do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1"); |
michael@0 | 764 | if (!filter) { |
michael@0 | 765 | mPendingSpellCheck = nullptr; |
michael@0 | 766 | NS_ENSURE_STATE(filter); |
michael@0 | 767 | } |
michael@0 | 768 | mPendingSpellCheck->SetFilter(filter); |
michael@0 | 769 | |
michael@0 | 770 | mPendingInitEditorSpellCheckCallback = new InitEditorSpellCheckCallback(this); |
michael@0 | 771 | if (!mPendingInitEditorSpellCheckCallback) { |
michael@0 | 772 | mPendingSpellCheck = nullptr; |
michael@0 | 773 | NS_ENSURE_STATE(mPendingInitEditorSpellCheckCallback); |
michael@0 | 774 | } |
michael@0 | 775 | |
michael@0 | 776 | nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor); |
michael@0 | 777 | nsresult rv = mPendingSpellCheck->InitSpellChecker( |
michael@0 | 778 | editor, false, mPendingInitEditorSpellCheckCallback); |
michael@0 | 779 | if (NS_FAILED(rv)) { |
michael@0 | 780 | mPendingSpellCheck = nullptr; |
michael@0 | 781 | mPendingInitEditorSpellCheckCallback = nullptr; |
michael@0 | 782 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 783 | } |
michael@0 | 784 | |
michael@0 | 785 | ChangeNumPendingSpellChecks(1); |
michael@0 | 786 | |
michael@0 | 787 | return NS_OK; |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | // Called when nsIEditorSpellCheck::InitSpellChecker completes. |
michael@0 | 791 | nsresult |
michael@0 | 792 | mozInlineSpellChecker::EditorSpellCheckInited() |
michael@0 | 793 | { |
michael@0 | 794 | NS_ASSERTION(mPendingSpellCheck, "Spell check should be pending!"); |
michael@0 | 795 | |
michael@0 | 796 | // spell checking is enabled, register our event listeners to track navigation |
michael@0 | 797 | RegisterEventListeners(); |
michael@0 | 798 | |
michael@0 | 799 | mSpellCheck = mPendingSpellCheck; |
michael@0 | 800 | mPendingSpellCheck = nullptr; |
michael@0 | 801 | mPendingInitEditorSpellCheckCallback = nullptr; |
michael@0 | 802 | ChangeNumPendingSpellChecks(-1); |
michael@0 | 803 | |
michael@0 | 804 | // spellcheck the current contents. SpellCheckRange doesn't supply a created |
michael@0 | 805 | // range to DoSpellCheck, which in our case is the entire range. But this |
michael@0 | 806 | // optimization doesn't matter because there is nothing in the spellcheck |
michael@0 | 807 | // selection when starting, which triggers a better optimization. |
michael@0 | 808 | return SpellCheckRange(nullptr); |
michael@0 | 809 | } |
michael@0 | 810 | |
michael@0 | 811 | // Changes the number of pending spell checks by the given delta. If the number |
michael@0 | 812 | // becomes zero or nonzero, observers are notified. See NotifyObservers for |
michael@0 | 813 | // info on the aEditor parameter. |
michael@0 | 814 | void |
michael@0 | 815 | mozInlineSpellChecker::ChangeNumPendingSpellChecks(int32_t aDelta, |
michael@0 | 816 | nsIEditor* aEditor) |
michael@0 | 817 | { |
michael@0 | 818 | int8_t oldNumPending = mNumPendingSpellChecks; |
michael@0 | 819 | mNumPendingSpellChecks += aDelta; |
michael@0 | 820 | NS_ASSERTION(mNumPendingSpellChecks >= 0, |
michael@0 | 821 | "Unbalanced ChangeNumPendingSpellChecks calls!"); |
michael@0 | 822 | if (oldNumPending == 0 && mNumPendingSpellChecks > 0) { |
michael@0 | 823 | NotifyObservers(INLINESPELL_STARTED_TOPIC, aEditor); |
michael@0 | 824 | } else if (oldNumPending > 0 && mNumPendingSpellChecks == 0) { |
michael@0 | 825 | NotifyObservers(INLINESPELL_ENDED_TOPIC, aEditor); |
michael@0 | 826 | } |
michael@0 | 827 | } |
michael@0 | 828 | |
michael@0 | 829 | // Broadcasts the given topic to observers. aEditor is passed to observers if |
michael@0 | 830 | // nonnull; otherwise mEditor is passed. |
michael@0 | 831 | void |
michael@0 | 832 | mozInlineSpellChecker::NotifyObservers(const char* aTopic, nsIEditor* aEditor) |
michael@0 | 833 | { |
michael@0 | 834 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
michael@0 | 835 | if (!os) |
michael@0 | 836 | return; |
michael@0 | 837 | nsCOMPtr<nsIEditor> editor = aEditor; |
michael@0 | 838 | if (!editor) { |
michael@0 | 839 | editor = do_QueryReferent(mEditor); |
michael@0 | 840 | } |
michael@0 | 841 | os->NotifyObservers(editor, aTopic, nullptr); |
michael@0 | 842 | } |
michael@0 | 843 | |
michael@0 | 844 | // mozInlineSpellChecker::SpellCheckAfterEditorChange |
michael@0 | 845 | // |
michael@0 | 846 | // Called by the editor when nearly anything happens to change the content. |
michael@0 | 847 | // |
michael@0 | 848 | // The start and end positions specify a range for the thing that happened, |
michael@0 | 849 | // but these are usually nullptr, even when you'd think they would be useful |
michael@0 | 850 | // because you want the range (for example, pasting). We ignore them in |
michael@0 | 851 | // this case. |
michael@0 | 852 | |
michael@0 | 853 | NS_IMETHODIMP |
michael@0 | 854 | mozInlineSpellChecker::SpellCheckAfterEditorChange( |
michael@0 | 855 | int32_t aAction, nsISelection *aSelection, |
michael@0 | 856 | nsIDOMNode *aPreviousSelectedNode, int32_t aPreviousSelectedOffset, |
michael@0 | 857 | nsIDOMNode *aStartNode, int32_t aStartOffset, |
michael@0 | 858 | nsIDOMNode *aEndNode, int32_t aEndOffset) |
michael@0 | 859 | { |
michael@0 | 860 | nsresult rv; |
michael@0 | 861 | NS_ENSURE_ARG_POINTER(aSelection); |
michael@0 | 862 | if (!mSpellCheck) |
michael@0 | 863 | return NS_OK; // disabling spell checking is not an error |
michael@0 | 864 | |
michael@0 | 865 | // this means something has changed, and we never check the current word, |
michael@0 | 866 | // therefore, we should spellcheck for subsequent caret navigations |
michael@0 | 867 | mNeedsCheckAfterNavigation = true; |
michael@0 | 868 | |
michael@0 | 869 | // the anchor node is the position of the caret |
michael@0 | 870 | nsCOMPtr<nsIDOMNode> anchorNode; |
michael@0 | 871 | rv = aSelection->GetAnchorNode(getter_AddRefs(anchorNode)); |
michael@0 | 872 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 873 | int32_t anchorOffset; |
michael@0 | 874 | rv = aSelection->GetAnchorOffset(&anchorOffset); |
michael@0 | 875 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 876 | |
michael@0 | 877 | mozInlineSpellStatus status(this); |
michael@0 | 878 | rv = status.InitForEditorChange((EditAction)aAction, |
michael@0 | 879 | anchorNode, anchorOffset, |
michael@0 | 880 | aPreviousSelectedNode, aPreviousSelectedOffset, |
michael@0 | 881 | aStartNode, aStartOffset, |
michael@0 | 882 | aEndNode, aEndOffset); |
michael@0 | 883 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 884 | rv = ScheduleSpellCheck(status); |
michael@0 | 885 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 886 | |
michael@0 | 887 | // remember the current caret position after every change |
michael@0 | 888 | SaveCurrentSelectionPosition(); |
michael@0 | 889 | return NS_OK; |
michael@0 | 890 | } |
michael@0 | 891 | |
michael@0 | 892 | // mozInlineSpellChecker::SpellCheckRange |
michael@0 | 893 | // |
michael@0 | 894 | // Spellchecks all the words in the given range. |
michael@0 | 895 | // Supply a nullptr range and this will check the entire editor. |
michael@0 | 896 | |
michael@0 | 897 | nsresult |
michael@0 | 898 | mozInlineSpellChecker::SpellCheckRange(nsIDOMRange* aRange) |
michael@0 | 899 | { |
michael@0 | 900 | NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 901 | |
michael@0 | 902 | mozInlineSpellStatus status(this); |
michael@0 | 903 | nsRange* range = static_cast<nsRange*>(aRange); |
michael@0 | 904 | nsresult rv = status.InitForRange(range); |
michael@0 | 905 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 906 | return ScheduleSpellCheck(status); |
michael@0 | 907 | } |
michael@0 | 908 | |
michael@0 | 909 | // mozInlineSpellChecker::GetMisspelledWord |
michael@0 | 910 | |
michael@0 | 911 | NS_IMETHODIMP |
michael@0 | 912 | mozInlineSpellChecker::GetMisspelledWord(nsIDOMNode *aNode, int32_t aOffset, |
michael@0 | 913 | nsIDOMRange **newword) |
michael@0 | 914 | { |
michael@0 | 915 | NS_ENSURE_ARG_POINTER(aNode); |
michael@0 | 916 | nsCOMPtr<nsISelection> spellCheckSelection; |
michael@0 | 917 | nsresult res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); |
michael@0 | 918 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 919 | |
michael@0 | 920 | return IsPointInSelection(spellCheckSelection, aNode, aOffset, newword); |
michael@0 | 921 | } |
michael@0 | 922 | |
michael@0 | 923 | // mozInlineSpellChecker::ReplaceWord |
michael@0 | 924 | |
michael@0 | 925 | NS_IMETHODIMP |
michael@0 | 926 | mozInlineSpellChecker::ReplaceWord(nsIDOMNode *aNode, int32_t aOffset, |
michael@0 | 927 | const nsAString &newword) |
michael@0 | 928 | { |
michael@0 | 929 | nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); |
michael@0 | 930 | NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); |
michael@0 | 931 | NS_ENSURE_TRUE(newword.Length() != 0, NS_ERROR_FAILURE); |
michael@0 | 932 | |
michael@0 | 933 | nsCOMPtr<nsIDOMRange> range; |
michael@0 | 934 | nsresult res = GetMisspelledWord(aNode, aOffset, getter_AddRefs(range)); |
michael@0 | 935 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 936 | |
michael@0 | 937 | if (range) |
michael@0 | 938 | { |
michael@0 | 939 | editor->BeginTransaction(); |
michael@0 | 940 | |
michael@0 | 941 | nsCOMPtr<nsISelection> selection; |
michael@0 | 942 | res = editor->GetSelection(getter_AddRefs(selection)); |
michael@0 | 943 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 944 | selection->RemoveAllRanges(); |
michael@0 | 945 | selection->AddRange(range); |
michael@0 | 946 | editor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); |
michael@0 | 947 | |
michael@0 | 948 | nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryReferent(mEditor)); |
michael@0 | 949 | if (textEditor) |
michael@0 | 950 | textEditor->InsertText(newword); |
michael@0 | 951 | |
michael@0 | 952 | editor->EndTransaction(); |
michael@0 | 953 | } |
michael@0 | 954 | |
michael@0 | 955 | return NS_OK; |
michael@0 | 956 | } |
michael@0 | 957 | |
michael@0 | 958 | // mozInlineSpellChecker::AddWordToDictionary |
michael@0 | 959 | |
michael@0 | 960 | NS_IMETHODIMP |
michael@0 | 961 | mozInlineSpellChecker::AddWordToDictionary(const nsAString &word) |
michael@0 | 962 | { |
michael@0 | 963 | NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 964 | |
michael@0 | 965 | nsAutoString wordstr(word); |
michael@0 | 966 | nsresult rv = mSpellCheck->AddWordToDictionary(wordstr.get()); |
michael@0 | 967 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 968 | |
michael@0 | 969 | mozInlineSpellStatus status(this); |
michael@0 | 970 | rv = status.InitForSelection(); |
michael@0 | 971 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 972 | return ScheduleSpellCheck(status); |
michael@0 | 973 | } |
michael@0 | 974 | |
michael@0 | 975 | // mozInlineSpellChecker::RemoveWordFromDictionary |
michael@0 | 976 | |
michael@0 | 977 | NS_IMETHODIMP |
michael@0 | 978 | mozInlineSpellChecker::RemoveWordFromDictionary(const nsAString &word) |
michael@0 | 979 | { |
michael@0 | 980 | NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 981 | |
michael@0 | 982 | nsAutoString wordstr(word); |
michael@0 | 983 | nsresult rv = mSpellCheck->RemoveWordFromDictionary(wordstr.get()); |
michael@0 | 984 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 985 | |
michael@0 | 986 | mozInlineSpellStatus status(this); |
michael@0 | 987 | rv = status.InitForRange(nullptr); |
michael@0 | 988 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 989 | return ScheduleSpellCheck(status); |
michael@0 | 990 | } |
michael@0 | 991 | |
michael@0 | 992 | // mozInlineSpellChecker::IgnoreWord |
michael@0 | 993 | |
michael@0 | 994 | NS_IMETHODIMP |
michael@0 | 995 | mozInlineSpellChecker::IgnoreWord(const nsAString &word) |
michael@0 | 996 | { |
michael@0 | 997 | NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 998 | |
michael@0 | 999 | nsAutoString wordstr(word); |
michael@0 | 1000 | nsresult rv = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get()); |
michael@0 | 1001 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1002 | |
michael@0 | 1003 | mozInlineSpellStatus status(this); |
michael@0 | 1004 | rv = status.InitForSelection(); |
michael@0 | 1005 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1006 | return ScheduleSpellCheck(status); |
michael@0 | 1007 | } |
michael@0 | 1008 | |
michael@0 | 1009 | // mozInlineSpellChecker::IgnoreWords |
michael@0 | 1010 | |
michael@0 | 1011 | NS_IMETHODIMP |
michael@0 | 1012 | mozInlineSpellChecker::IgnoreWords(const char16_t **aWordsToIgnore, |
michael@0 | 1013 | uint32_t aCount) |
michael@0 | 1014 | { |
michael@0 | 1015 | NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1016 | |
michael@0 | 1017 | // add each word to the ignore list and then recheck the document |
michael@0 | 1018 | for (uint32_t index = 0; index < aCount; index++) |
michael@0 | 1019 | mSpellCheck->IgnoreWordAllOccurrences(aWordsToIgnore[index]); |
michael@0 | 1020 | |
michael@0 | 1021 | mozInlineSpellStatus status(this); |
michael@0 | 1022 | nsresult rv = status.InitForSelection(); |
michael@0 | 1023 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1024 | return ScheduleSpellCheck(status); |
michael@0 | 1025 | } |
michael@0 | 1026 | |
michael@0 | 1027 | NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, int32_t aPosition) |
michael@0 | 1028 | { |
michael@0 | 1029 | return NS_OK; |
michael@0 | 1030 | } |
michael@0 | 1031 | |
michael@0 | 1032 | NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, |
michael@0 | 1033 | int32_t aPosition, nsresult aResult) |
michael@0 | 1034 | { |
michael@0 | 1035 | return NS_OK; |
michael@0 | 1036 | } |
michael@0 | 1037 | |
michael@0 | 1038 | NS_IMETHODIMP mozInlineSpellChecker::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, |
michael@0 | 1039 | int32_t aPosition) |
michael@0 | 1040 | { |
michael@0 | 1041 | return NS_OK; |
michael@0 | 1042 | } |
michael@0 | 1043 | |
michael@0 | 1044 | NS_IMETHODIMP mozInlineSpellChecker::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, |
michael@0 | 1045 | int32_t aPosition, nsresult aResult) |
michael@0 | 1046 | { |
michael@0 | 1047 | |
michael@0 | 1048 | return NS_OK; |
michael@0 | 1049 | } |
michael@0 | 1050 | |
michael@0 | 1051 | NS_IMETHODIMP mozInlineSpellChecker::WillDeleteNode(nsIDOMNode *aChild) |
michael@0 | 1052 | { |
michael@0 | 1053 | return NS_OK; |
michael@0 | 1054 | } |
michael@0 | 1055 | |
michael@0 | 1056 | NS_IMETHODIMP mozInlineSpellChecker::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) |
michael@0 | 1057 | { |
michael@0 | 1058 | return NS_OK; |
michael@0 | 1059 | } |
michael@0 | 1060 | |
michael@0 | 1061 | NS_IMETHODIMP mozInlineSpellChecker::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset) |
michael@0 | 1062 | { |
michael@0 | 1063 | return NS_OK; |
michael@0 | 1064 | } |
michael@0 | 1065 | |
michael@0 | 1066 | NS_IMETHODIMP |
michael@0 | 1067 | mozInlineSpellChecker::DidSplitNode(nsIDOMNode *aExistingRightNode, |
michael@0 | 1068 | int32_t aOffset, |
michael@0 | 1069 | nsIDOMNode *aNewLeftNode, nsresult aResult) |
michael@0 | 1070 | { |
michael@0 | 1071 | return SpellCheckBetweenNodes(aNewLeftNode, 0, aNewLeftNode, 0); |
michael@0 | 1072 | } |
michael@0 | 1073 | |
michael@0 | 1074 | NS_IMETHODIMP mozInlineSpellChecker::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) |
michael@0 | 1075 | { |
michael@0 | 1076 | return NS_OK; |
michael@0 | 1077 | } |
michael@0 | 1078 | |
michael@0 | 1079 | NS_IMETHODIMP mozInlineSpellChecker::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, |
michael@0 | 1080 | nsIDOMNode *aParent, nsresult aResult) |
michael@0 | 1081 | { |
michael@0 | 1082 | return SpellCheckBetweenNodes(aRightNode, 0, aRightNode, 0); |
michael@0 | 1083 | } |
michael@0 | 1084 | |
michael@0 | 1085 | NS_IMETHODIMP mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString & aString) |
michael@0 | 1086 | { |
michael@0 | 1087 | return NS_OK; |
michael@0 | 1088 | } |
michael@0 | 1089 | |
michael@0 | 1090 | NS_IMETHODIMP mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, |
michael@0 | 1091 | const nsAString & aString, nsresult aResult) |
michael@0 | 1092 | { |
michael@0 | 1093 | return NS_OK; |
michael@0 | 1094 | } |
michael@0 | 1095 | |
michael@0 | 1096 | NS_IMETHODIMP mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) |
michael@0 | 1097 | { |
michael@0 | 1098 | return NS_OK; |
michael@0 | 1099 | } |
michael@0 | 1100 | |
michael@0 | 1101 | NS_IMETHODIMP mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult) |
michael@0 | 1102 | { |
michael@0 | 1103 | return NS_OK; |
michael@0 | 1104 | } |
michael@0 | 1105 | |
michael@0 | 1106 | NS_IMETHODIMP mozInlineSpellChecker::WillDeleteSelection(nsISelection *aSelection) |
michael@0 | 1107 | { |
michael@0 | 1108 | return NS_OK; |
michael@0 | 1109 | } |
michael@0 | 1110 | |
michael@0 | 1111 | NS_IMETHODIMP mozInlineSpellChecker::DidDeleteSelection(nsISelection *aSelection) |
michael@0 | 1112 | { |
michael@0 | 1113 | return NS_OK; |
michael@0 | 1114 | } |
michael@0 | 1115 | |
michael@0 | 1116 | // mozInlineSpellChecker::MakeSpellCheckRange |
michael@0 | 1117 | // |
michael@0 | 1118 | // Given begin and end positions, this function constructs a range as |
michael@0 | 1119 | // required for ScheduleSpellCheck. If the start and end nodes are nullptr, |
michael@0 | 1120 | // then the entire range will be selected, and you can supply -1 as the |
michael@0 | 1121 | // offset to the end range to select all of that node. |
michael@0 | 1122 | // |
michael@0 | 1123 | // If the resulting range would be empty, nullptr is put into *aRange and the |
michael@0 | 1124 | // function succeeds. |
michael@0 | 1125 | |
michael@0 | 1126 | nsresult |
michael@0 | 1127 | mozInlineSpellChecker::MakeSpellCheckRange( |
michael@0 | 1128 | nsIDOMNode* aStartNode, int32_t aStartOffset, |
michael@0 | 1129 | nsIDOMNode* aEndNode, int32_t aEndOffset, |
michael@0 | 1130 | nsRange** aRange) |
michael@0 | 1131 | { |
michael@0 | 1132 | nsresult rv; |
michael@0 | 1133 | *aRange = nullptr; |
michael@0 | 1134 | |
michael@0 | 1135 | nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); |
michael@0 | 1136 | NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); |
michael@0 | 1137 | |
michael@0 | 1138 | nsCOMPtr<nsIDOMDocument> doc; |
michael@0 | 1139 | rv = editor->GetDocument(getter_AddRefs(doc)); |
michael@0 | 1140 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1141 | NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
michael@0 | 1142 | |
michael@0 | 1143 | nsCOMPtr<nsIDOMRange> range; |
michael@0 | 1144 | rv = doc->CreateRange(getter_AddRefs(range)); |
michael@0 | 1145 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1146 | |
michael@0 | 1147 | // possibly use full range of the editor |
michael@0 | 1148 | nsCOMPtr<nsIDOMElement> rootElem; |
michael@0 | 1149 | if (! aStartNode || ! aEndNode) { |
michael@0 | 1150 | rv = editor->GetRootElement(getter_AddRefs(rootElem)); |
michael@0 | 1151 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1152 | |
michael@0 | 1153 | aStartNode = rootElem; |
michael@0 | 1154 | aStartOffset = 0; |
michael@0 | 1155 | |
michael@0 | 1156 | aEndNode = rootElem; |
michael@0 | 1157 | aEndOffset = -1; |
michael@0 | 1158 | } |
michael@0 | 1159 | |
michael@0 | 1160 | if (aEndOffset == -1) { |
michael@0 | 1161 | nsCOMPtr<nsIDOMNodeList> childNodes; |
michael@0 | 1162 | rv = aEndNode->GetChildNodes(getter_AddRefs(childNodes)); |
michael@0 | 1163 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1164 | |
michael@0 | 1165 | uint32_t childCount; |
michael@0 | 1166 | rv = childNodes->GetLength(&childCount); |
michael@0 | 1167 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1168 | |
michael@0 | 1169 | aEndOffset = childCount; |
michael@0 | 1170 | } |
michael@0 | 1171 | |
michael@0 | 1172 | // sometimes we are are requested to check an empty range (possibly an empty |
michael@0 | 1173 | // document). This will result in assertions later. |
michael@0 | 1174 | if (aStartNode == aEndNode && aStartOffset == aEndOffset) |
michael@0 | 1175 | return NS_OK; |
michael@0 | 1176 | |
michael@0 | 1177 | rv = range->SetStart(aStartNode, aStartOffset); |
michael@0 | 1178 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1179 | if (aEndOffset) |
michael@0 | 1180 | rv = range->SetEnd(aEndNode, aEndOffset); |
michael@0 | 1181 | else |
michael@0 | 1182 | rv = range->SetEndAfter(aEndNode); |
michael@0 | 1183 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1184 | |
michael@0 | 1185 | *aRange = static_cast<nsRange*>(range.forget().take()); |
michael@0 | 1186 | return NS_OK; |
michael@0 | 1187 | } |
michael@0 | 1188 | |
michael@0 | 1189 | nsresult |
michael@0 | 1190 | mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode, |
michael@0 | 1191 | int32_t aStartOffset, |
michael@0 | 1192 | nsIDOMNode *aEndNode, |
michael@0 | 1193 | int32_t aEndOffset) |
michael@0 | 1194 | { |
michael@0 | 1195 | nsRefPtr<nsRange> range; |
michael@0 | 1196 | nsresult rv = MakeSpellCheckRange(aStartNode, aStartOffset, |
michael@0 | 1197 | aEndNode, aEndOffset, |
michael@0 | 1198 | getter_AddRefs(range)); |
michael@0 | 1199 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1200 | |
michael@0 | 1201 | if (! range) |
michael@0 | 1202 | return NS_OK; // range is empty: nothing to do |
michael@0 | 1203 | |
michael@0 | 1204 | mozInlineSpellStatus status(this); |
michael@0 | 1205 | rv = status.InitForRange(range); |
michael@0 | 1206 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1207 | return ScheduleSpellCheck(status); |
michael@0 | 1208 | } |
michael@0 | 1209 | |
michael@0 | 1210 | // mozInlineSpellChecker::SkipSpellCheckForNode |
michael@0 | 1211 | // |
michael@0 | 1212 | // There are certain conditions when we don't want to spell check a node. In |
michael@0 | 1213 | // particular quotations, moz signatures, etc. This routine returns false |
michael@0 | 1214 | // for these cases. |
michael@0 | 1215 | |
michael@0 | 1216 | nsresult |
michael@0 | 1217 | mozInlineSpellChecker::SkipSpellCheckForNode(nsIEditor* aEditor, |
michael@0 | 1218 | nsIDOMNode *aNode, |
michael@0 | 1219 | bool *checkSpelling) |
michael@0 | 1220 | { |
michael@0 | 1221 | *checkSpelling = true; |
michael@0 | 1222 | NS_ENSURE_ARG_POINTER(aNode); |
michael@0 | 1223 | |
michael@0 | 1224 | uint32_t flags; |
michael@0 | 1225 | aEditor->GetFlags(&flags); |
michael@0 | 1226 | if (flags & nsIPlaintextEditor::eEditorMailMask) |
michael@0 | 1227 | { |
michael@0 | 1228 | nsCOMPtr<nsIDOMNode> parent; |
michael@0 | 1229 | aNode->GetParentNode(getter_AddRefs(parent)); |
michael@0 | 1230 | |
michael@0 | 1231 | while (parent) |
michael@0 | 1232 | { |
michael@0 | 1233 | nsCOMPtr<nsIDOMElement> parentElement = do_QueryInterface(parent); |
michael@0 | 1234 | if (!parentElement) |
michael@0 | 1235 | break; |
michael@0 | 1236 | |
michael@0 | 1237 | nsAutoString parentTagName; |
michael@0 | 1238 | parentElement->GetTagName(parentTagName); |
michael@0 | 1239 | |
michael@0 | 1240 | if (parentTagName.Equals(NS_LITERAL_STRING("blockquote"), nsCaseInsensitiveStringComparator())) |
michael@0 | 1241 | { |
michael@0 | 1242 | nsAutoString quotetype; |
michael@0 | 1243 | parentElement->GetAttribute(NS_LITERAL_STRING("type"), quotetype); |
michael@0 | 1244 | if (quotetype.Equals(NS_LITERAL_STRING("cite"), nsCaseInsensitiveStringComparator())) |
michael@0 | 1245 | { |
michael@0 | 1246 | *checkSpelling = false; |
michael@0 | 1247 | break; |
michael@0 | 1248 | } |
michael@0 | 1249 | } |
michael@0 | 1250 | else if (parentTagName.Equals(NS_LITERAL_STRING("pre"), nsCaseInsensitiveStringComparator())) |
michael@0 | 1251 | { |
michael@0 | 1252 | nsAutoString classname; |
michael@0 | 1253 | parentElement->GetAttribute(NS_LITERAL_STRING("class"),classname); |
michael@0 | 1254 | if (classname.Equals(NS_LITERAL_STRING("moz-signature"))) |
michael@0 | 1255 | *checkSpelling = false; |
michael@0 | 1256 | } |
michael@0 | 1257 | |
michael@0 | 1258 | nsCOMPtr<nsIDOMNode> nextParent; |
michael@0 | 1259 | parent->GetParentNode(getter_AddRefs(nextParent)); |
michael@0 | 1260 | parent = nextParent; |
michael@0 | 1261 | } |
michael@0 | 1262 | } |
michael@0 | 1263 | else { |
michael@0 | 1264 | // Check spelling only if the node is editable, and GetSpellcheck() is true |
michael@0 | 1265 | // on the nearest HTMLElement ancestor. |
michael@0 | 1266 | nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
michael@0 | 1267 | if (!content->IsEditable()) { |
michael@0 | 1268 | *checkSpelling = false; |
michael@0 | 1269 | return NS_OK; |
michael@0 | 1270 | } |
michael@0 | 1271 | |
michael@0 | 1272 | // Make sure that we can always turn on spell checking for inputs/textareas. |
michael@0 | 1273 | // Note that because of the previous check, at this point we know that the |
michael@0 | 1274 | // node is editable. |
michael@0 | 1275 | if (content->IsInAnonymousSubtree()) { |
michael@0 | 1276 | nsCOMPtr<nsIContent> node = content->GetParent(); |
michael@0 | 1277 | while (node && node->IsInNativeAnonymousSubtree()) { |
michael@0 | 1278 | node = node->GetParent(); |
michael@0 | 1279 | } |
michael@0 | 1280 | nsCOMPtr<nsITextControlElement> textControl = do_QueryInterface(node); |
michael@0 | 1281 | if (textControl) { |
michael@0 | 1282 | *checkSpelling = true; |
michael@0 | 1283 | return NS_OK; |
michael@0 | 1284 | } |
michael@0 | 1285 | } |
michael@0 | 1286 | |
michael@0 | 1287 | // Get HTML element ancestor (might be aNode itself, although probably that |
michael@0 | 1288 | // has to be a text node in real life here) |
michael@0 | 1289 | nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(content); |
michael@0 | 1290 | while (content && !htmlElement) { |
michael@0 | 1291 | content = content->GetParent(); |
michael@0 | 1292 | htmlElement = do_QueryInterface(content); |
michael@0 | 1293 | } |
michael@0 | 1294 | NS_ASSERTION(htmlElement, "Why do we have no htmlElement?"); |
michael@0 | 1295 | if (!htmlElement) { |
michael@0 | 1296 | return NS_OK; |
michael@0 | 1297 | } |
michael@0 | 1298 | |
michael@0 | 1299 | // See if it's spellcheckable |
michael@0 | 1300 | htmlElement->GetSpellcheck(checkSpelling); |
michael@0 | 1301 | return NS_OK; |
michael@0 | 1302 | } |
michael@0 | 1303 | |
michael@0 | 1304 | return NS_OK; |
michael@0 | 1305 | } |
michael@0 | 1306 | |
michael@0 | 1307 | // mozInlineSpellChecker::ScheduleSpellCheck |
michael@0 | 1308 | // |
michael@0 | 1309 | // This is called by code to do the actual spellchecking. We will set up |
michael@0 | 1310 | // the proper structures for calls to DoSpellCheck. |
michael@0 | 1311 | |
michael@0 | 1312 | nsresult |
michael@0 | 1313 | mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus& aStatus) |
michael@0 | 1314 | { |
michael@0 | 1315 | if (mFullSpellCheckScheduled) { |
michael@0 | 1316 | // Just ignore this; we're going to spell-check everything anyway |
michael@0 | 1317 | return NS_OK; |
michael@0 | 1318 | } |
michael@0 | 1319 | |
michael@0 | 1320 | mozInlineSpellResume* resume = |
michael@0 | 1321 | new mozInlineSpellResume(aStatus, mDisabledAsyncToken); |
michael@0 | 1322 | NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 1323 | |
michael@0 | 1324 | nsresult rv = resume->Post(); |
michael@0 | 1325 | if (NS_FAILED(rv)) { |
michael@0 | 1326 | delete resume; |
michael@0 | 1327 | } else { |
michael@0 | 1328 | if (aStatus.IsFullSpellCheck()) { |
michael@0 | 1329 | // We're going to check everything. Suppress further spell-check attempts |
michael@0 | 1330 | // until that happens. |
michael@0 | 1331 | mFullSpellCheckScheduled = true; |
michael@0 | 1332 | } |
michael@0 | 1333 | ChangeNumPendingSpellChecks(1); |
michael@0 | 1334 | } |
michael@0 | 1335 | return rv; |
michael@0 | 1336 | } |
michael@0 | 1337 | |
michael@0 | 1338 | // mozInlineSpellChecker::DoSpellCheckSelection |
michael@0 | 1339 | // |
michael@0 | 1340 | // Called to re-check all misspelled words. We iterate over all ranges in |
michael@0 | 1341 | // the selection and call DoSpellCheck on them. This is used when a word |
michael@0 | 1342 | // is ignored or added to the dictionary: all instances of that word should |
michael@0 | 1343 | // be removed from the selection. |
michael@0 | 1344 | // |
michael@0 | 1345 | // FIXME-PERFORMANCE: This takes as long as it takes and is not resumable. |
michael@0 | 1346 | // Typically, checking this small amount of text is relatively fast, but |
michael@0 | 1347 | // for large numbers of words, a lag may be noticeable. |
michael@0 | 1348 | |
michael@0 | 1349 | nsresult |
michael@0 | 1350 | mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil, |
michael@0 | 1351 | nsISelection* aSpellCheckSelection, |
michael@0 | 1352 | mozInlineSpellStatus* aStatus) |
michael@0 | 1353 | { |
michael@0 | 1354 | nsresult rv; |
michael@0 | 1355 | |
michael@0 | 1356 | // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges. |
michael@0 | 1357 | mNumWordsInSpellSelection = 0; |
michael@0 | 1358 | |
michael@0 | 1359 | // Since we could be modifying the ranges for the spellCheckSelection while |
michael@0 | 1360 | // looping on the spell check selection, keep a separate array of range |
michael@0 | 1361 | // elements inside the selection |
michael@0 | 1362 | nsCOMArray<nsIDOMRange> ranges; |
michael@0 | 1363 | |
michael@0 | 1364 | int32_t count; |
michael@0 | 1365 | aSpellCheckSelection->GetRangeCount(&count); |
michael@0 | 1366 | |
michael@0 | 1367 | int32_t idx; |
michael@0 | 1368 | nsCOMPtr<nsIDOMRange> checkRange; |
michael@0 | 1369 | for (idx = 0; idx < count; idx ++) { |
michael@0 | 1370 | aSpellCheckSelection->GetRangeAt(idx, getter_AddRefs(checkRange)); |
michael@0 | 1371 | if (checkRange) { |
michael@0 | 1372 | if (! ranges.AppendObject(checkRange)) |
michael@0 | 1373 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 1374 | } |
michael@0 | 1375 | } |
michael@0 | 1376 | |
michael@0 | 1377 | // We have saved the ranges above. Clearing the spellcheck selection here |
michael@0 | 1378 | // isn't necessary (rechecking each word will modify it as necessary) but |
michael@0 | 1379 | // provides better performance. By ensuring that no ranges need to be |
michael@0 | 1380 | // removed in DoSpellCheck, we can save checking range inclusion which is |
michael@0 | 1381 | // slow. |
michael@0 | 1382 | aSpellCheckSelection->RemoveAllRanges(); |
michael@0 | 1383 | |
michael@0 | 1384 | // We use this state object for all calls, and just update its range. Note |
michael@0 | 1385 | // that we don't need to call FinishInit since we will be filling in the |
michael@0 | 1386 | // necessary information. |
michael@0 | 1387 | mozInlineSpellStatus status(this); |
michael@0 | 1388 | rv = status.InitForRange(nullptr); |
michael@0 | 1389 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1390 | |
michael@0 | 1391 | bool doneChecking; |
michael@0 | 1392 | for (idx = 0; idx < count; idx ++) { |
michael@0 | 1393 | checkRange = ranges[idx]; |
michael@0 | 1394 | if (checkRange) { |
michael@0 | 1395 | // We can consider this word as "added" since we know it has no spell |
michael@0 | 1396 | // check range over it that needs to be deleted. All the old ranges |
michael@0 | 1397 | // were cleared above. We also need to clear the word count so that we |
michael@0 | 1398 | // check all words instead of stopping early. |
michael@0 | 1399 | status.mRange = static_cast<nsRange*>(checkRange.get()); |
michael@0 | 1400 | rv = DoSpellCheck(aWordUtil, aSpellCheckSelection, &status, |
michael@0 | 1401 | &doneChecking); |
michael@0 | 1402 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1403 | NS_ASSERTION(doneChecking, "We gave the spellchecker one word, but it didn't finish checking?!?!"); |
michael@0 | 1404 | |
michael@0 | 1405 | status.mWordCount = 0; |
michael@0 | 1406 | } |
michael@0 | 1407 | } |
michael@0 | 1408 | |
michael@0 | 1409 | return NS_OK; |
michael@0 | 1410 | } |
michael@0 | 1411 | |
michael@0 | 1412 | // mozInlineSpellChecker::DoSpellCheck |
michael@0 | 1413 | // |
michael@0 | 1414 | // This function checks words intersecting the given range, excluding those |
michael@0 | 1415 | // inside mStatus->mNoCheckRange (can be nullptr). Words inside aNoCheckRange |
michael@0 | 1416 | // will have any spell selection removed (this is used to hide the |
michael@0 | 1417 | // underlining for the word that the caret is in). aNoCheckRange should be |
michael@0 | 1418 | // on word boundaries. |
michael@0 | 1419 | // |
michael@0 | 1420 | // mResume->mCreatedRange is a possibly nullptr range of new text that was |
michael@0 | 1421 | // inserted. Inside this range, we don't bother to check whether things are |
michael@0 | 1422 | // inside the spellcheck selection, which speeds up large paste operations |
michael@0 | 1423 | // considerably. |
michael@0 | 1424 | // |
michael@0 | 1425 | // Normal case when editing text by typing |
michael@0 | 1426 | // h e l l o w o r k d h o w a r e y o u |
michael@0 | 1427 | // ^ caret |
michael@0 | 1428 | // [-------] mRange |
michael@0 | 1429 | // [-------] mNoCheckRange |
michael@0 | 1430 | // -> does nothing (range is the same as the no check range) |
michael@0 | 1431 | // |
michael@0 | 1432 | // Case when pasting: |
michael@0 | 1433 | // [---------- pasted text ----------] |
michael@0 | 1434 | // h e l l o w o r k d h o w a r e y o u |
michael@0 | 1435 | // ^ caret |
michael@0 | 1436 | // [---] aNoCheckRange |
michael@0 | 1437 | // -> recheck all words in range except those in aNoCheckRange |
michael@0 | 1438 | // |
michael@0 | 1439 | // If checking is complete, *aDoneChecking will be set. If there is more |
michael@0 | 1440 | // but we ran out of time, this will be false and the range will be |
michael@0 | 1441 | // updated with the stuff that still needs checking. |
michael@0 | 1442 | |
michael@0 | 1443 | nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil, |
michael@0 | 1444 | nsISelection *aSpellCheckSelection, |
michael@0 | 1445 | mozInlineSpellStatus* aStatus, |
michael@0 | 1446 | bool* aDoneChecking) |
michael@0 | 1447 | { |
michael@0 | 1448 | *aDoneChecking = true; |
michael@0 | 1449 | |
michael@0 | 1450 | NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 1451 | |
michael@0 | 1452 | // get the editor for SkipSpellCheckForNode, this may fail in reasonable |
michael@0 | 1453 | // circumstances since the editor could have gone away |
michael@0 | 1454 | nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); |
michael@0 | 1455 | if (! editor) |
michael@0 | 1456 | return NS_ERROR_FAILURE; |
michael@0 | 1457 | |
michael@0 | 1458 | bool iscollapsed; |
michael@0 | 1459 | nsresult rv = aStatus->mRange->GetCollapsed(&iscollapsed); |
michael@0 | 1460 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1461 | if (iscollapsed) |
michael@0 | 1462 | return NS_OK; |
michael@0 | 1463 | |
michael@0 | 1464 | nsCOMPtr<nsISelectionPrivate> privSel = do_QueryInterface(aSpellCheckSelection); |
michael@0 | 1465 | |
michael@0 | 1466 | // see if the selection has any ranges, if not, then we can optimize checking |
michael@0 | 1467 | // range inclusion later (we have no ranges when we are initially checking or |
michael@0 | 1468 | // when there are no misspelled words yet). |
michael@0 | 1469 | int32_t originalRangeCount; |
michael@0 | 1470 | rv = aSpellCheckSelection->GetRangeCount(&originalRangeCount); |
michael@0 | 1471 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1472 | |
michael@0 | 1473 | // set the starting DOM position to be the beginning of our range |
michael@0 | 1474 | { |
michael@0 | 1475 | // Scope for the node/offset pairs here so they don't get |
michael@0 | 1476 | // accidentally used later |
michael@0 | 1477 | nsINode* beginNode = aStatus->mRange->GetStartParent(); |
michael@0 | 1478 | int32_t beginOffset = aStatus->mRange->StartOffset(); |
michael@0 | 1479 | nsINode* endNode = aStatus->mRange->GetEndParent(); |
michael@0 | 1480 | int32_t endOffset = aStatus->mRange->EndOffset(); |
michael@0 | 1481 | |
michael@0 | 1482 | // Now check that we're still looking at a range that's under |
michael@0 | 1483 | // aWordUtil.GetRootNode() |
michael@0 | 1484 | nsINode* rootNode = aWordUtil.GetRootNode(); |
michael@0 | 1485 | if (!nsContentUtils::ContentIsDescendantOf(beginNode, rootNode) || |
michael@0 | 1486 | !nsContentUtils::ContentIsDescendantOf(endNode, rootNode)) { |
michael@0 | 1487 | // Just bail out and don't try to spell-check this |
michael@0 | 1488 | return NS_OK; |
michael@0 | 1489 | } |
michael@0 | 1490 | |
michael@0 | 1491 | aWordUtil.SetEnd(endNode, endOffset); |
michael@0 | 1492 | aWordUtil.SetPosition(beginNode, beginOffset); |
michael@0 | 1493 | } |
michael@0 | 1494 | |
michael@0 | 1495 | // aWordUtil.SetPosition flushes pending notifications, check editor again. |
michael@0 | 1496 | editor = do_QueryReferent(mEditor); |
michael@0 | 1497 | if (! editor) |
michael@0 | 1498 | return NS_ERROR_FAILURE; |
michael@0 | 1499 | |
michael@0 | 1500 | int32_t wordsSinceTimeCheck = 0; |
michael@0 | 1501 | PRTime beginTime = PR_Now(); |
michael@0 | 1502 | |
michael@0 | 1503 | nsAutoString wordText; |
michael@0 | 1504 | nsRefPtr<nsRange> wordRange; |
michael@0 | 1505 | bool dontCheckWord; |
michael@0 | 1506 | while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText, |
michael@0 | 1507 | getter_AddRefs(wordRange), |
michael@0 | 1508 | &dontCheckWord)) && |
michael@0 | 1509 | wordRange) { |
michael@0 | 1510 | wordsSinceTimeCheck ++; |
michael@0 | 1511 | |
michael@0 | 1512 | // get the range for the current word. |
michael@0 | 1513 | // Not using nsINode here for now because we have to call into |
michael@0 | 1514 | // selection APIs that use nsIDOMNode. :( |
michael@0 | 1515 | nsCOMPtr<nsIDOMNode> beginNode, endNode; |
michael@0 | 1516 | int32_t beginOffset, endOffset; |
michael@0 | 1517 | wordRange->GetStartContainer(getter_AddRefs(beginNode)); |
michael@0 | 1518 | wordRange->GetEndContainer(getter_AddRefs(endNode)); |
michael@0 | 1519 | wordRange->GetStartOffset(&beginOffset); |
michael@0 | 1520 | wordRange->GetEndOffset(&endOffset); |
michael@0 | 1521 | |
michael@0 | 1522 | #ifdef DEBUG_INLINESPELL |
michael@0 | 1523 | printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText).get()); |
michael@0 | 1524 | if (dontCheckWord) |
michael@0 | 1525 | printf(" (not checking)"); |
michael@0 | 1526 | printf("\n"); |
michael@0 | 1527 | #endif |
michael@0 | 1528 | |
michael@0 | 1529 | // see if there is a spellcheck range that already intersects the word |
michael@0 | 1530 | // and remove it. We only need to remove old ranges, so don't bother if |
michael@0 | 1531 | // there were no ranges when we started out. |
michael@0 | 1532 | if (originalRangeCount > 0) { |
michael@0 | 1533 | // likewise, if this word is inside new text, we won't bother testing |
michael@0 | 1534 | bool inCreatedRange = false; |
michael@0 | 1535 | if (aStatus->mCreatedRange) |
michael@0 | 1536 | aStatus->mCreatedRange->IsPointInRange(beginNode, beginOffset, &inCreatedRange); |
michael@0 | 1537 | if (! inCreatedRange) { |
michael@0 | 1538 | nsTArray<nsRange*> ranges; |
michael@0 | 1539 | nsCOMPtr<nsINode> firstNode = do_QueryInterface(beginNode); |
michael@0 | 1540 | nsCOMPtr<nsINode> lastNode = do_QueryInterface(endNode); |
michael@0 | 1541 | rv = privSel->GetRangesForIntervalArray(firstNode, beginOffset, |
michael@0 | 1542 | lastNode, endOffset, |
michael@0 | 1543 | true, &ranges); |
michael@0 | 1544 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1545 | for (uint32_t i = 0; i < ranges.Length(); i++) |
michael@0 | 1546 | RemoveRange(aSpellCheckSelection, ranges[i]); |
michael@0 | 1547 | } |
michael@0 | 1548 | } |
michael@0 | 1549 | |
michael@0 | 1550 | // some words are special and don't need checking |
michael@0 | 1551 | if (dontCheckWord) |
michael@0 | 1552 | continue; |
michael@0 | 1553 | |
michael@0 | 1554 | // some nodes we don't spellcheck |
michael@0 | 1555 | bool checkSpelling; |
michael@0 | 1556 | rv = SkipSpellCheckForNode(editor, beginNode, &checkSpelling); |
michael@0 | 1557 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1558 | if (!checkSpelling) |
michael@0 | 1559 | continue; |
michael@0 | 1560 | |
michael@0 | 1561 | // Don't check spelling if we're inside the noCheckRange. This needs to |
michael@0 | 1562 | // be done after we clear any old selection because the excluded word |
michael@0 | 1563 | // might have been previously marked. |
michael@0 | 1564 | // |
michael@0 | 1565 | // We do a simple check to see if the beginning of our word is in the |
michael@0 | 1566 | // exclusion range. Because the exclusion range is a multiple of a word, |
michael@0 | 1567 | // this is sufficient. |
michael@0 | 1568 | if (aStatus->mNoCheckRange) { |
michael@0 | 1569 | bool inExclusion = false; |
michael@0 | 1570 | aStatus->mNoCheckRange->IsPointInRange(beginNode, beginOffset, |
michael@0 | 1571 | &inExclusion); |
michael@0 | 1572 | if (inExclusion) |
michael@0 | 1573 | continue; |
michael@0 | 1574 | } |
michael@0 | 1575 | |
michael@0 | 1576 | // check spelling and add to selection if misspelled |
michael@0 | 1577 | bool isMisspelled; |
michael@0 | 1578 | aWordUtil.NormalizeWord(wordText); |
michael@0 | 1579 | rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText.get(), &isMisspelled); |
michael@0 | 1580 | if (NS_FAILED(rv)) |
michael@0 | 1581 | continue; |
michael@0 | 1582 | |
michael@0 | 1583 | if (isMisspelled) { |
michael@0 | 1584 | // misspelled words count extra toward the max |
michael@0 | 1585 | wordsSinceTimeCheck += MISSPELLED_WORD_COUNT_PENALTY; |
michael@0 | 1586 | AddRange(aSpellCheckSelection, wordRange); |
michael@0 | 1587 | |
michael@0 | 1588 | aStatus->mWordCount ++; |
michael@0 | 1589 | if (aStatus->mWordCount >= mMaxMisspellingsPerCheck || |
michael@0 | 1590 | SpellCheckSelectionIsFull()) |
michael@0 | 1591 | break; |
michael@0 | 1592 | } |
michael@0 | 1593 | |
michael@0 | 1594 | // see if we've run out of time, only check every N words for perf |
michael@0 | 1595 | if (wordsSinceTimeCheck >= INLINESPELL_TIMEOUT_CHECK_FREQUENCY) { |
michael@0 | 1596 | wordsSinceTimeCheck = 0; |
michael@0 | 1597 | if (PR_Now() > PRTime(beginTime + INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC)) { |
michael@0 | 1598 | // stop checking, our time limit has been exceeded |
michael@0 | 1599 | |
michael@0 | 1600 | // move the range to encompass the stuff that needs checking |
michael@0 | 1601 | rv = aStatus->mRange->SetStart(endNode, endOffset); |
michael@0 | 1602 | if (NS_FAILED(rv)) { |
michael@0 | 1603 | // The range might be unhappy because the beginning is after the |
michael@0 | 1604 | // end. This is possible when the requested end was in the middle |
michael@0 | 1605 | // of a word, just ignore this situation and assume we're done. |
michael@0 | 1606 | return NS_OK; |
michael@0 | 1607 | } |
michael@0 | 1608 | *aDoneChecking = false; |
michael@0 | 1609 | return NS_OK; |
michael@0 | 1610 | } |
michael@0 | 1611 | } |
michael@0 | 1612 | } |
michael@0 | 1613 | |
michael@0 | 1614 | return NS_OK; |
michael@0 | 1615 | } |
michael@0 | 1616 | |
michael@0 | 1617 | // An RAII helper that calls ChangeNumPendingSpellChecks on destruction. |
michael@0 | 1618 | class AutoChangeNumPendingSpellChecks |
michael@0 | 1619 | { |
michael@0 | 1620 | public: |
michael@0 | 1621 | AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker, |
michael@0 | 1622 | int32_t aDelta) |
michael@0 | 1623 | : mSpellChecker(aSpellChecker), mDelta(aDelta) {} |
michael@0 | 1624 | |
michael@0 | 1625 | ~AutoChangeNumPendingSpellChecks() |
michael@0 | 1626 | { |
michael@0 | 1627 | mSpellChecker->ChangeNumPendingSpellChecks(mDelta); |
michael@0 | 1628 | } |
michael@0 | 1629 | |
michael@0 | 1630 | private: |
michael@0 | 1631 | nsRefPtr<mozInlineSpellChecker> mSpellChecker; |
michael@0 | 1632 | int32_t mDelta; |
michael@0 | 1633 | }; |
michael@0 | 1634 | |
michael@0 | 1635 | // mozInlineSpellChecker::ResumeCheck |
michael@0 | 1636 | // |
michael@0 | 1637 | // Called by the resume event when it fires. We will try to pick up where |
michael@0 | 1638 | // the last resume left off. |
michael@0 | 1639 | |
michael@0 | 1640 | nsresult |
michael@0 | 1641 | mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus) |
michael@0 | 1642 | { |
michael@0 | 1643 | // Observers should be notified that spell check has ended only after spell |
michael@0 | 1644 | // check is done below, but since there are many early returns in this method |
michael@0 | 1645 | // and the number of pending spell checks must be decremented regardless of |
michael@0 | 1646 | // whether the spell check actually happens, use this RAII object. |
michael@0 | 1647 | AutoChangeNumPendingSpellChecks autoChangeNumPending(this, -1); |
michael@0 | 1648 | |
michael@0 | 1649 | if (aStatus->IsFullSpellCheck()) { |
michael@0 | 1650 | // Allow posting new spellcheck resume events from inside |
michael@0 | 1651 | // ResumeCheck, now that we're actually firing. |
michael@0 | 1652 | NS_ASSERTION(mFullSpellCheckScheduled, |
michael@0 | 1653 | "How could this be false? The full spell check is " |
michael@0 | 1654 | "calling us!!"); |
michael@0 | 1655 | mFullSpellCheckScheduled = false; |
michael@0 | 1656 | } |
michael@0 | 1657 | |
michael@0 | 1658 | if (! mSpellCheck) |
michael@0 | 1659 | return NS_OK; // spell checking has been turned off |
michael@0 | 1660 | |
michael@0 | 1661 | nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor); |
michael@0 | 1662 | if (! editor) |
michael@0 | 1663 | return NS_OK; // editor is gone |
michael@0 | 1664 | |
michael@0 | 1665 | mozInlineSpellWordUtil wordUtil; |
michael@0 | 1666 | nsresult rv = wordUtil.Init(mEditor); |
michael@0 | 1667 | if (NS_FAILED(rv)) |
michael@0 | 1668 | return NS_OK; // editor doesn't like us, don't assert |
michael@0 | 1669 | |
michael@0 | 1670 | nsCOMPtr<nsISelection> spellCheckSelection; |
michael@0 | 1671 | rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); |
michael@0 | 1672 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1673 | |
michael@0 | 1674 | nsAutoString currentDictionary; |
michael@0 | 1675 | rv = mSpellCheck->GetCurrentDictionary(currentDictionary); |
michael@0 | 1676 | if (NS_FAILED(rv)) { |
michael@0 | 1677 | // no active dictionary |
michael@0 | 1678 | int32_t count; |
michael@0 | 1679 | spellCheckSelection->GetRangeCount(&count); |
michael@0 | 1680 | for (int32_t index = count - 1; index >= 0; index--) { |
michael@0 | 1681 | nsCOMPtr<nsIDOMRange> checkRange; |
michael@0 | 1682 | spellCheckSelection->GetRangeAt(index, getter_AddRefs(checkRange)); |
michael@0 | 1683 | if (checkRange) { |
michael@0 | 1684 | RemoveRange(spellCheckSelection, checkRange); |
michael@0 | 1685 | } |
michael@0 | 1686 | } |
michael@0 | 1687 | return NS_OK; |
michael@0 | 1688 | } |
michael@0 | 1689 | |
michael@0 | 1690 | CleanupRangesInSelection(spellCheckSelection); |
michael@0 | 1691 | |
michael@0 | 1692 | rv = aStatus->FinishInitOnEvent(wordUtil); |
michael@0 | 1693 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1694 | if (! aStatus->mRange) |
michael@0 | 1695 | return NS_OK; // empty range, nothing to do |
michael@0 | 1696 | |
michael@0 | 1697 | bool doneChecking = true; |
michael@0 | 1698 | if (aStatus->mOp == mozInlineSpellStatus::eOpSelection) |
michael@0 | 1699 | rv = DoSpellCheckSelection(wordUtil, spellCheckSelection, aStatus); |
michael@0 | 1700 | else |
michael@0 | 1701 | rv = DoSpellCheck(wordUtil, spellCheckSelection, aStatus, &doneChecking); |
michael@0 | 1702 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1703 | |
michael@0 | 1704 | if (! doneChecking) |
michael@0 | 1705 | rv = ScheduleSpellCheck(*aStatus); |
michael@0 | 1706 | return rv; |
michael@0 | 1707 | } |
michael@0 | 1708 | |
michael@0 | 1709 | // mozInlineSpellChecker::IsPointInSelection |
michael@0 | 1710 | // |
michael@0 | 1711 | // Determines if a given (node,offset) point is inside the given |
michael@0 | 1712 | // selection. If so, the specific range of the selection that |
michael@0 | 1713 | // intersects is places in *aRange. (There may be multiple disjoint |
michael@0 | 1714 | // ranges in a selection.) |
michael@0 | 1715 | // |
michael@0 | 1716 | // If there is no intersection, *aRange will be nullptr. |
michael@0 | 1717 | |
michael@0 | 1718 | nsresult |
michael@0 | 1719 | mozInlineSpellChecker::IsPointInSelection(nsISelection *aSelection, |
michael@0 | 1720 | nsIDOMNode *aNode, |
michael@0 | 1721 | int32_t aOffset, |
michael@0 | 1722 | nsIDOMRange **aRange) |
michael@0 | 1723 | { |
michael@0 | 1724 | *aRange = nullptr; |
michael@0 | 1725 | |
michael@0 | 1726 | nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection)); |
michael@0 | 1727 | |
michael@0 | 1728 | nsTArray<nsRange*> ranges; |
michael@0 | 1729 | nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
michael@0 | 1730 | nsresult rv = privSel->GetRangesForIntervalArray(node, aOffset, node, aOffset, |
michael@0 | 1731 | true, &ranges); |
michael@0 | 1732 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1733 | |
michael@0 | 1734 | if (ranges.Length() == 0) |
michael@0 | 1735 | return NS_OK; // no matches |
michael@0 | 1736 | |
michael@0 | 1737 | // there may be more than one range returned, and we don't know what do |
michael@0 | 1738 | // do with that, so just get the first one |
michael@0 | 1739 | NS_ADDREF(*aRange = ranges[0]); |
michael@0 | 1740 | return NS_OK; |
michael@0 | 1741 | } |
michael@0 | 1742 | |
michael@0 | 1743 | nsresult |
michael@0 | 1744 | mozInlineSpellChecker::CleanupRangesInSelection(nsISelection *aSelection) |
michael@0 | 1745 | { |
michael@0 | 1746 | // integrity check - remove ranges that have collapsed to nothing. This |
michael@0 | 1747 | // can happen if the node containing a highlighted word was removed. |
michael@0 | 1748 | NS_ENSURE_ARG_POINTER(aSelection); |
michael@0 | 1749 | |
michael@0 | 1750 | int32_t count; |
michael@0 | 1751 | aSelection->GetRangeCount(&count); |
michael@0 | 1752 | |
michael@0 | 1753 | for (int32_t index = 0; index < count; index++) |
michael@0 | 1754 | { |
michael@0 | 1755 | nsCOMPtr<nsIDOMRange> checkRange; |
michael@0 | 1756 | aSelection->GetRangeAt(index, getter_AddRefs(checkRange)); |
michael@0 | 1757 | |
michael@0 | 1758 | if (checkRange) |
michael@0 | 1759 | { |
michael@0 | 1760 | bool collapsed; |
michael@0 | 1761 | checkRange->GetCollapsed(&collapsed); |
michael@0 | 1762 | if (collapsed) |
michael@0 | 1763 | { |
michael@0 | 1764 | RemoveRange(aSelection, checkRange); |
michael@0 | 1765 | index--; |
michael@0 | 1766 | count--; |
michael@0 | 1767 | } |
michael@0 | 1768 | } |
michael@0 | 1769 | } |
michael@0 | 1770 | |
michael@0 | 1771 | return NS_OK; |
michael@0 | 1772 | } |
michael@0 | 1773 | |
michael@0 | 1774 | |
michael@0 | 1775 | // mozInlineSpellChecker::RemoveRange |
michael@0 | 1776 | // |
michael@0 | 1777 | // For performance reasons, we have an upper bound on the number of word |
michael@0 | 1778 | // ranges in the spell check selection. When removing a range from the |
michael@0 | 1779 | // selection, we need to decrement mNumWordsInSpellSelection |
michael@0 | 1780 | |
michael@0 | 1781 | nsresult |
michael@0 | 1782 | mozInlineSpellChecker::RemoveRange(nsISelection* aSpellCheckSelection, |
michael@0 | 1783 | nsIDOMRange* aRange) |
michael@0 | 1784 | { |
michael@0 | 1785 | NS_ENSURE_ARG_POINTER(aSpellCheckSelection); |
michael@0 | 1786 | NS_ENSURE_ARG_POINTER(aRange); |
michael@0 | 1787 | |
michael@0 | 1788 | nsresult rv = aSpellCheckSelection->RemoveRange(aRange); |
michael@0 | 1789 | if (NS_SUCCEEDED(rv) && mNumWordsInSpellSelection) |
michael@0 | 1790 | mNumWordsInSpellSelection--; |
michael@0 | 1791 | |
michael@0 | 1792 | return rv; |
michael@0 | 1793 | } |
michael@0 | 1794 | |
michael@0 | 1795 | |
michael@0 | 1796 | // mozInlineSpellChecker::AddRange |
michael@0 | 1797 | // |
michael@0 | 1798 | // For performance reasons, we have an upper bound on the number of word |
michael@0 | 1799 | // ranges we'll add to the spell check selection. Once we reach that upper |
michael@0 | 1800 | // bound, stop adding the ranges |
michael@0 | 1801 | |
michael@0 | 1802 | nsresult |
michael@0 | 1803 | mozInlineSpellChecker::AddRange(nsISelection* aSpellCheckSelection, |
michael@0 | 1804 | nsIDOMRange* aRange) |
michael@0 | 1805 | { |
michael@0 | 1806 | NS_ENSURE_ARG_POINTER(aSpellCheckSelection); |
michael@0 | 1807 | NS_ENSURE_ARG_POINTER(aRange); |
michael@0 | 1808 | |
michael@0 | 1809 | nsresult rv = NS_OK; |
michael@0 | 1810 | |
michael@0 | 1811 | if (!SpellCheckSelectionIsFull()) |
michael@0 | 1812 | { |
michael@0 | 1813 | rv = aSpellCheckSelection->AddRange(aRange); |
michael@0 | 1814 | if (NS_SUCCEEDED(rv)) |
michael@0 | 1815 | mNumWordsInSpellSelection++; |
michael@0 | 1816 | } |
michael@0 | 1817 | |
michael@0 | 1818 | return rv; |
michael@0 | 1819 | } |
michael@0 | 1820 | |
michael@0 | 1821 | nsresult mozInlineSpellChecker::GetSpellCheckSelection(nsISelection ** aSpellCheckSelection) |
michael@0 | 1822 | { |
michael@0 | 1823 | nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); |
michael@0 | 1824 | NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); |
michael@0 | 1825 | |
michael@0 | 1826 | nsCOMPtr<nsISelectionController> selcon; |
michael@0 | 1827 | nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon)); |
michael@0 | 1828 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1829 | |
michael@0 | 1830 | nsCOMPtr<nsISelection> spellCheckSelection; |
michael@0 | 1831 | return selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, aSpellCheckSelection); |
michael@0 | 1832 | } |
michael@0 | 1833 | |
michael@0 | 1834 | nsresult mozInlineSpellChecker::SaveCurrentSelectionPosition() |
michael@0 | 1835 | { |
michael@0 | 1836 | nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); |
michael@0 | 1837 | NS_ENSURE_TRUE(editor, NS_OK); |
michael@0 | 1838 | |
michael@0 | 1839 | // figure out the old caret position based on the current selection |
michael@0 | 1840 | nsCOMPtr<nsISelection> selection; |
michael@0 | 1841 | nsresult rv = editor->GetSelection(getter_AddRefs(selection)); |
michael@0 | 1842 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1843 | |
michael@0 | 1844 | rv = selection->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode)); |
michael@0 | 1845 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1846 | |
michael@0 | 1847 | selection->GetFocusOffset(&mCurrentSelectionOffset); |
michael@0 | 1848 | |
michael@0 | 1849 | return NS_OK; |
michael@0 | 1850 | } |
michael@0 | 1851 | |
michael@0 | 1852 | // This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime |
michael@0 | 1853 | // for XPCOM's rap sheet |
michael@0 | 1854 | bool // static |
michael@0 | 1855 | ContentIsDescendantOf(nsINode* aPossibleDescendant, |
michael@0 | 1856 | nsINode* aPossibleAncestor) |
michael@0 | 1857 | { |
michael@0 | 1858 | NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); |
michael@0 | 1859 | NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); |
michael@0 | 1860 | |
michael@0 | 1861 | do { |
michael@0 | 1862 | if (aPossibleDescendant == aPossibleAncestor) |
michael@0 | 1863 | return true; |
michael@0 | 1864 | aPossibleDescendant = aPossibleDescendant->GetParentNode(); |
michael@0 | 1865 | } while (aPossibleDescendant); |
michael@0 | 1866 | |
michael@0 | 1867 | return false; |
michael@0 | 1868 | } |
michael@0 | 1869 | |
michael@0 | 1870 | // mozInlineSpellChecker::HandleNavigationEvent |
michael@0 | 1871 | // |
michael@0 | 1872 | // Acts upon mouse clicks and keyboard navigation changes, spell checking |
michael@0 | 1873 | // the previous word if the new navigation location moves us to another |
michael@0 | 1874 | // word. |
michael@0 | 1875 | // |
michael@0 | 1876 | // This is complicated by the fact that our mouse events are happening after |
michael@0 | 1877 | // selection has been changed to account for the mouse click. But keyboard |
michael@0 | 1878 | // events are happening before the caret selection has changed. Working |
michael@0 | 1879 | // around this by letting keyboard events setting forceWordSpellCheck to |
michael@0 | 1880 | // true. aNewPositionOffset also tries to work around this for the |
michael@0 | 1881 | // DOM_VK_RIGHT and DOM_VK_LEFT cases. |
michael@0 | 1882 | |
michael@0 | 1883 | nsresult |
michael@0 | 1884 | mozInlineSpellChecker::HandleNavigationEvent(bool aForceWordSpellCheck, |
michael@0 | 1885 | int32_t aNewPositionOffset) |
michael@0 | 1886 | { |
michael@0 | 1887 | nsresult rv; |
michael@0 | 1888 | |
michael@0 | 1889 | // If we already handled the navigation event and there is no possibility |
michael@0 | 1890 | // anything has changed since then, we don't have to do anything. This |
michael@0 | 1891 | // optimization makes a noticeable difference when you hold down a navigation |
michael@0 | 1892 | // key like Page Down. |
michael@0 | 1893 | if (! mNeedsCheckAfterNavigation) |
michael@0 | 1894 | return NS_OK; |
michael@0 | 1895 | |
michael@0 | 1896 | nsCOMPtr<nsIDOMNode> currentAnchorNode = mCurrentSelectionAnchorNode; |
michael@0 | 1897 | int32_t currentAnchorOffset = mCurrentSelectionOffset; |
michael@0 | 1898 | |
michael@0 | 1899 | // now remember the new focus position resulting from the event |
michael@0 | 1900 | rv = SaveCurrentSelectionPosition(); |
michael@0 | 1901 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1902 | |
michael@0 | 1903 | bool shouldPost; |
michael@0 | 1904 | mozInlineSpellStatus status(this); |
michael@0 | 1905 | rv = status.InitForNavigation(aForceWordSpellCheck, aNewPositionOffset, |
michael@0 | 1906 | currentAnchorNode, currentAnchorOffset, |
michael@0 | 1907 | mCurrentSelectionAnchorNode, mCurrentSelectionOffset, |
michael@0 | 1908 | &shouldPost); |
michael@0 | 1909 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1910 | if (shouldPost) { |
michael@0 | 1911 | rv = ScheduleSpellCheck(status); |
michael@0 | 1912 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1913 | } |
michael@0 | 1914 | |
michael@0 | 1915 | return NS_OK; |
michael@0 | 1916 | } |
michael@0 | 1917 | |
michael@0 | 1918 | NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(nsIDOMEvent* aEvent) |
michael@0 | 1919 | { |
michael@0 | 1920 | nsAutoString eventType; |
michael@0 | 1921 | aEvent->GetType(eventType); |
michael@0 | 1922 | |
michael@0 | 1923 | if (eventType.EqualsLiteral("blur")) { |
michael@0 | 1924 | return Blur(aEvent); |
michael@0 | 1925 | } |
michael@0 | 1926 | if (eventType.EqualsLiteral("click")) { |
michael@0 | 1927 | return MouseClick(aEvent); |
michael@0 | 1928 | } |
michael@0 | 1929 | if (eventType.EqualsLiteral("keypress")) { |
michael@0 | 1930 | return KeyPress(aEvent); |
michael@0 | 1931 | } |
michael@0 | 1932 | |
michael@0 | 1933 | return NS_OK; |
michael@0 | 1934 | } |
michael@0 | 1935 | |
michael@0 | 1936 | nsresult mozInlineSpellChecker::Blur(nsIDOMEvent* aEvent) |
michael@0 | 1937 | { |
michael@0 | 1938 | // force spellcheck on blur, for instance when tabbing out of a textbox |
michael@0 | 1939 | HandleNavigationEvent(true); |
michael@0 | 1940 | return NS_OK; |
michael@0 | 1941 | } |
michael@0 | 1942 | |
michael@0 | 1943 | nsresult mozInlineSpellChecker::MouseClick(nsIDOMEvent *aMouseEvent) |
michael@0 | 1944 | { |
michael@0 | 1945 | nsCOMPtr<nsIDOMMouseEvent>mouseEvent = do_QueryInterface(aMouseEvent); |
michael@0 | 1946 | NS_ENSURE_TRUE(mouseEvent, NS_OK); |
michael@0 | 1947 | |
michael@0 | 1948 | // ignore any errors from HandleNavigationEvent as we don't want to prevent |
michael@0 | 1949 | // anyone else from seeing this event. |
michael@0 | 1950 | int16_t button; |
michael@0 | 1951 | mouseEvent->GetButton(&button); |
michael@0 | 1952 | HandleNavigationEvent(button != 0); |
michael@0 | 1953 | return NS_OK; |
michael@0 | 1954 | } |
michael@0 | 1955 | |
michael@0 | 1956 | nsresult mozInlineSpellChecker::KeyPress(nsIDOMEvent* aKeyEvent) |
michael@0 | 1957 | { |
michael@0 | 1958 | nsCOMPtr<nsIDOMKeyEvent>keyEvent = do_QueryInterface(aKeyEvent); |
michael@0 | 1959 | NS_ENSURE_TRUE(keyEvent, NS_OK); |
michael@0 | 1960 | |
michael@0 | 1961 | uint32_t keyCode; |
michael@0 | 1962 | keyEvent->GetKeyCode(&keyCode); |
michael@0 | 1963 | |
michael@0 | 1964 | // we only care about navigation keys that moved selection |
michael@0 | 1965 | switch (keyCode) |
michael@0 | 1966 | { |
michael@0 | 1967 | case nsIDOMKeyEvent::DOM_VK_RIGHT: |
michael@0 | 1968 | case nsIDOMKeyEvent::DOM_VK_LEFT: |
michael@0 | 1969 | HandleNavigationEvent(false, keyCode == nsIDOMKeyEvent::DOM_VK_RIGHT ? 1 : -1); |
michael@0 | 1970 | break; |
michael@0 | 1971 | case nsIDOMKeyEvent::DOM_VK_UP: |
michael@0 | 1972 | case nsIDOMKeyEvent::DOM_VK_DOWN: |
michael@0 | 1973 | case nsIDOMKeyEvent::DOM_VK_HOME: |
michael@0 | 1974 | case nsIDOMKeyEvent::DOM_VK_END: |
michael@0 | 1975 | case nsIDOMKeyEvent::DOM_VK_PAGE_UP: |
michael@0 | 1976 | case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: |
michael@0 | 1977 | HandleNavigationEvent(true /* force a spelling correction */); |
michael@0 | 1978 | break; |
michael@0 | 1979 | } |
michael@0 | 1980 | |
michael@0 | 1981 | return NS_OK; |
michael@0 | 1982 | } |
michael@0 | 1983 | |
michael@0 | 1984 | // Used as the nsIEditorSpellCheck::UpdateCurrentDictionary callback. |
michael@0 | 1985 | class UpdateCurrentDictionaryCallback MOZ_FINAL : public nsIEditorSpellCheckCallback |
michael@0 | 1986 | { |
michael@0 | 1987 | public: |
michael@0 | 1988 | NS_DECL_ISUPPORTS |
michael@0 | 1989 | |
michael@0 | 1990 | explicit UpdateCurrentDictionaryCallback(mozInlineSpellChecker* aSpellChecker, |
michael@0 | 1991 | uint32_t aDisabledAsyncToken) |
michael@0 | 1992 | : mSpellChecker(aSpellChecker), mDisabledAsyncToken(aDisabledAsyncToken) {} |
michael@0 | 1993 | |
michael@0 | 1994 | NS_IMETHOD EditorSpellCheckDone() |
michael@0 | 1995 | { |
michael@0 | 1996 | // Ignore this callback if SetEnableRealTimeSpell(false) was called after |
michael@0 | 1997 | // the UpdateCurrentDictionary call that triggered it. |
michael@0 | 1998 | return mSpellChecker->mDisabledAsyncToken > mDisabledAsyncToken ? |
michael@0 | 1999 | NS_OK : |
michael@0 | 2000 | mSpellChecker->CurrentDictionaryUpdated(); |
michael@0 | 2001 | } |
michael@0 | 2002 | |
michael@0 | 2003 | private: |
michael@0 | 2004 | nsRefPtr<mozInlineSpellChecker> mSpellChecker; |
michael@0 | 2005 | uint32_t mDisabledAsyncToken; |
michael@0 | 2006 | }; |
michael@0 | 2007 | NS_IMPL_ISUPPORTS(UpdateCurrentDictionaryCallback, nsIEditorSpellCheckCallback) |
michael@0 | 2008 | |
michael@0 | 2009 | NS_IMETHODIMP mozInlineSpellChecker::UpdateCurrentDictionary() |
michael@0 | 2010 | { |
michael@0 | 2011 | // mSpellCheck is null and mPendingSpellCheck is nonnull while the spell |
michael@0 | 2012 | // checker is being initialized. Calling UpdateCurrentDictionary on |
michael@0 | 2013 | // mPendingSpellCheck simply queues the dictionary update after the init. |
michael@0 | 2014 | nsCOMPtr<nsIEditorSpellCheck> spellCheck = mSpellCheck ? mSpellCheck : |
michael@0 | 2015 | mPendingSpellCheck; |
michael@0 | 2016 | if (!spellCheck) { |
michael@0 | 2017 | return NS_OK; |
michael@0 | 2018 | } |
michael@0 | 2019 | |
michael@0 | 2020 | if (NS_FAILED(spellCheck->GetCurrentDictionary(mPreviousDictionary))) { |
michael@0 | 2021 | mPreviousDictionary.Truncate(); |
michael@0 | 2022 | } |
michael@0 | 2023 | |
michael@0 | 2024 | nsRefPtr<UpdateCurrentDictionaryCallback> cb = |
michael@0 | 2025 | new UpdateCurrentDictionaryCallback(this, mDisabledAsyncToken); |
michael@0 | 2026 | NS_ENSURE_STATE(cb); |
michael@0 | 2027 | nsresult rv = spellCheck->UpdateCurrentDictionary(cb); |
michael@0 | 2028 | if (NS_FAILED(rv)) { |
michael@0 | 2029 | cb = nullptr; |
michael@0 | 2030 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2031 | } |
michael@0 | 2032 | mNumPendingUpdateCurrentDictionary++; |
michael@0 | 2033 | ChangeNumPendingSpellChecks(1); |
michael@0 | 2034 | |
michael@0 | 2035 | return NS_OK; |
michael@0 | 2036 | } |
michael@0 | 2037 | |
michael@0 | 2038 | // Called when nsIEditorSpellCheck::UpdateCurrentDictionary completes. |
michael@0 | 2039 | nsresult mozInlineSpellChecker::CurrentDictionaryUpdated() |
michael@0 | 2040 | { |
michael@0 | 2041 | mNumPendingUpdateCurrentDictionary--; |
michael@0 | 2042 | NS_ASSERTION(mNumPendingUpdateCurrentDictionary >= 0, |
michael@0 | 2043 | "CurrentDictionaryUpdated called without corresponding " |
michael@0 | 2044 | "UpdateCurrentDictionary call!"); |
michael@0 | 2045 | ChangeNumPendingSpellChecks(-1); |
michael@0 | 2046 | |
michael@0 | 2047 | nsAutoString currentDictionary; |
michael@0 | 2048 | if (!mSpellCheck || |
michael@0 | 2049 | NS_FAILED(mSpellCheck->GetCurrentDictionary(currentDictionary))) { |
michael@0 | 2050 | currentDictionary.Truncate(); |
michael@0 | 2051 | } |
michael@0 | 2052 | |
michael@0 | 2053 | if (!mPreviousDictionary.Equals(currentDictionary)) { |
michael@0 | 2054 | nsresult rv = SpellCheckRange(nullptr); |
michael@0 | 2055 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 2056 | } |
michael@0 | 2057 | |
michael@0 | 2058 | return NS_OK; |
michael@0 | 2059 | } |
michael@0 | 2060 | |
michael@0 | 2061 | NS_IMETHODIMP |
michael@0 | 2062 | mozInlineSpellChecker::GetSpellCheckPending(bool* aPending) |
michael@0 | 2063 | { |
michael@0 | 2064 | *aPending = mNumPendingSpellChecks > 0; |
michael@0 | 2065 | return NS_OK; |
michael@0 | 2066 | } |