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