1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2066 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/** 1.10 + * This class is called by the editor to handle spellchecking after various 1.11 + * events. The main entrypoint is SpellCheckAfterEditorChange, which is called 1.12 + * when the text is changed. 1.13 + * 1.14 + * It is VERY IMPORTANT that we do NOT do any operations that might cause DOM 1.15 + * notifications to be flushed when we are called from the editor. This is 1.16 + * because the call might originate from a frame, and flushing the 1.17 + * notifications might cause that frame to be deleted. 1.18 + * 1.19 + * Using the WordUtil class to find words causes DOM notifications to be 1.20 + * flushed because it asks for style information. As a result, we post an event 1.21 + * and do all of the spellchecking in that event handler, which occurs later. 1.22 + * We store all DOM pointers in ranges because they are kept up-to-date with 1.23 + * DOM changes that may have happened while the event was on the queue. 1.24 + * 1.25 + * We also allow the spellcheck to be suspended and resumed later. This makes 1.26 + * large pastes or initializations with a lot of text not hang the browser UI. 1.27 + * 1.28 + * An optimization is the mNeedsCheckAfterNavigation flag. This is set to 1.29 + * true when we get any change, and false once there is no possibility 1.30 + * something changed that we need to check on navigation. Navigation events 1.31 + * tend to be a little tricky because we want to check the current word on 1.32 + * exit if something has changed. If we navigate inside the word, we don't want 1.33 + * to do anything. As a result, this flag is cleared in FinishNavigationEvent 1.34 + * when we know that we are checking as a result of navigation. 1.35 + */ 1.36 + 1.37 +#include "mozInlineSpellChecker.h" 1.38 +#include "mozInlineSpellWordUtil.h" 1.39 +#include "mozISpellI18NManager.h" 1.40 +#include "nsCOMPtr.h" 1.41 +#include "nsCRT.h" 1.42 +#include "nsIDOMNode.h" 1.43 +#include "nsIDOMDocument.h" 1.44 +#include "nsIDOMElement.h" 1.45 +#include "nsIDOMHTMLElement.h" 1.46 +#include "nsIDOMMouseEvent.h" 1.47 +#include "nsIDOMKeyEvent.h" 1.48 +#include "nsIDOMNode.h" 1.49 +#include "nsIDOMNodeList.h" 1.50 +#include "nsRange.h" 1.51 +#include "nsIPlaintextEditor.h" 1.52 +#include "nsIPrefBranch.h" 1.53 +#include "nsIPrefService.h" 1.54 +#include "nsIRunnable.h" 1.55 +#include "nsISelection.h" 1.56 +#include "nsISelectionPrivate.h" 1.57 +#include "nsISelectionController.h" 1.58 +#include "nsIServiceManager.h" 1.59 +#include "nsITextServicesFilter.h" 1.60 +#include "nsString.h" 1.61 +#include "nsThreadUtils.h" 1.62 +#include "nsUnicharUtils.h" 1.63 +#include "nsIContent.h" 1.64 +#include "nsRange.h" 1.65 +#include "nsContentUtils.h" 1.66 +#include "nsEditor.h" 1.67 +#include "mozilla/Services.h" 1.68 +#include "nsIObserverService.h" 1.69 +#include "nsITextControlElement.h" 1.70 +#include "prtime.h" 1.71 + 1.72 +using namespace mozilla::dom; 1.73 + 1.74 +// Set to spew messages to the console about what is happening. 1.75 +//#define DEBUG_INLINESPELL 1.76 + 1.77 +// the number of milliseconds that we will take at once to do spellchecking 1.78 +#define INLINESPELL_CHECK_TIMEOUT 50 1.79 + 1.80 +// The number of words to check before we look at the time to see if 1.81 +// INLINESPELL_CHECK_TIMEOUT ms have elapsed. This prevents us from spending 1.82 +// too much time checking the clock. Note that misspelled words count for 1.83 +// more than one word in this calculation. 1.84 +#define INLINESPELL_TIMEOUT_CHECK_FREQUENCY 50 1.85 + 1.86 +// This number is the number of checked words a misspelled word counts for 1.87 +// when we're checking the time to see if the alloted time is up for 1.88 +// spellchecking. Misspelled words take longer to process since we have to 1.89 +// create a range, so they count more. The exact number isn't very important 1.90 +// since this just controls how often we check the current time. 1.91 +#define MISSPELLED_WORD_COUNT_PENALTY 4 1.92 + 1.93 +// These notifications are broadcast when spell check starts and ends. STARTED 1.94 +// must always be followed by ENDED. 1.95 +#define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started" 1.96 +#define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended" 1.97 + 1.98 +static bool ContentIsDescendantOf(nsINode* aPossibleDescendant, 1.99 + nsINode* aPossibleAncestor); 1.100 + 1.101 +static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings"; 1.102 + 1.103 +mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker) 1.104 + : mSpellChecker(aSpellChecker), mWordCount(0) 1.105 +{ 1.106 +} 1.107 + 1.108 +// mozInlineSpellStatus::InitForEditorChange 1.109 +// 1.110 +// This is the most complicated case. For changes, we need to compute the 1.111 +// range of stuff that changed based on the old and new caret positions, 1.112 +// as well as use a range possibly provided by the editor (start and end, 1.113 +// which are usually nullptr) to get a range with the union of these. 1.114 + 1.115 +nsresult 1.116 +mozInlineSpellStatus::InitForEditorChange( 1.117 + EditAction aAction, 1.118 + nsIDOMNode* aAnchorNode, int32_t aAnchorOffset, 1.119 + nsIDOMNode* aPreviousNode, int32_t aPreviousOffset, 1.120 + nsIDOMNode* aStartNode, int32_t aStartOffset, 1.121 + nsIDOMNode* aEndNode, int32_t aEndOffset) 1.122 +{ 1.123 + nsresult rv; 1.124 + 1.125 + nsCOMPtr<nsIDOMDocument> doc; 1.126 + rv = GetDocument(getter_AddRefs(doc)); 1.127 + NS_ENSURE_SUCCESS(rv, rv); 1.128 + 1.129 + // save the anchor point as a range so we can find the current word later 1.130 + rv = PositionToCollapsedRange(doc, aAnchorNode, aAnchorOffset, 1.131 + getter_AddRefs(mAnchorRange)); 1.132 + NS_ENSURE_SUCCESS(rv, rv); 1.133 + 1.134 + if (aAction == EditAction::deleteSelection) { 1.135 + // Deletes are easy, the range is just the current anchor. We set the range 1.136 + // to check to be empty, FinishInitOnEvent will fill in the range to be 1.137 + // the current word. 1.138 + mOp = eOpChangeDelete; 1.139 + mRange = nullptr; 1.140 + return NS_OK; 1.141 + } 1.142 + 1.143 + mOp = eOpChange; 1.144 + 1.145 + // range to check 1.146 + nsCOMPtr<nsINode> prevNode = do_QueryInterface(aPreviousNode); 1.147 + NS_ENSURE_STATE(prevNode); 1.148 + 1.149 + mRange = new nsRange(prevNode); 1.150 + 1.151 + // ...we need to put the start and end in the correct order 1.152 + int16_t cmpResult; 1.153 + rv = mAnchorRange->ComparePoint(aPreviousNode, aPreviousOffset, &cmpResult); 1.154 + NS_ENSURE_SUCCESS(rv, rv); 1.155 + if (cmpResult < 0) { 1.156 + // previous anchor node is before the current anchor 1.157 + rv = mRange->SetStart(aPreviousNode, aPreviousOffset); 1.158 + NS_ENSURE_SUCCESS(rv, rv); 1.159 + rv = mRange->SetEnd(aAnchorNode, aAnchorOffset); 1.160 + } else { 1.161 + // previous anchor node is after (or the same as) the current anchor 1.162 + rv = mRange->SetStart(aAnchorNode, aAnchorOffset); 1.163 + NS_ENSURE_SUCCESS(rv, rv); 1.164 + rv = mRange->SetEnd(aPreviousNode, aPreviousOffset); 1.165 + } 1.166 + NS_ENSURE_SUCCESS(rv, rv); 1.167 + 1.168 + // On insert save this range: DoSpellCheck optimizes things in this range. 1.169 + // Otherwise, just leave this nullptr. 1.170 + if (aAction == EditAction::insertText) 1.171 + mCreatedRange = mRange; 1.172 + 1.173 + // if we were given a range, we need to expand our range to encompass it 1.174 + if (aStartNode && aEndNode) { 1.175 + rv = mRange->ComparePoint(aStartNode, aStartOffset, &cmpResult); 1.176 + NS_ENSURE_SUCCESS(rv, rv); 1.177 + if (cmpResult < 0) { // given range starts before 1.178 + rv = mRange->SetStart(aStartNode, aStartOffset); 1.179 + NS_ENSURE_SUCCESS(rv, rv); 1.180 + } 1.181 + 1.182 + rv = mRange->ComparePoint(aEndNode, aEndOffset, &cmpResult); 1.183 + NS_ENSURE_SUCCESS(rv, rv); 1.184 + if (cmpResult > 0) { // given range ends after 1.185 + rv = mRange->SetEnd(aEndNode, aEndOffset); 1.186 + NS_ENSURE_SUCCESS(rv, rv); 1.187 + } 1.188 + } 1.189 + 1.190 + return NS_OK; 1.191 +} 1.192 + 1.193 +// mozInlineSpellStatis::InitForNavigation 1.194 +// 1.195 +// For navigation events, we just need to store the new and old positions. 1.196 +// 1.197 +// In some cases, we detect that we shouldn't check. If this event should 1.198 +// not be processed, *aContinue will be false. 1.199 + 1.200 +nsresult 1.201 +mozInlineSpellStatus::InitForNavigation( 1.202 + bool aForceCheck, int32_t aNewPositionOffset, 1.203 + nsIDOMNode* aOldAnchorNode, int32_t aOldAnchorOffset, 1.204 + nsIDOMNode* aNewAnchorNode, int32_t aNewAnchorOffset, 1.205 + bool* aContinue) 1.206 +{ 1.207 + nsresult rv; 1.208 + mOp = eOpNavigation; 1.209 + 1.210 + mForceNavigationWordCheck = aForceCheck; 1.211 + mNewNavigationPositionOffset = aNewPositionOffset; 1.212 + 1.213 + // get the root node for checking 1.214 + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv); 1.215 + NS_ENSURE_SUCCESS(rv, rv); 1.216 + nsCOMPtr<nsIDOMElement> rootElt; 1.217 + rv = editor->GetRootElement(getter_AddRefs(rootElt)); 1.218 + NS_ENSURE_SUCCESS(rv, rv); 1.219 + 1.220 + // the anchor node might not be in the DOM anymore, check 1.221 + nsCOMPtr<nsINode> root = do_QueryInterface(rootElt, &rv); 1.222 + NS_ENSURE_SUCCESS(rv, rv); 1.223 + nsCOMPtr<nsINode> currentAnchor = do_QueryInterface(aOldAnchorNode, &rv); 1.224 + NS_ENSURE_SUCCESS(rv, rv); 1.225 + if (root && currentAnchor && ! ContentIsDescendantOf(currentAnchor, root)) { 1.226 + *aContinue = false; 1.227 + return NS_OK; 1.228 + } 1.229 + 1.230 + nsCOMPtr<nsIDOMDocument> doc; 1.231 + rv = GetDocument(getter_AddRefs(doc)); 1.232 + NS_ENSURE_SUCCESS(rv, rv); 1.233 + 1.234 + rv = PositionToCollapsedRange(doc, aOldAnchorNode, aOldAnchorOffset, 1.235 + getter_AddRefs(mOldNavigationAnchorRange)); 1.236 + NS_ENSURE_SUCCESS(rv, rv); 1.237 + rv = PositionToCollapsedRange(doc, aNewAnchorNode, aNewAnchorOffset, 1.238 + getter_AddRefs(mAnchorRange)); 1.239 + NS_ENSURE_SUCCESS(rv, rv); 1.240 + 1.241 + *aContinue = true; 1.242 + return NS_OK; 1.243 +} 1.244 + 1.245 +// mozInlineSpellStatus::InitForSelection 1.246 +// 1.247 +// It is easy for selections since we always re-check the spellcheck 1.248 +// selection. 1.249 + 1.250 +nsresult 1.251 +mozInlineSpellStatus::InitForSelection() 1.252 +{ 1.253 + mOp = eOpSelection; 1.254 + return NS_OK; 1.255 +} 1.256 + 1.257 +// mozInlineSpellStatus::InitForRange 1.258 +// 1.259 +// Called to cause the spellcheck of the given range. This will look like 1.260 +// a change operation over the given range. 1.261 + 1.262 +nsresult 1.263 +mozInlineSpellStatus::InitForRange(nsRange* aRange) 1.264 +{ 1.265 + mOp = eOpChange; 1.266 + mRange = aRange; 1.267 + return NS_OK; 1.268 +} 1.269 + 1.270 +// mozInlineSpellStatus::FinishInitOnEvent 1.271 +// 1.272 +// Called when the event is triggered to complete initialization that 1.273 +// might require the WordUtil. This calls to the operation-specific 1.274 +// initializer, and also sets the range to be the entire element if it 1.275 +// is nullptr. 1.276 +// 1.277 +// Watch out: the range might still be nullptr if there is nothing to do, 1.278 +// the caller will have to check for this. 1.279 + 1.280 +nsresult 1.281 +mozInlineSpellStatus::FinishInitOnEvent(mozInlineSpellWordUtil& aWordUtil) 1.282 +{ 1.283 + nsresult rv; 1.284 + if (! mRange) { 1.285 + rv = mSpellChecker->MakeSpellCheckRange(nullptr, 0, nullptr, 0, 1.286 + getter_AddRefs(mRange)); 1.287 + NS_ENSURE_SUCCESS(rv, rv); 1.288 + } 1.289 + 1.290 + switch (mOp) { 1.291 + case eOpChange: 1.292 + if (mAnchorRange) 1.293 + return FillNoCheckRangeFromAnchor(aWordUtil); 1.294 + break; 1.295 + case eOpChangeDelete: 1.296 + if (mAnchorRange) { 1.297 + rv = FillNoCheckRangeFromAnchor(aWordUtil); 1.298 + NS_ENSURE_SUCCESS(rv, rv); 1.299 + } 1.300 + // Delete events will have no range for the changed text (because it was 1.301 + // deleted), and InitForEditorChange will set it to nullptr. Here, we select 1.302 + // the entire word to cause any underlining to be removed. 1.303 + mRange = mNoCheckRange; 1.304 + break; 1.305 + case eOpNavigation: 1.306 + return FinishNavigationEvent(aWordUtil); 1.307 + case eOpSelection: 1.308 + // this gets special handling in ResumeCheck 1.309 + break; 1.310 + case eOpResume: 1.311 + // everything should be initialized already in this case 1.312 + break; 1.313 + default: 1.314 + NS_NOTREACHED("Bad operation"); 1.315 + return NS_ERROR_NOT_INITIALIZED; 1.316 + } 1.317 + return NS_OK; 1.318 +} 1.319 + 1.320 +// mozInlineSpellStatus::FinishNavigationEvent 1.321 +// 1.322 +// This verifies that we need to check the word at the previous caret 1.323 +// position. Now that we have the word util, we can find the word belonging 1.324 +// to the previous caret position. If the new position is inside that word, 1.325 +// we don't want to do anything. In this case, we'll nullptr out mRange so 1.326 +// that the caller will know not to continue. 1.327 +// 1.328 +// Notice that we don't set mNoCheckRange. We check here whether the cursor 1.329 +// is in the word that needs checking, so it isn't necessary. Plus, the 1.330 +// spellchecker isn't guaranteed to only check the given word, and it could 1.331 +// remove the underline from the new word under the cursor. 1.332 + 1.333 +nsresult 1.334 +mozInlineSpellStatus::FinishNavigationEvent(mozInlineSpellWordUtil& aWordUtil) 1.335 +{ 1.336 + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor); 1.337 + if (! editor) 1.338 + return NS_ERROR_FAILURE; // editor is gone 1.339 + 1.340 + NS_ASSERTION(mAnchorRange, "No anchor for navigation!"); 1.341 + nsCOMPtr<nsIDOMNode> newAnchorNode, oldAnchorNode; 1.342 + int32_t newAnchorOffset, oldAnchorOffset; 1.343 + 1.344 + // get the DOM position of the old caret, the range should be collapsed 1.345 + nsresult rv = mOldNavigationAnchorRange->GetStartContainer( 1.346 + getter_AddRefs(oldAnchorNode)); 1.347 + NS_ENSURE_SUCCESS(rv, rv); 1.348 + rv = mOldNavigationAnchorRange->GetStartOffset(&oldAnchorOffset); 1.349 + NS_ENSURE_SUCCESS(rv, rv); 1.350 + 1.351 + // find the word on the old caret position, this is the one that we MAY need 1.352 + // to check 1.353 + nsRefPtr<nsRange> oldWord; 1.354 + rv = aWordUtil.GetRangeForWord(oldAnchorNode, oldAnchorOffset, 1.355 + getter_AddRefs(oldWord)); 1.356 + NS_ENSURE_SUCCESS(rv, rv); 1.357 + 1.358 + // aWordUtil.GetRangeForWord flushes pending notifications, check editor again. 1.359 + editor = do_QueryReferent(mSpellChecker->mEditor); 1.360 + if (! editor) 1.361 + return NS_ERROR_FAILURE; // editor is gone 1.362 + 1.363 + // get the DOM position of the new caret, the range should be collapsed 1.364 + rv = mAnchorRange->GetStartContainer(getter_AddRefs(newAnchorNode)); 1.365 + NS_ENSURE_SUCCESS(rv, rv); 1.366 + rv = mAnchorRange->GetStartOffset(&newAnchorOffset); 1.367 + NS_ENSURE_SUCCESS(rv, rv); 1.368 + 1.369 + // see if the new cursor position is in the word of the old cursor position 1.370 + bool isInRange = false; 1.371 + if (! mForceNavigationWordCheck) { 1.372 + rv = oldWord->IsPointInRange(newAnchorNode, 1.373 + newAnchorOffset + mNewNavigationPositionOffset, 1.374 + &isInRange); 1.375 + NS_ENSURE_SUCCESS(rv, rv); 1.376 + } 1.377 + 1.378 + if (isInRange) { 1.379 + // caller should give up 1.380 + mRange = nullptr; 1.381 + } else { 1.382 + // check the old word 1.383 + mRange = oldWord; 1.384 + 1.385 + // Once we've spellchecked the current word, we don't need to spellcheck 1.386 + // for any more navigation events. 1.387 + mSpellChecker->mNeedsCheckAfterNavigation = false; 1.388 + } 1.389 + return NS_OK; 1.390 +} 1.391 + 1.392 +// mozInlineSpellStatus::FillNoCheckRangeFromAnchor 1.393 +// 1.394 +// Given the mAnchorRange object, computes the range of the word it is on 1.395 +// (if any) and fills that range into mNoCheckRange. This is used for 1.396 +// change and navigation events to know which word we should skip spell 1.397 +// checking on 1.398 + 1.399 +nsresult 1.400 +mozInlineSpellStatus::FillNoCheckRangeFromAnchor( 1.401 + mozInlineSpellWordUtil& aWordUtil) 1.402 +{ 1.403 + nsCOMPtr<nsIDOMNode> anchorNode; 1.404 + nsresult rv = mAnchorRange->GetStartContainer(getter_AddRefs(anchorNode)); 1.405 + NS_ENSURE_SUCCESS(rv, rv); 1.406 + 1.407 + int32_t anchorOffset; 1.408 + rv = mAnchorRange->GetStartOffset(&anchorOffset); 1.409 + NS_ENSURE_SUCCESS(rv, rv); 1.410 + 1.411 + return aWordUtil.GetRangeForWord(anchorNode, anchorOffset, 1.412 + getter_AddRefs(mNoCheckRange)); 1.413 +} 1.414 + 1.415 +// mozInlineSpellStatus::GetDocument 1.416 +// 1.417 +// Returns the nsIDOMDocument object for the document for the 1.418 +// current spellchecker. 1.419 + 1.420 +nsresult 1.421 +mozInlineSpellStatus::GetDocument(nsIDOMDocument** aDocument) 1.422 +{ 1.423 + nsresult rv; 1.424 + *aDocument = nullptr; 1.425 + if (! mSpellChecker->mEditor) 1.426 + return NS_ERROR_UNEXPECTED; 1.427 + 1.428 + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mSpellChecker->mEditor, &rv); 1.429 + NS_ENSURE_SUCCESS(rv, rv); 1.430 + 1.431 + nsCOMPtr<nsIDOMDocument> domDoc; 1.432 + rv = editor->GetDocument(getter_AddRefs(domDoc)); 1.433 + NS_ENSURE_SUCCESS(rv, rv); 1.434 + NS_ENSURE_TRUE(domDoc, NS_ERROR_NULL_POINTER); 1.435 + domDoc.forget(aDocument); 1.436 + return NS_OK; 1.437 +} 1.438 + 1.439 +// mozInlineSpellStatus::PositionToCollapsedRange 1.440 +// 1.441 +// Converts a given DOM position to a collapsed range covering that 1.442 +// position. We use ranges to store DOM positions becuase they stay 1.443 +// updated as the DOM is changed. 1.444 + 1.445 +nsresult 1.446 +mozInlineSpellStatus::PositionToCollapsedRange(nsIDOMDocument* aDocument, 1.447 + nsIDOMNode* aNode, int32_t aOffset, nsIDOMRange** aRange) 1.448 +{ 1.449 + *aRange = nullptr; 1.450 + nsCOMPtr<nsIDOMRange> range; 1.451 + nsresult rv = aDocument->CreateRange(getter_AddRefs(range)); 1.452 + NS_ENSURE_SUCCESS(rv, rv); 1.453 + 1.454 + rv = range->SetStart(aNode, aOffset); 1.455 + NS_ENSURE_SUCCESS(rv, rv); 1.456 + rv = range->SetEnd(aNode, aOffset); 1.457 + NS_ENSURE_SUCCESS(rv, rv); 1.458 + 1.459 + range.swap(*aRange); 1.460 + return NS_OK; 1.461 +} 1.462 + 1.463 +// mozInlineSpellResume 1.464 + 1.465 +class mozInlineSpellResume : public nsRunnable 1.466 +{ 1.467 +public: 1.468 + mozInlineSpellResume(const mozInlineSpellStatus& aStatus, 1.469 + uint32_t aDisabledAsyncToken) 1.470 + : mDisabledAsyncToken(aDisabledAsyncToken), mStatus(aStatus) {} 1.471 + 1.472 + nsresult Post() 1.473 + { 1.474 + return NS_DispatchToMainThread(this); 1.475 + } 1.476 + 1.477 + NS_IMETHOD Run() 1.478 + { 1.479 + // Discard the resumption if the spell checker was disabled after the 1.480 + // resumption was scheduled. 1.481 + if (mDisabledAsyncToken == mStatus.mSpellChecker->mDisabledAsyncToken) { 1.482 + mStatus.mSpellChecker->ResumeCheck(&mStatus); 1.483 + } 1.484 + return NS_OK; 1.485 + } 1.486 + 1.487 +private: 1.488 + uint32_t mDisabledAsyncToken; 1.489 + mozInlineSpellStatus mStatus; 1.490 +}; 1.491 + 1.492 +// Used as the nsIEditorSpellCheck::InitSpellChecker callback. 1.493 +class InitEditorSpellCheckCallback MOZ_FINAL : public nsIEditorSpellCheckCallback 1.494 +{ 1.495 +public: 1.496 + NS_DECL_ISUPPORTS 1.497 + 1.498 + explicit InitEditorSpellCheckCallback(mozInlineSpellChecker* aSpellChecker) 1.499 + : mSpellChecker(aSpellChecker) {} 1.500 + 1.501 + NS_IMETHOD EditorSpellCheckDone() 1.502 + { 1.503 + return mSpellChecker ? mSpellChecker->EditorSpellCheckInited() : NS_OK; 1.504 + } 1.505 + 1.506 + void Cancel() 1.507 + { 1.508 + mSpellChecker = nullptr; 1.509 + } 1.510 + 1.511 +private: 1.512 + nsRefPtr<mozInlineSpellChecker> mSpellChecker; 1.513 +}; 1.514 +NS_IMPL_ISUPPORTS(InitEditorSpellCheckCallback, nsIEditorSpellCheckCallback) 1.515 + 1.516 + 1.517 +NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker) 1.518 + NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker) 1.519 + NS_INTERFACE_MAP_ENTRY(nsIEditActionListener) 1.520 + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 1.521 + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 1.522 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) 1.523 + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozInlineSpellChecker) 1.524 +NS_INTERFACE_MAP_END 1.525 + 1.526 +NS_IMPL_CYCLE_COLLECTING_ADDREF(mozInlineSpellChecker) 1.527 +NS_IMPL_CYCLE_COLLECTING_RELEASE(mozInlineSpellChecker) 1.528 + 1.529 +NS_IMPL_CYCLE_COLLECTION(mozInlineSpellChecker, 1.530 + mSpellCheck, 1.531 + mTreeWalker, 1.532 + mCurrentSelectionAnchorNode) 1.533 + 1.534 +mozInlineSpellChecker::SpellCheckingState 1.535 + mozInlineSpellChecker::gCanEnableSpellChecking = 1.536 + mozInlineSpellChecker::SpellCheck_Uninitialized; 1.537 + 1.538 +mozInlineSpellChecker::mozInlineSpellChecker() : 1.539 + mNumWordsInSpellSelection(0), 1.540 + mMaxNumWordsInSpellSelection(250), 1.541 + mNumPendingSpellChecks(0), 1.542 + mNumPendingUpdateCurrentDictionary(0), 1.543 + mDisabledAsyncToken(0), 1.544 + mNeedsCheckAfterNavigation(false), 1.545 + mFullSpellCheckScheduled(false) 1.546 +{ 1.547 + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.548 + if (prefs) 1.549 + prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection); 1.550 + mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4; 1.551 +} 1.552 + 1.553 +mozInlineSpellChecker::~mozInlineSpellChecker() 1.554 +{ 1.555 +} 1.556 + 1.557 +NS_IMETHODIMP 1.558 +mozInlineSpellChecker::GetSpellChecker(nsIEditorSpellCheck **aSpellCheck) 1.559 +{ 1.560 + *aSpellCheck = mSpellCheck; 1.561 + NS_IF_ADDREF(*aSpellCheck); 1.562 + return NS_OK; 1.563 +} 1.564 + 1.565 +NS_IMETHODIMP 1.566 +mozInlineSpellChecker::Init(nsIEditor *aEditor) 1.567 +{ 1.568 + mEditor = do_GetWeakReference(aEditor); 1.569 + return NS_OK; 1.570 +} 1.571 + 1.572 +// mozInlineSpellChecker::Cleanup 1.573 +// 1.574 +// Called by the editor when the editor is going away. This is important 1.575 +// because we remove listeners. We do NOT clean up anything else in this 1.576 +// function, because it can get called while DoSpellCheck is running! 1.577 +// 1.578 +// Getting the style information there can cause DOM notifications to be 1.579 +// flushed, which can cause editors to go away which will bring us here. 1.580 +// We can not do anything that will cause DoSpellCheck to freak out. 1.581 + 1.582 +nsresult mozInlineSpellChecker::Cleanup(bool aDestroyingFrames) 1.583 +{ 1.584 + mNumWordsInSpellSelection = 0; 1.585 + nsCOMPtr<nsISelection> spellCheckSelection; 1.586 + nsresult rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); 1.587 + if (NS_FAILED(rv)) { 1.588 + // Ensure we still unregister event listeners (but return a failure code) 1.589 + UnregisterEventListeners(); 1.590 + } else { 1.591 + if (!aDestroyingFrames) { 1.592 + spellCheckSelection->RemoveAllRanges(); 1.593 + } 1.594 + 1.595 + rv = UnregisterEventListeners(); 1.596 + } 1.597 + 1.598 + // Notify ENDED observers now. If we wait to notify as we normally do when 1.599 + // these async operations finish, then in the meantime the editor may create 1.600 + // another inline spell checker and cause more STARTED and ENDED 1.601 + // notifications to be broadcast. Interleaved notifications for the same 1.602 + // editor but different inline spell checkers could easily confuse 1.603 + // observers. They may receive two consecutive STARTED notifications for 1.604 + // example, which we guarantee will not happen. 1.605 + 1.606 + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor); 1.607 + if (mPendingSpellCheck) { 1.608 + // Cancel the pending editor spell checker initialization. 1.609 + mPendingSpellCheck = nullptr; 1.610 + mPendingInitEditorSpellCheckCallback->Cancel(); 1.611 + mPendingInitEditorSpellCheckCallback = nullptr; 1.612 + ChangeNumPendingSpellChecks(-1, editor); 1.613 + } 1.614 + 1.615 + // Increment this token so that pending UpdateCurrentDictionary calls and 1.616 + // scheduled spell checks are discarded when they finish. 1.617 + mDisabledAsyncToken++; 1.618 + 1.619 + if (mNumPendingUpdateCurrentDictionary > 0) { 1.620 + // Account for pending UpdateCurrentDictionary calls. 1.621 + ChangeNumPendingSpellChecks(-mNumPendingUpdateCurrentDictionary, editor); 1.622 + mNumPendingUpdateCurrentDictionary = 0; 1.623 + } 1.624 + if (mNumPendingSpellChecks > 0) { 1.625 + // If mNumPendingSpellChecks is still > 0 at this point, the remainder is 1.626 + // pending scheduled spell checks. 1.627 + ChangeNumPendingSpellChecks(-mNumPendingSpellChecks, editor); 1.628 + } 1.629 + 1.630 + mEditor = nullptr; 1.631 + mFullSpellCheckScheduled = false; 1.632 + 1.633 + return rv; 1.634 +} 1.635 + 1.636 +// mozInlineSpellChecker::CanEnableInlineSpellChecking 1.637 +// 1.638 +// This function can be called to see if it seems likely that we can enable 1.639 +// spellchecking before actually creating the InlineSpellChecking objects. 1.640 +// 1.641 +// The problem is that we can't get the dictionary list without actually 1.642 +// creating a whole bunch of spellchecking objects. This function tries to 1.643 +// do that and caches the result so we don't have to keep allocating those 1.644 +// objects if there are no dictionaries or spellchecking. 1.645 +// 1.646 +// Whenever dictionaries are added or removed at runtime, this value must be 1.647 +// updated before an observer notification is sent out about the change, to 1.648 +// avoid editors getting a wrong cached result. 1.649 + 1.650 +bool // static 1.651 +mozInlineSpellChecker::CanEnableInlineSpellChecking() 1.652 +{ 1.653 + nsresult rv; 1.654 + if (gCanEnableSpellChecking == SpellCheck_Uninitialized) { 1.655 + gCanEnableSpellChecking = SpellCheck_NotAvailable; 1.656 + 1.657 + nsCOMPtr<nsIEditorSpellCheck> spellchecker = 1.658 + do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv); 1.659 + NS_ENSURE_SUCCESS(rv, false); 1.660 + 1.661 + bool canSpellCheck = false; 1.662 + rv = spellchecker->CanSpellCheck(&canSpellCheck); 1.663 + NS_ENSURE_SUCCESS(rv, false); 1.664 + 1.665 + if (canSpellCheck) 1.666 + gCanEnableSpellChecking = SpellCheck_Available; 1.667 + } 1.668 + return (gCanEnableSpellChecking == SpellCheck_Available); 1.669 +} 1.670 + 1.671 +void // static 1.672 +mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking() 1.673 +{ 1.674 + gCanEnableSpellChecking = SpellCheck_Uninitialized; 1.675 +} 1.676 + 1.677 +// mozInlineSpellChecker::RegisterEventListeners 1.678 +// 1.679 +// The inline spell checker listens to mouse events and keyboard navigation+ // events. 1.680 + 1.681 +nsresult 1.682 +mozInlineSpellChecker::RegisterEventListeners() 1.683 +{ 1.684 + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); 1.685 + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); 1.686 + 1.687 + editor->AddEditActionListener(this); 1.688 + 1.689 + nsCOMPtr<nsIDOMDocument> doc; 1.690 + nsresult rv = editor->GetDocument(getter_AddRefs(doc)); 1.691 + NS_ENSURE_SUCCESS(rv, rv); 1.692 + 1.693 + nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc, &rv); 1.694 + NS_ENSURE_SUCCESS(rv, rv); 1.695 + 1.696 + piTarget->AddEventListener(NS_LITERAL_STRING("blur"), this, 1.697 + true, false); 1.698 + piTarget->AddEventListener(NS_LITERAL_STRING("click"), this, 1.699 + false, false); 1.700 + piTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, 1.701 + false, false); 1.702 + return NS_OK; 1.703 +} 1.704 + 1.705 +// mozInlineSpellChecker::UnregisterEventListeners 1.706 + 1.707 +nsresult 1.708 +mozInlineSpellChecker::UnregisterEventListeners() 1.709 +{ 1.710 + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); 1.711 + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); 1.712 + 1.713 + editor->RemoveEditActionListener(this); 1.714 + 1.715 + nsCOMPtr<nsIDOMDocument> doc; 1.716 + editor->GetDocument(getter_AddRefs(doc)); 1.717 + NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER); 1.718 + 1.719 + nsCOMPtr<EventTarget> piTarget = do_QueryInterface(doc); 1.720 + NS_ENSURE_TRUE(piTarget, NS_ERROR_NULL_POINTER); 1.721 + 1.722 + piTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true); 1.723 + piTarget->RemoveEventListener(NS_LITERAL_STRING("click"), this, false); 1.724 + piTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, false); 1.725 + return NS_OK; 1.726 +} 1.727 + 1.728 +// mozInlineSpellChecker::GetEnableRealTimeSpell 1.729 + 1.730 +NS_IMETHODIMP 1.731 +mozInlineSpellChecker::GetEnableRealTimeSpell(bool* aEnabled) 1.732 +{ 1.733 + NS_ENSURE_ARG_POINTER(aEnabled); 1.734 + *aEnabled = mSpellCheck != nullptr || mPendingSpellCheck != nullptr; 1.735 + return NS_OK; 1.736 +} 1.737 + 1.738 +// mozInlineSpellChecker::SetEnableRealTimeSpell 1.739 + 1.740 +NS_IMETHODIMP 1.741 +mozInlineSpellChecker::SetEnableRealTimeSpell(bool aEnabled) 1.742 +{ 1.743 + if (!aEnabled) { 1.744 + mSpellCheck = nullptr; 1.745 + return Cleanup(false); 1.746 + } 1.747 + 1.748 + if (mSpellCheck) { 1.749 + // spellcheck the current contents. SpellCheckRange doesn't supply a created 1.750 + // range to DoSpellCheck, which in our case is the entire range. But this 1.751 + // optimization doesn't matter because there is nothing in the spellcheck 1.752 + // selection when starting, which triggers a better optimization. 1.753 + return SpellCheckRange(nullptr); 1.754 + } 1.755 + 1.756 + if (mPendingSpellCheck) { 1.757 + // The editor spell checker is already being initialized. 1.758 + return NS_OK; 1.759 + } 1.760 + 1.761 + mPendingSpellCheck = 1.762 + do_CreateInstance("@mozilla.org/editor/editorspellchecker;1"); 1.763 + NS_ENSURE_STATE(mPendingSpellCheck); 1.764 + 1.765 + nsCOMPtr<nsITextServicesFilter> filter = 1.766 + do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1"); 1.767 + if (!filter) { 1.768 + mPendingSpellCheck = nullptr; 1.769 + NS_ENSURE_STATE(filter); 1.770 + } 1.771 + mPendingSpellCheck->SetFilter(filter); 1.772 + 1.773 + mPendingInitEditorSpellCheckCallback = new InitEditorSpellCheckCallback(this); 1.774 + if (!mPendingInitEditorSpellCheckCallback) { 1.775 + mPendingSpellCheck = nullptr; 1.776 + NS_ENSURE_STATE(mPendingInitEditorSpellCheckCallback); 1.777 + } 1.778 + 1.779 + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor); 1.780 + nsresult rv = mPendingSpellCheck->InitSpellChecker( 1.781 + editor, false, mPendingInitEditorSpellCheckCallback); 1.782 + if (NS_FAILED(rv)) { 1.783 + mPendingSpellCheck = nullptr; 1.784 + mPendingInitEditorSpellCheckCallback = nullptr; 1.785 + NS_ENSURE_SUCCESS(rv, rv); 1.786 + } 1.787 + 1.788 + ChangeNumPendingSpellChecks(1); 1.789 + 1.790 + return NS_OK; 1.791 +} 1.792 + 1.793 +// Called when nsIEditorSpellCheck::InitSpellChecker completes. 1.794 +nsresult 1.795 +mozInlineSpellChecker::EditorSpellCheckInited() 1.796 +{ 1.797 + NS_ASSERTION(mPendingSpellCheck, "Spell check should be pending!"); 1.798 + 1.799 + // spell checking is enabled, register our event listeners to track navigation 1.800 + RegisterEventListeners(); 1.801 + 1.802 + mSpellCheck = mPendingSpellCheck; 1.803 + mPendingSpellCheck = nullptr; 1.804 + mPendingInitEditorSpellCheckCallback = nullptr; 1.805 + ChangeNumPendingSpellChecks(-1); 1.806 + 1.807 + // spellcheck the current contents. SpellCheckRange doesn't supply a created 1.808 + // range to DoSpellCheck, which in our case is the entire range. But this 1.809 + // optimization doesn't matter because there is nothing in the spellcheck 1.810 + // selection when starting, which triggers a better optimization. 1.811 + return SpellCheckRange(nullptr); 1.812 +} 1.813 + 1.814 +// Changes the number of pending spell checks by the given delta. If the number 1.815 +// becomes zero or nonzero, observers are notified. See NotifyObservers for 1.816 +// info on the aEditor parameter. 1.817 +void 1.818 +mozInlineSpellChecker::ChangeNumPendingSpellChecks(int32_t aDelta, 1.819 + nsIEditor* aEditor) 1.820 +{ 1.821 + int8_t oldNumPending = mNumPendingSpellChecks; 1.822 + mNumPendingSpellChecks += aDelta; 1.823 + NS_ASSERTION(mNumPendingSpellChecks >= 0, 1.824 + "Unbalanced ChangeNumPendingSpellChecks calls!"); 1.825 + if (oldNumPending == 0 && mNumPendingSpellChecks > 0) { 1.826 + NotifyObservers(INLINESPELL_STARTED_TOPIC, aEditor); 1.827 + } else if (oldNumPending > 0 && mNumPendingSpellChecks == 0) { 1.828 + NotifyObservers(INLINESPELL_ENDED_TOPIC, aEditor); 1.829 + } 1.830 +} 1.831 + 1.832 +// Broadcasts the given topic to observers. aEditor is passed to observers if 1.833 +// nonnull; otherwise mEditor is passed. 1.834 +void 1.835 +mozInlineSpellChecker::NotifyObservers(const char* aTopic, nsIEditor* aEditor) 1.836 +{ 1.837 + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1.838 + if (!os) 1.839 + return; 1.840 + nsCOMPtr<nsIEditor> editor = aEditor; 1.841 + if (!editor) { 1.842 + editor = do_QueryReferent(mEditor); 1.843 + } 1.844 + os->NotifyObservers(editor, aTopic, nullptr); 1.845 +} 1.846 + 1.847 +// mozInlineSpellChecker::SpellCheckAfterEditorChange 1.848 +// 1.849 +// Called by the editor when nearly anything happens to change the content. 1.850 +// 1.851 +// The start and end positions specify a range for the thing that happened, 1.852 +// but these are usually nullptr, even when you'd think they would be useful 1.853 +// because you want the range (for example, pasting). We ignore them in 1.854 +// this case. 1.855 + 1.856 +NS_IMETHODIMP 1.857 +mozInlineSpellChecker::SpellCheckAfterEditorChange( 1.858 + int32_t aAction, nsISelection *aSelection, 1.859 + nsIDOMNode *aPreviousSelectedNode, int32_t aPreviousSelectedOffset, 1.860 + nsIDOMNode *aStartNode, int32_t aStartOffset, 1.861 + nsIDOMNode *aEndNode, int32_t aEndOffset) 1.862 +{ 1.863 + nsresult rv; 1.864 + NS_ENSURE_ARG_POINTER(aSelection); 1.865 + if (!mSpellCheck) 1.866 + return NS_OK; // disabling spell checking is not an error 1.867 + 1.868 + // this means something has changed, and we never check the current word, 1.869 + // therefore, we should spellcheck for subsequent caret navigations 1.870 + mNeedsCheckAfterNavigation = true; 1.871 + 1.872 + // the anchor node is the position of the caret 1.873 + nsCOMPtr<nsIDOMNode> anchorNode; 1.874 + rv = aSelection->GetAnchorNode(getter_AddRefs(anchorNode)); 1.875 + NS_ENSURE_SUCCESS(rv, rv); 1.876 + int32_t anchorOffset; 1.877 + rv = aSelection->GetAnchorOffset(&anchorOffset); 1.878 + NS_ENSURE_SUCCESS(rv, rv); 1.879 + 1.880 + mozInlineSpellStatus status(this); 1.881 + rv = status.InitForEditorChange((EditAction)aAction, 1.882 + anchorNode, anchorOffset, 1.883 + aPreviousSelectedNode, aPreviousSelectedOffset, 1.884 + aStartNode, aStartOffset, 1.885 + aEndNode, aEndOffset); 1.886 + NS_ENSURE_SUCCESS(rv, rv); 1.887 + rv = ScheduleSpellCheck(status); 1.888 + NS_ENSURE_SUCCESS(rv, rv); 1.889 + 1.890 + // remember the current caret position after every change 1.891 + SaveCurrentSelectionPosition(); 1.892 + return NS_OK; 1.893 +} 1.894 + 1.895 +// mozInlineSpellChecker::SpellCheckRange 1.896 +// 1.897 +// Spellchecks all the words in the given range. 1.898 +// Supply a nullptr range and this will check the entire editor. 1.899 + 1.900 +nsresult 1.901 +mozInlineSpellChecker::SpellCheckRange(nsIDOMRange* aRange) 1.902 +{ 1.903 + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); 1.904 + 1.905 + mozInlineSpellStatus status(this); 1.906 + nsRange* range = static_cast<nsRange*>(aRange); 1.907 + nsresult rv = status.InitForRange(range); 1.908 + NS_ENSURE_SUCCESS(rv, rv); 1.909 + return ScheduleSpellCheck(status); 1.910 +} 1.911 + 1.912 +// mozInlineSpellChecker::GetMisspelledWord 1.913 + 1.914 +NS_IMETHODIMP 1.915 +mozInlineSpellChecker::GetMisspelledWord(nsIDOMNode *aNode, int32_t aOffset, 1.916 + nsIDOMRange **newword) 1.917 +{ 1.918 + NS_ENSURE_ARG_POINTER(aNode); 1.919 + nsCOMPtr<nsISelection> spellCheckSelection; 1.920 + nsresult res = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); 1.921 + NS_ENSURE_SUCCESS(res, res); 1.922 + 1.923 + return IsPointInSelection(spellCheckSelection, aNode, aOffset, newword); 1.924 +} 1.925 + 1.926 +// mozInlineSpellChecker::ReplaceWord 1.927 + 1.928 +NS_IMETHODIMP 1.929 +mozInlineSpellChecker::ReplaceWord(nsIDOMNode *aNode, int32_t aOffset, 1.930 + const nsAString &newword) 1.931 +{ 1.932 + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); 1.933 + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); 1.934 + NS_ENSURE_TRUE(newword.Length() != 0, NS_ERROR_FAILURE); 1.935 + 1.936 + nsCOMPtr<nsIDOMRange> range; 1.937 + nsresult res = GetMisspelledWord(aNode, aOffset, getter_AddRefs(range)); 1.938 + NS_ENSURE_SUCCESS(res, res); 1.939 + 1.940 + if (range) 1.941 + { 1.942 + editor->BeginTransaction(); 1.943 + 1.944 + nsCOMPtr<nsISelection> selection; 1.945 + res = editor->GetSelection(getter_AddRefs(selection)); 1.946 + NS_ENSURE_SUCCESS(res, res); 1.947 + selection->RemoveAllRanges(); 1.948 + selection->AddRange(range); 1.949 + editor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); 1.950 + 1.951 + nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryReferent(mEditor)); 1.952 + if (textEditor) 1.953 + textEditor->InsertText(newword); 1.954 + 1.955 + editor->EndTransaction(); 1.956 + } 1.957 + 1.958 + return NS_OK; 1.959 +} 1.960 + 1.961 +// mozInlineSpellChecker::AddWordToDictionary 1.962 + 1.963 +NS_IMETHODIMP 1.964 +mozInlineSpellChecker::AddWordToDictionary(const nsAString &word) 1.965 +{ 1.966 + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); 1.967 + 1.968 + nsAutoString wordstr(word); 1.969 + nsresult rv = mSpellCheck->AddWordToDictionary(wordstr.get()); 1.970 + NS_ENSURE_SUCCESS(rv, rv); 1.971 + 1.972 + mozInlineSpellStatus status(this); 1.973 + rv = status.InitForSelection(); 1.974 + NS_ENSURE_SUCCESS(rv, rv); 1.975 + return ScheduleSpellCheck(status); 1.976 +} 1.977 + 1.978 +// mozInlineSpellChecker::RemoveWordFromDictionary 1.979 + 1.980 +NS_IMETHODIMP 1.981 +mozInlineSpellChecker::RemoveWordFromDictionary(const nsAString &word) 1.982 +{ 1.983 + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); 1.984 + 1.985 + nsAutoString wordstr(word); 1.986 + nsresult rv = mSpellCheck->RemoveWordFromDictionary(wordstr.get()); 1.987 + NS_ENSURE_SUCCESS(rv, rv); 1.988 + 1.989 + mozInlineSpellStatus status(this); 1.990 + rv = status.InitForRange(nullptr); 1.991 + NS_ENSURE_SUCCESS(rv, rv); 1.992 + return ScheduleSpellCheck(status); 1.993 +} 1.994 + 1.995 +// mozInlineSpellChecker::IgnoreWord 1.996 + 1.997 +NS_IMETHODIMP 1.998 +mozInlineSpellChecker::IgnoreWord(const nsAString &word) 1.999 +{ 1.1000 + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); 1.1001 + 1.1002 + nsAutoString wordstr(word); 1.1003 + nsresult rv = mSpellCheck->IgnoreWordAllOccurrences(wordstr.get()); 1.1004 + NS_ENSURE_SUCCESS(rv, rv); 1.1005 + 1.1006 + mozInlineSpellStatus status(this); 1.1007 + rv = status.InitForSelection(); 1.1008 + NS_ENSURE_SUCCESS(rv, rv); 1.1009 + return ScheduleSpellCheck(status); 1.1010 +} 1.1011 + 1.1012 +// mozInlineSpellChecker::IgnoreWords 1.1013 + 1.1014 +NS_IMETHODIMP 1.1015 +mozInlineSpellChecker::IgnoreWords(const char16_t **aWordsToIgnore, 1.1016 + uint32_t aCount) 1.1017 +{ 1.1018 + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); 1.1019 + 1.1020 + // add each word to the ignore list and then recheck the document 1.1021 + for (uint32_t index = 0; index < aCount; index++) 1.1022 + mSpellCheck->IgnoreWordAllOccurrences(aWordsToIgnore[index]); 1.1023 + 1.1024 + mozInlineSpellStatus status(this); 1.1025 + nsresult rv = status.InitForSelection(); 1.1026 + NS_ENSURE_SUCCESS(rv, rv); 1.1027 + return ScheduleSpellCheck(status); 1.1028 +} 1.1029 + 1.1030 +NS_IMETHODIMP mozInlineSpellChecker::WillCreateNode(const nsAString & aTag, nsIDOMNode *aParent, int32_t aPosition) 1.1031 +{ 1.1032 + return NS_OK; 1.1033 +} 1.1034 + 1.1035 +NS_IMETHODIMP mozInlineSpellChecker::DidCreateNode(const nsAString & aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, 1.1036 + int32_t aPosition, nsresult aResult) 1.1037 +{ 1.1038 + return NS_OK; 1.1039 +} 1.1040 + 1.1041 +NS_IMETHODIMP mozInlineSpellChecker::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, 1.1042 + int32_t aPosition) 1.1043 +{ 1.1044 + return NS_OK; 1.1045 +} 1.1046 + 1.1047 +NS_IMETHODIMP mozInlineSpellChecker::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, 1.1048 + int32_t aPosition, nsresult aResult) 1.1049 +{ 1.1050 + 1.1051 + return NS_OK; 1.1052 +} 1.1053 + 1.1054 +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteNode(nsIDOMNode *aChild) 1.1055 +{ 1.1056 + return NS_OK; 1.1057 +} 1.1058 + 1.1059 +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) 1.1060 +{ 1.1061 + return NS_OK; 1.1062 +} 1.1063 + 1.1064 +NS_IMETHODIMP mozInlineSpellChecker::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset) 1.1065 +{ 1.1066 + return NS_OK; 1.1067 +} 1.1068 + 1.1069 +NS_IMETHODIMP 1.1070 +mozInlineSpellChecker::DidSplitNode(nsIDOMNode *aExistingRightNode, 1.1071 + int32_t aOffset, 1.1072 + nsIDOMNode *aNewLeftNode, nsresult aResult) 1.1073 +{ 1.1074 + return SpellCheckBetweenNodes(aNewLeftNode, 0, aNewLeftNode, 0); 1.1075 +} 1.1076 + 1.1077 +NS_IMETHODIMP mozInlineSpellChecker::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) 1.1078 +{ 1.1079 + return NS_OK; 1.1080 +} 1.1081 + 1.1082 +NS_IMETHODIMP mozInlineSpellChecker::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, 1.1083 + nsIDOMNode *aParent, nsresult aResult) 1.1084 +{ 1.1085 + return SpellCheckBetweenNodes(aRightNode, 0, aRightNode, 0); 1.1086 +} 1.1087 + 1.1088 +NS_IMETHODIMP mozInlineSpellChecker::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString & aString) 1.1089 +{ 1.1090 + return NS_OK; 1.1091 +} 1.1092 + 1.1093 +NS_IMETHODIMP mozInlineSpellChecker::DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, 1.1094 + const nsAString & aString, nsresult aResult) 1.1095 +{ 1.1096 + return NS_OK; 1.1097 +} 1.1098 + 1.1099 +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) 1.1100 +{ 1.1101 + return NS_OK; 1.1102 +} 1.1103 + 1.1104 +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult) 1.1105 +{ 1.1106 + return NS_OK; 1.1107 +} 1.1108 + 1.1109 +NS_IMETHODIMP mozInlineSpellChecker::WillDeleteSelection(nsISelection *aSelection) 1.1110 +{ 1.1111 + return NS_OK; 1.1112 +} 1.1113 + 1.1114 +NS_IMETHODIMP mozInlineSpellChecker::DidDeleteSelection(nsISelection *aSelection) 1.1115 +{ 1.1116 + return NS_OK; 1.1117 +} 1.1118 + 1.1119 +// mozInlineSpellChecker::MakeSpellCheckRange 1.1120 +// 1.1121 +// Given begin and end positions, this function constructs a range as 1.1122 +// required for ScheduleSpellCheck. If the start and end nodes are nullptr, 1.1123 +// then the entire range will be selected, and you can supply -1 as the 1.1124 +// offset to the end range to select all of that node. 1.1125 +// 1.1126 +// If the resulting range would be empty, nullptr is put into *aRange and the 1.1127 +// function succeeds. 1.1128 + 1.1129 +nsresult 1.1130 +mozInlineSpellChecker::MakeSpellCheckRange( 1.1131 + nsIDOMNode* aStartNode, int32_t aStartOffset, 1.1132 + nsIDOMNode* aEndNode, int32_t aEndOffset, 1.1133 + nsRange** aRange) 1.1134 +{ 1.1135 + nsresult rv; 1.1136 + *aRange = nullptr; 1.1137 + 1.1138 + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); 1.1139 + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); 1.1140 + 1.1141 + nsCOMPtr<nsIDOMDocument> doc; 1.1142 + rv = editor->GetDocument(getter_AddRefs(doc)); 1.1143 + NS_ENSURE_SUCCESS(rv, rv); 1.1144 + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); 1.1145 + 1.1146 + nsCOMPtr<nsIDOMRange> range; 1.1147 + rv = doc->CreateRange(getter_AddRefs(range)); 1.1148 + NS_ENSURE_SUCCESS(rv, rv); 1.1149 + 1.1150 + // possibly use full range of the editor 1.1151 + nsCOMPtr<nsIDOMElement> rootElem; 1.1152 + if (! aStartNode || ! aEndNode) { 1.1153 + rv = editor->GetRootElement(getter_AddRefs(rootElem)); 1.1154 + NS_ENSURE_SUCCESS(rv, rv); 1.1155 + 1.1156 + aStartNode = rootElem; 1.1157 + aStartOffset = 0; 1.1158 + 1.1159 + aEndNode = rootElem; 1.1160 + aEndOffset = -1; 1.1161 + } 1.1162 + 1.1163 + if (aEndOffset == -1) { 1.1164 + nsCOMPtr<nsIDOMNodeList> childNodes; 1.1165 + rv = aEndNode->GetChildNodes(getter_AddRefs(childNodes)); 1.1166 + NS_ENSURE_SUCCESS(rv, rv); 1.1167 + 1.1168 + uint32_t childCount; 1.1169 + rv = childNodes->GetLength(&childCount); 1.1170 + NS_ENSURE_SUCCESS(rv, rv); 1.1171 + 1.1172 + aEndOffset = childCount; 1.1173 + } 1.1174 + 1.1175 + // sometimes we are are requested to check an empty range (possibly an empty 1.1176 + // document). This will result in assertions later. 1.1177 + if (aStartNode == aEndNode && aStartOffset == aEndOffset) 1.1178 + return NS_OK; 1.1179 + 1.1180 + rv = range->SetStart(aStartNode, aStartOffset); 1.1181 + NS_ENSURE_SUCCESS(rv, rv); 1.1182 + if (aEndOffset) 1.1183 + rv = range->SetEnd(aEndNode, aEndOffset); 1.1184 + else 1.1185 + rv = range->SetEndAfter(aEndNode); 1.1186 + NS_ENSURE_SUCCESS(rv, rv); 1.1187 + 1.1188 + *aRange = static_cast<nsRange*>(range.forget().take()); 1.1189 + return NS_OK; 1.1190 +} 1.1191 + 1.1192 +nsresult 1.1193 +mozInlineSpellChecker::SpellCheckBetweenNodes(nsIDOMNode *aStartNode, 1.1194 + int32_t aStartOffset, 1.1195 + nsIDOMNode *aEndNode, 1.1196 + int32_t aEndOffset) 1.1197 +{ 1.1198 + nsRefPtr<nsRange> range; 1.1199 + nsresult rv = MakeSpellCheckRange(aStartNode, aStartOffset, 1.1200 + aEndNode, aEndOffset, 1.1201 + getter_AddRefs(range)); 1.1202 + NS_ENSURE_SUCCESS(rv, rv); 1.1203 + 1.1204 + if (! range) 1.1205 + return NS_OK; // range is empty: nothing to do 1.1206 + 1.1207 + mozInlineSpellStatus status(this); 1.1208 + rv = status.InitForRange(range); 1.1209 + NS_ENSURE_SUCCESS(rv, rv); 1.1210 + return ScheduleSpellCheck(status); 1.1211 +} 1.1212 + 1.1213 +// mozInlineSpellChecker::SkipSpellCheckForNode 1.1214 +// 1.1215 +// There are certain conditions when we don't want to spell check a node. In 1.1216 +// particular quotations, moz signatures, etc. This routine returns false 1.1217 +// for these cases. 1.1218 + 1.1219 +nsresult 1.1220 +mozInlineSpellChecker::SkipSpellCheckForNode(nsIEditor* aEditor, 1.1221 + nsIDOMNode *aNode, 1.1222 + bool *checkSpelling) 1.1223 +{ 1.1224 + *checkSpelling = true; 1.1225 + NS_ENSURE_ARG_POINTER(aNode); 1.1226 + 1.1227 + uint32_t flags; 1.1228 + aEditor->GetFlags(&flags); 1.1229 + if (flags & nsIPlaintextEditor::eEditorMailMask) 1.1230 + { 1.1231 + nsCOMPtr<nsIDOMNode> parent; 1.1232 + aNode->GetParentNode(getter_AddRefs(parent)); 1.1233 + 1.1234 + while (parent) 1.1235 + { 1.1236 + nsCOMPtr<nsIDOMElement> parentElement = do_QueryInterface(parent); 1.1237 + if (!parentElement) 1.1238 + break; 1.1239 + 1.1240 + nsAutoString parentTagName; 1.1241 + parentElement->GetTagName(parentTagName); 1.1242 + 1.1243 + if (parentTagName.Equals(NS_LITERAL_STRING("blockquote"), nsCaseInsensitiveStringComparator())) 1.1244 + { 1.1245 + nsAutoString quotetype; 1.1246 + parentElement->GetAttribute(NS_LITERAL_STRING("type"), quotetype); 1.1247 + if (quotetype.Equals(NS_LITERAL_STRING("cite"), nsCaseInsensitiveStringComparator())) 1.1248 + { 1.1249 + *checkSpelling = false; 1.1250 + break; 1.1251 + } 1.1252 + } 1.1253 + else if (parentTagName.Equals(NS_LITERAL_STRING("pre"), nsCaseInsensitiveStringComparator())) 1.1254 + { 1.1255 + nsAutoString classname; 1.1256 + parentElement->GetAttribute(NS_LITERAL_STRING("class"),classname); 1.1257 + if (classname.Equals(NS_LITERAL_STRING("moz-signature"))) 1.1258 + *checkSpelling = false; 1.1259 + } 1.1260 + 1.1261 + nsCOMPtr<nsIDOMNode> nextParent; 1.1262 + parent->GetParentNode(getter_AddRefs(nextParent)); 1.1263 + parent = nextParent; 1.1264 + } 1.1265 + } 1.1266 + else { 1.1267 + // Check spelling only if the node is editable, and GetSpellcheck() is true 1.1268 + // on the nearest HTMLElement ancestor. 1.1269 + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); 1.1270 + if (!content->IsEditable()) { 1.1271 + *checkSpelling = false; 1.1272 + return NS_OK; 1.1273 + } 1.1274 + 1.1275 + // Make sure that we can always turn on spell checking for inputs/textareas. 1.1276 + // Note that because of the previous check, at this point we know that the 1.1277 + // node is editable. 1.1278 + if (content->IsInAnonymousSubtree()) { 1.1279 + nsCOMPtr<nsIContent> node = content->GetParent(); 1.1280 + while (node && node->IsInNativeAnonymousSubtree()) { 1.1281 + node = node->GetParent(); 1.1282 + } 1.1283 + nsCOMPtr<nsITextControlElement> textControl = do_QueryInterface(node); 1.1284 + if (textControl) { 1.1285 + *checkSpelling = true; 1.1286 + return NS_OK; 1.1287 + } 1.1288 + } 1.1289 + 1.1290 + // Get HTML element ancestor (might be aNode itself, although probably that 1.1291 + // has to be a text node in real life here) 1.1292 + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(content); 1.1293 + while (content && !htmlElement) { 1.1294 + content = content->GetParent(); 1.1295 + htmlElement = do_QueryInterface(content); 1.1296 + } 1.1297 + NS_ASSERTION(htmlElement, "Why do we have no htmlElement?"); 1.1298 + if (!htmlElement) { 1.1299 + return NS_OK; 1.1300 + } 1.1301 + 1.1302 + // See if it's spellcheckable 1.1303 + htmlElement->GetSpellcheck(checkSpelling); 1.1304 + return NS_OK; 1.1305 + } 1.1306 + 1.1307 + return NS_OK; 1.1308 +} 1.1309 + 1.1310 +// mozInlineSpellChecker::ScheduleSpellCheck 1.1311 +// 1.1312 +// This is called by code to do the actual spellchecking. We will set up 1.1313 +// the proper structures for calls to DoSpellCheck. 1.1314 + 1.1315 +nsresult 1.1316 +mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus& aStatus) 1.1317 +{ 1.1318 + if (mFullSpellCheckScheduled) { 1.1319 + // Just ignore this; we're going to spell-check everything anyway 1.1320 + return NS_OK; 1.1321 + } 1.1322 + 1.1323 + mozInlineSpellResume* resume = 1.1324 + new mozInlineSpellResume(aStatus, mDisabledAsyncToken); 1.1325 + NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY); 1.1326 + 1.1327 + nsresult rv = resume->Post(); 1.1328 + if (NS_FAILED(rv)) { 1.1329 + delete resume; 1.1330 + } else { 1.1331 + if (aStatus.IsFullSpellCheck()) { 1.1332 + // We're going to check everything. Suppress further spell-check attempts 1.1333 + // until that happens. 1.1334 + mFullSpellCheckScheduled = true; 1.1335 + } 1.1336 + ChangeNumPendingSpellChecks(1); 1.1337 + } 1.1338 + return rv; 1.1339 +} 1.1340 + 1.1341 +// mozInlineSpellChecker::DoSpellCheckSelection 1.1342 +// 1.1343 +// Called to re-check all misspelled words. We iterate over all ranges in 1.1344 +// the selection and call DoSpellCheck on them. This is used when a word 1.1345 +// is ignored or added to the dictionary: all instances of that word should 1.1346 +// be removed from the selection. 1.1347 +// 1.1348 +// FIXME-PERFORMANCE: This takes as long as it takes and is not resumable. 1.1349 +// Typically, checking this small amount of text is relatively fast, but 1.1350 +// for large numbers of words, a lag may be noticeable. 1.1351 + 1.1352 +nsresult 1.1353 +mozInlineSpellChecker::DoSpellCheckSelection(mozInlineSpellWordUtil& aWordUtil, 1.1354 + nsISelection* aSpellCheckSelection, 1.1355 + mozInlineSpellStatus* aStatus) 1.1356 +{ 1.1357 + nsresult rv; 1.1358 + 1.1359 + // clear out mNumWordsInSpellSelection since we'll be rebuilding the ranges. 1.1360 + mNumWordsInSpellSelection = 0; 1.1361 + 1.1362 + // Since we could be modifying the ranges for the spellCheckSelection while 1.1363 + // looping on the spell check selection, keep a separate array of range 1.1364 + // elements inside the selection 1.1365 + nsCOMArray<nsIDOMRange> ranges; 1.1366 + 1.1367 + int32_t count; 1.1368 + aSpellCheckSelection->GetRangeCount(&count); 1.1369 + 1.1370 + int32_t idx; 1.1371 + nsCOMPtr<nsIDOMRange> checkRange; 1.1372 + for (idx = 0; idx < count; idx ++) { 1.1373 + aSpellCheckSelection->GetRangeAt(idx, getter_AddRefs(checkRange)); 1.1374 + if (checkRange) { 1.1375 + if (! ranges.AppendObject(checkRange)) 1.1376 + return NS_ERROR_OUT_OF_MEMORY; 1.1377 + } 1.1378 + } 1.1379 + 1.1380 + // We have saved the ranges above. Clearing the spellcheck selection here 1.1381 + // isn't necessary (rechecking each word will modify it as necessary) but 1.1382 + // provides better performance. By ensuring that no ranges need to be 1.1383 + // removed in DoSpellCheck, we can save checking range inclusion which is 1.1384 + // slow. 1.1385 + aSpellCheckSelection->RemoveAllRanges(); 1.1386 + 1.1387 + // We use this state object for all calls, and just update its range. Note 1.1388 + // that we don't need to call FinishInit since we will be filling in the 1.1389 + // necessary information. 1.1390 + mozInlineSpellStatus status(this); 1.1391 + rv = status.InitForRange(nullptr); 1.1392 + NS_ENSURE_SUCCESS(rv, rv); 1.1393 + 1.1394 + bool doneChecking; 1.1395 + for (idx = 0; idx < count; idx ++) { 1.1396 + checkRange = ranges[idx]; 1.1397 + if (checkRange) { 1.1398 + // We can consider this word as "added" since we know it has no spell 1.1399 + // check range over it that needs to be deleted. All the old ranges 1.1400 + // were cleared above. We also need to clear the word count so that we 1.1401 + // check all words instead of stopping early. 1.1402 + status.mRange = static_cast<nsRange*>(checkRange.get()); 1.1403 + rv = DoSpellCheck(aWordUtil, aSpellCheckSelection, &status, 1.1404 + &doneChecking); 1.1405 + NS_ENSURE_SUCCESS(rv, rv); 1.1406 + NS_ASSERTION(doneChecking, "We gave the spellchecker one word, but it didn't finish checking?!?!"); 1.1407 + 1.1408 + status.mWordCount = 0; 1.1409 + } 1.1410 + } 1.1411 + 1.1412 + return NS_OK; 1.1413 +} 1.1414 + 1.1415 +// mozInlineSpellChecker::DoSpellCheck 1.1416 +// 1.1417 +// This function checks words intersecting the given range, excluding those 1.1418 +// inside mStatus->mNoCheckRange (can be nullptr). Words inside aNoCheckRange 1.1419 +// will have any spell selection removed (this is used to hide the 1.1420 +// underlining for the word that the caret is in). aNoCheckRange should be 1.1421 +// on word boundaries. 1.1422 +// 1.1423 +// mResume->mCreatedRange is a possibly nullptr range of new text that was 1.1424 +// inserted. Inside this range, we don't bother to check whether things are 1.1425 +// inside the spellcheck selection, which speeds up large paste operations 1.1426 +// considerably. 1.1427 +// 1.1428 +// Normal case when editing text by typing 1.1429 +// h e l l o w o r k d h o w a r e y o u 1.1430 +// ^ caret 1.1431 +// [-------] mRange 1.1432 +// [-------] mNoCheckRange 1.1433 +// -> does nothing (range is the same as the no check range) 1.1434 +// 1.1435 +// Case when pasting: 1.1436 +// [---------- pasted text ----------] 1.1437 +// h e l l o w o r k d h o w a r e y o u 1.1438 +// ^ caret 1.1439 +// [---] aNoCheckRange 1.1440 +// -> recheck all words in range except those in aNoCheckRange 1.1441 +// 1.1442 +// If checking is complete, *aDoneChecking will be set. If there is more 1.1443 +// but we ran out of time, this will be false and the range will be 1.1444 +// updated with the stuff that still needs checking. 1.1445 + 1.1446 +nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil, 1.1447 + nsISelection *aSpellCheckSelection, 1.1448 + mozInlineSpellStatus* aStatus, 1.1449 + bool* aDoneChecking) 1.1450 +{ 1.1451 + *aDoneChecking = true; 1.1452 + 1.1453 + NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED); 1.1454 + 1.1455 + // get the editor for SkipSpellCheckForNode, this may fail in reasonable 1.1456 + // circumstances since the editor could have gone away 1.1457 + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); 1.1458 + if (! editor) 1.1459 + return NS_ERROR_FAILURE; 1.1460 + 1.1461 + bool iscollapsed; 1.1462 + nsresult rv = aStatus->mRange->GetCollapsed(&iscollapsed); 1.1463 + NS_ENSURE_SUCCESS(rv, rv); 1.1464 + if (iscollapsed) 1.1465 + return NS_OK; 1.1466 + 1.1467 + nsCOMPtr<nsISelectionPrivate> privSel = do_QueryInterface(aSpellCheckSelection); 1.1468 + 1.1469 + // see if the selection has any ranges, if not, then we can optimize checking 1.1470 + // range inclusion later (we have no ranges when we are initially checking or 1.1471 + // when there are no misspelled words yet). 1.1472 + int32_t originalRangeCount; 1.1473 + rv = aSpellCheckSelection->GetRangeCount(&originalRangeCount); 1.1474 + NS_ENSURE_SUCCESS(rv, rv); 1.1475 + 1.1476 + // set the starting DOM position to be the beginning of our range 1.1477 + { 1.1478 + // Scope for the node/offset pairs here so they don't get 1.1479 + // accidentally used later 1.1480 + nsINode* beginNode = aStatus->mRange->GetStartParent(); 1.1481 + int32_t beginOffset = aStatus->mRange->StartOffset(); 1.1482 + nsINode* endNode = aStatus->mRange->GetEndParent(); 1.1483 + int32_t endOffset = aStatus->mRange->EndOffset(); 1.1484 + 1.1485 + // Now check that we're still looking at a range that's under 1.1486 + // aWordUtil.GetRootNode() 1.1487 + nsINode* rootNode = aWordUtil.GetRootNode(); 1.1488 + if (!nsContentUtils::ContentIsDescendantOf(beginNode, rootNode) || 1.1489 + !nsContentUtils::ContentIsDescendantOf(endNode, rootNode)) { 1.1490 + // Just bail out and don't try to spell-check this 1.1491 + return NS_OK; 1.1492 + } 1.1493 + 1.1494 + aWordUtil.SetEnd(endNode, endOffset); 1.1495 + aWordUtil.SetPosition(beginNode, beginOffset); 1.1496 + } 1.1497 + 1.1498 + // aWordUtil.SetPosition flushes pending notifications, check editor again. 1.1499 + editor = do_QueryReferent(mEditor); 1.1500 + if (! editor) 1.1501 + return NS_ERROR_FAILURE; 1.1502 + 1.1503 + int32_t wordsSinceTimeCheck = 0; 1.1504 + PRTime beginTime = PR_Now(); 1.1505 + 1.1506 + nsAutoString wordText; 1.1507 + nsRefPtr<nsRange> wordRange; 1.1508 + bool dontCheckWord; 1.1509 + while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText, 1.1510 + getter_AddRefs(wordRange), 1.1511 + &dontCheckWord)) && 1.1512 + wordRange) { 1.1513 + wordsSinceTimeCheck ++; 1.1514 + 1.1515 + // get the range for the current word. 1.1516 + // Not using nsINode here for now because we have to call into 1.1517 + // selection APIs that use nsIDOMNode. :( 1.1518 + nsCOMPtr<nsIDOMNode> beginNode, endNode; 1.1519 + int32_t beginOffset, endOffset; 1.1520 + wordRange->GetStartContainer(getter_AddRefs(beginNode)); 1.1521 + wordRange->GetEndContainer(getter_AddRefs(endNode)); 1.1522 + wordRange->GetStartOffset(&beginOffset); 1.1523 + wordRange->GetEndOffset(&endOffset); 1.1524 + 1.1525 +#ifdef DEBUG_INLINESPELL 1.1526 + printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText).get()); 1.1527 + if (dontCheckWord) 1.1528 + printf(" (not checking)"); 1.1529 + printf("\n"); 1.1530 +#endif 1.1531 + 1.1532 + // see if there is a spellcheck range that already intersects the word 1.1533 + // and remove it. We only need to remove old ranges, so don't bother if 1.1534 + // there were no ranges when we started out. 1.1535 + if (originalRangeCount > 0) { 1.1536 + // likewise, if this word is inside new text, we won't bother testing 1.1537 + bool inCreatedRange = false; 1.1538 + if (aStatus->mCreatedRange) 1.1539 + aStatus->mCreatedRange->IsPointInRange(beginNode, beginOffset, &inCreatedRange); 1.1540 + if (! inCreatedRange) { 1.1541 + nsTArray<nsRange*> ranges; 1.1542 + nsCOMPtr<nsINode> firstNode = do_QueryInterface(beginNode); 1.1543 + nsCOMPtr<nsINode> lastNode = do_QueryInterface(endNode); 1.1544 + rv = privSel->GetRangesForIntervalArray(firstNode, beginOffset, 1.1545 + lastNode, endOffset, 1.1546 + true, &ranges); 1.1547 + NS_ENSURE_SUCCESS(rv, rv); 1.1548 + for (uint32_t i = 0; i < ranges.Length(); i++) 1.1549 + RemoveRange(aSpellCheckSelection, ranges[i]); 1.1550 + } 1.1551 + } 1.1552 + 1.1553 + // some words are special and don't need checking 1.1554 + if (dontCheckWord) 1.1555 + continue; 1.1556 + 1.1557 + // some nodes we don't spellcheck 1.1558 + bool checkSpelling; 1.1559 + rv = SkipSpellCheckForNode(editor, beginNode, &checkSpelling); 1.1560 + NS_ENSURE_SUCCESS(rv, rv); 1.1561 + if (!checkSpelling) 1.1562 + continue; 1.1563 + 1.1564 + // Don't check spelling if we're inside the noCheckRange. This needs to 1.1565 + // be done after we clear any old selection because the excluded word 1.1566 + // might have been previously marked. 1.1567 + // 1.1568 + // We do a simple check to see if the beginning of our word is in the 1.1569 + // exclusion range. Because the exclusion range is a multiple of a word, 1.1570 + // this is sufficient. 1.1571 + if (aStatus->mNoCheckRange) { 1.1572 + bool inExclusion = false; 1.1573 + aStatus->mNoCheckRange->IsPointInRange(beginNode, beginOffset, 1.1574 + &inExclusion); 1.1575 + if (inExclusion) 1.1576 + continue; 1.1577 + } 1.1578 + 1.1579 + // check spelling and add to selection if misspelled 1.1580 + bool isMisspelled; 1.1581 + aWordUtil.NormalizeWord(wordText); 1.1582 + rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText.get(), &isMisspelled); 1.1583 + if (NS_FAILED(rv)) 1.1584 + continue; 1.1585 + 1.1586 + if (isMisspelled) { 1.1587 + // misspelled words count extra toward the max 1.1588 + wordsSinceTimeCheck += MISSPELLED_WORD_COUNT_PENALTY; 1.1589 + AddRange(aSpellCheckSelection, wordRange); 1.1590 + 1.1591 + aStatus->mWordCount ++; 1.1592 + if (aStatus->mWordCount >= mMaxMisspellingsPerCheck || 1.1593 + SpellCheckSelectionIsFull()) 1.1594 + break; 1.1595 + } 1.1596 + 1.1597 + // see if we've run out of time, only check every N words for perf 1.1598 + if (wordsSinceTimeCheck >= INLINESPELL_TIMEOUT_CHECK_FREQUENCY) { 1.1599 + wordsSinceTimeCheck = 0; 1.1600 + if (PR_Now() > PRTime(beginTime + INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC)) { 1.1601 + // stop checking, our time limit has been exceeded 1.1602 + 1.1603 + // move the range to encompass the stuff that needs checking 1.1604 + rv = aStatus->mRange->SetStart(endNode, endOffset); 1.1605 + if (NS_FAILED(rv)) { 1.1606 + // The range might be unhappy because the beginning is after the 1.1607 + // end. This is possible when the requested end was in the middle 1.1608 + // of a word, just ignore this situation and assume we're done. 1.1609 + return NS_OK; 1.1610 + } 1.1611 + *aDoneChecking = false; 1.1612 + return NS_OK; 1.1613 + } 1.1614 + } 1.1615 + } 1.1616 + 1.1617 + return NS_OK; 1.1618 +} 1.1619 + 1.1620 +// An RAII helper that calls ChangeNumPendingSpellChecks on destruction. 1.1621 +class AutoChangeNumPendingSpellChecks 1.1622 +{ 1.1623 +public: 1.1624 + AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker, 1.1625 + int32_t aDelta) 1.1626 + : mSpellChecker(aSpellChecker), mDelta(aDelta) {} 1.1627 + 1.1628 + ~AutoChangeNumPendingSpellChecks() 1.1629 + { 1.1630 + mSpellChecker->ChangeNumPendingSpellChecks(mDelta); 1.1631 + } 1.1632 + 1.1633 +private: 1.1634 + nsRefPtr<mozInlineSpellChecker> mSpellChecker; 1.1635 + int32_t mDelta; 1.1636 +}; 1.1637 + 1.1638 +// mozInlineSpellChecker::ResumeCheck 1.1639 +// 1.1640 +// Called by the resume event when it fires. We will try to pick up where 1.1641 +// the last resume left off. 1.1642 + 1.1643 +nsresult 1.1644 +mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus) 1.1645 +{ 1.1646 + // Observers should be notified that spell check has ended only after spell 1.1647 + // check is done below, but since there are many early returns in this method 1.1648 + // and the number of pending spell checks must be decremented regardless of 1.1649 + // whether the spell check actually happens, use this RAII object. 1.1650 + AutoChangeNumPendingSpellChecks autoChangeNumPending(this, -1); 1.1651 + 1.1652 + if (aStatus->IsFullSpellCheck()) { 1.1653 + // Allow posting new spellcheck resume events from inside 1.1654 + // ResumeCheck, now that we're actually firing. 1.1655 + NS_ASSERTION(mFullSpellCheckScheduled, 1.1656 + "How could this be false? The full spell check is " 1.1657 + "calling us!!"); 1.1658 + mFullSpellCheckScheduled = false; 1.1659 + } 1.1660 + 1.1661 + if (! mSpellCheck) 1.1662 + return NS_OK; // spell checking has been turned off 1.1663 + 1.1664 + nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor); 1.1665 + if (! editor) 1.1666 + return NS_OK; // editor is gone 1.1667 + 1.1668 + mozInlineSpellWordUtil wordUtil; 1.1669 + nsresult rv = wordUtil.Init(mEditor); 1.1670 + if (NS_FAILED(rv)) 1.1671 + return NS_OK; // editor doesn't like us, don't assert 1.1672 + 1.1673 + nsCOMPtr<nsISelection> spellCheckSelection; 1.1674 + rv = GetSpellCheckSelection(getter_AddRefs(spellCheckSelection)); 1.1675 + NS_ENSURE_SUCCESS(rv, rv); 1.1676 + 1.1677 + nsAutoString currentDictionary; 1.1678 + rv = mSpellCheck->GetCurrentDictionary(currentDictionary); 1.1679 + if (NS_FAILED(rv)) { 1.1680 + // no active dictionary 1.1681 + int32_t count; 1.1682 + spellCheckSelection->GetRangeCount(&count); 1.1683 + for (int32_t index = count - 1; index >= 0; index--) { 1.1684 + nsCOMPtr<nsIDOMRange> checkRange; 1.1685 + spellCheckSelection->GetRangeAt(index, getter_AddRefs(checkRange)); 1.1686 + if (checkRange) { 1.1687 + RemoveRange(spellCheckSelection, checkRange); 1.1688 + } 1.1689 + } 1.1690 + return NS_OK; 1.1691 + } 1.1692 + 1.1693 + CleanupRangesInSelection(spellCheckSelection); 1.1694 + 1.1695 + rv = aStatus->FinishInitOnEvent(wordUtil); 1.1696 + NS_ENSURE_SUCCESS(rv, rv); 1.1697 + if (! aStatus->mRange) 1.1698 + return NS_OK; // empty range, nothing to do 1.1699 + 1.1700 + bool doneChecking = true; 1.1701 + if (aStatus->mOp == mozInlineSpellStatus::eOpSelection) 1.1702 + rv = DoSpellCheckSelection(wordUtil, spellCheckSelection, aStatus); 1.1703 + else 1.1704 + rv = DoSpellCheck(wordUtil, spellCheckSelection, aStatus, &doneChecking); 1.1705 + NS_ENSURE_SUCCESS(rv, rv); 1.1706 + 1.1707 + if (! doneChecking) 1.1708 + rv = ScheduleSpellCheck(*aStatus); 1.1709 + return rv; 1.1710 +} 1.1711 + 1.1712 +// mozInlineSpellChecker::IsPointInSelection 1.1713 +// 1.1714 +// Determines if a given (node,offset) point is inside the given 1.1715 +// selection. If so, the specific range of the selection that 1.1716 +// intersects is places in *aRange. (There may be multiple disjoint 1.1717 +// ranges in a selection.) 1.1718 +// 1.1719 +// If there is no intersection, *aRange will be nullptr. 1.1720 + 1.1721 +nsresult 1.1722 +mozInlineSpellChecker::IsPointInSelection(nsISelection *aSelection, 1.1723 + nsIDOMNode *aNode, 1.1724 + int32_t aOffset, 1.1725 + nsIDOMRange **aRange) 1.1726 +{ 1.1727 + *aRange = nullptr; 1.1728 + 1.1729 + nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection)); 1.1730 + 1.1731 + nsTArray<nsRange*> ranges; 1.1732 + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); 1.1733 + nsresult rv = privSel->GetRangesForIntervalArray(node, aOffset, node, aOffset, 1.1734 + true, &ranges); 1.1735 + NS_ENSURE_SUCCESS(rv, rv); 1.1736 + 1.1737 + if (ranges.Length() == 0) 1.1738 + return NS_OK; // no matches 1.1739 + 1.1740 + // there may be more than one range returned, and we don't know what do 1.1741 + // do with that, so just get the first one 1.1742 + NS_ADDREF(*aRange = ranges[0]); 1.1743 + return NS_OK; 1.1744 +} 1.1745 + 1.1746 +nsresult 1.1747 +mozInlineSpellChecker::CleanupRangesInSelection(nsISelection *aSelection) 1.1748 +{ 1.1749 + // integrity check - remove ranges that have collapsed to nothing. This 1.1750 + // can happen if the node containing a highlighted word was removed. 1.1751 + NS_ENSURE_ARG_POINTER(aSelection); 1.1752 + 1.1753 + int32_t count; 1.1754 + aSelection->GetRangeCount(&count); 1.1755 + 1.1756 + for (int32_t index = 0; index < count; index++) 1.1757 + { 1.1758 + nsCOMPtr<nsIDOMRange> checkRange; 1.1759 + aSelection->GetRangeAt(index, getter_AddRefs(checkRange)); 1.1760 + 1.1761 + if (checkRange) 1.1762 + { 1.1763 + bool collapsed; 1.1764 + checkRange->GetCollapsed(&collapsed); 1.1765 + if (collapsed) 1.1766 + { 1.1767 + RemoveRange(aSelection, checkRange); 1.1768 + index--; 1.1769 + count--; 1.1770 + } 1.1771 + } 1.1772 + } 1.1773 + 1.1774 + return NS_OK; 1.1775 +} 1.1776 + 1.1777 + 1.1778 +// mozInlineSpellChecker::RemoveRange 1.1779 +// 1.1780 +// For performance reasons, we have an upper bound on the number of word 1.1781 +// ranges in the spell check selection. When removing a range from the 1.1782 +// selection, we need to decrement mNumWordsInSpellSelection 1.1783 + 1.1784 +nsresult 1.1785 +mozInlineSpellChecker::RemoveRange(nsISelection* aSpellCheckSelection, 1.1786 + nsIDOMRange* aRange) 1.1787 +{ 1.1788 + NS_ENSURE_ARG_POINTER(aSpellCheckSelection); 1.1789 + NS_ENSURE_ARG_POINTER(aRange); 1.1790 + 1.1791 + nsresult rv = aSpellCheckSelection->RemoveRange(aRange); 1.1792 + if (NS_SUCCEEDED(rv) && mNumWordsInSpellSelection) 1.1793 + mNumWordsInSpellSelection--; 1.1794 + 1.1795 + return rv; 1.1796 +} 1.1797 + 1.1798 + 1.1799 +// mozInlineSpellChecker::AddRange 1.1800 +// 1.1801 +// For performance reasons, we have an upper bound on the number of word 1.1802 +// ranges we'll add to the spell check selection. Once we reach that upper 1.1803 +// bound, stop adding the ranges 1.1804 + 1.1805 +nsresult 1.1806 +mozInlineSpellChecker::AddRange(nsISelection* aSpellCheckSelection, 1.1807 + nsIDOMRange* aRange) 1.1808 +{ 1.1809 + NS_ENSURE_ARG_POINTER(aSpellCheckSelection); 1.1810 + NS_ENSURE_ARG_POINTER(aRange); 1.1811 + 1.1812 + nsresult rv = NS_OK; 1.1813 + 1.1814 + if (!SpellCheckSelectionIsFull()) 1.1815 + { 1.1816 + rv = aSpellCheckSelection->AddRange(aRange); 1.1817 + if (NS_SUCCEEDED(rv)) 1.1818 + mNumWordsInSpellSelection++; 1.1819 + } 1.1820 + 1.1821 + return rv; 1.1822 +} 1.1823 + 1.1824 +nsresult mozInlineSpellChecker::GetSpellCheckSelection(nsISelection ** aSpellCheckSelection) 1.1825 +{ 1.1826 + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); 1.1827 + NS_ENSURE_TRUE(editor, NS_ERROR_NULL_POINTER); 1.1828 + 1.1829 + nsCOMPtr<nsISelectionController> selcon; 1.1830 + nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon)); 1.1831 + NS_ENSURE_SUCCESS(rv, rv); 1.1832 + 1.1833 + nsCOMPtr<nsISelection> spellCheckSelection; 1.1834 + return selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, aSpellCheckSelection); 1.1835 +} 1.1836 + 1.1837 +nsresult mozInlineSpellChecker::SaveCurrentSelectionPosition() 1.1838 +{ 1.1839 + nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor)); 1.1840 + NS_ENSURE_TRUE(editor, NS_OK); 1.1841 + 1.1842 + // figure out the old caret position based on the current selection 1.1843 + nsCOMPtr<nsISelection> selection; 1.1844 + nsresult rv = editor->GetSelection(getter_AddRefs(selection)); 1.1845 + NS_ENSURE_SUCCESS(rv, rv); 1.1846 + 1.1847 + rv = selection->GetFocusNode(getter_AddRefs(mCurrentSelectionAnchorNode)); 1.1848 + NS_ENSURE_SUCCESS(rv, rv); 1.1849 + 1.1850 + selection->GetFocusOffset(&mCurrentSelectionOffset); 1.1851 + 1.1852 + return NS_OK; 1.1853 +} 1.1854 + 1.1855 +// This is a copy of nsContentUtils::ContentIsDescendantOf. Another crime 1.1856 +// for XPCOM's rap sheet 1.1857 +bool // static 1.1858 +ContentIsDescendantOf(nsINode* aPossibleDescendant, 1.1859 + nsINode* aPossibleAncestor) 1.1860 +{ 1.1861 + NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!"); 1.1862 + NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!"); 1.1863 + 1.1864 + do { 1.1865 + if (aPossibleDescendant == aPossibleAncestor) 1.1866 + return true; 1.1867 + aPossibleDescendant = aPossibleDescendant->GetParentNode(); 1.1868 + } while (aPossibleDescendant); 1.1869 + 1.1870 + return false; 1.1871 +} 1.1872 + 1.1873 +// mozInlineSpellChecker::HandleNavigationEvent 1.1874 +// 1.1875 +// Acts upon mouse clicks and keyboard navigation changes, spell checking 1.1876 +// the previous word if the new navigation location moves us to another 1.1877 +// word. 1.1878 +// 1.1879 +// This is complicated by the fact that our mouse events are happening after 1.1880 +// selection has been changed to account for the mouse click. But keyboard 1.1881 +// events are happening before the caret selection has changed. Working 1.1882 +// around this by letting keyboard events setting forceWordSpellCheck to 1.1883 +// true. aNewPositionOffset also tries to work around this for the 1.1884 +// DOM_VK_RIGHT and DOM_VK_LEFT cases. 1.1885 + 1.1886 +nsresult 1.1887 +mozInlineSpellChecker::HandleNavigationEvent(bool aForceWordSpellCheck, 1.1888 + int32_t aNewPositionOffset) 1.1889 +{ 1.1890 + nsresult rv; 1.1891 + 1.1892 + // If we already handled the navigation event and there is no possibility 1.1893 + // anything has changed since then, we don't have to do anything. This 1.1894 + // optimization makes a noticeable difference when you hold down a navigation 1.1895 + // key like Page Down. 1.1896 + if (! mNeedsCheckAfterNavigation) 1.1897 + return NS_OK; 1.1898 + 1.1899 + nsCOMPtr<nsIDOMNode> currentAnchorNode = mCurrentSelectionAnchorNode; 1.1900 + int32_t currentAnchorOffset = mCurrentSelectionOffset; 1.1901 + 1.1902 + // now remember the new focus position resulting from the event 1.1903 + rv = SaveCurrentSelectionPosition(); 1.1904 + NS_ENSURE_SUCCESS(rv, rv); 1.1905 + 1.1906 + bool shouldPost; 1.1907 + mozInlineSpellStatus status(this); 1.1908 + rv = status.InitForNavigation(aForceWordSpellCheck, aNewPositionOffset, 1.1909 + currentAnchorNode, currentAnchorOffset, 1.1910 + mCurrentSelectionAnchorNode, mCurrentSelectionOffset, 1.1911 + &shouldPost); 1.1912 + NS_ENSURE_SUCCESS(rv, rv); 1.1913 + if (shouldPost) { 1.1914 + rv = ScheduleSpellCheck(status); 1.1915 + NS_ENSURE_SUCCESS(rv, rv); 1.1916 + } 1.1917 + 1.1918 + return NS_OK; 1.1919 +} 1.1920 + 1.1921 +NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(nsIDOMEvent* aEvent) 1.1922 +{ 1.1923 + nsAutoString eventType; 1.1924 + aEvent->GetType(eventType); 1.1925 + 1.1926 + if (eventType.EqualsLiteral("blur")) { 1.1927 + return Blur(aEvent); 1.1928 + } 1.1929 + if (eventType.EqualsLiteral("click")) { 1.1930 + return MouseClick(aEvent); 1.1931 + } 1.1932 + if (eventType.EqualsLiteral("keypress")) { 1.1933 + return KeyPress(aEvent); 1.1934 + } 1.1935 + 1.1936 + return NS_OK; 1.1937 +} 1.1938 + 1.1939 +nsresult mozInlineSpellChecker::Blur(nsIDOMEvent* aEvent) 1.1940 +{ 1.1941 + // force spellcheck on blur, for instance when tabbing out of a textbox 1.1942 + HandleNavigationEvent(true); 1.1943 + return NS_OK; 1.1944 +} 1.1945 + 1.1946 +nsresult mozInlineSpellChecker::MouseClick(nsIDOMEvent *aMouseEvent) 1.1947 +{ 1.1948 + nsCOMPtr<nsIDOMMouseEvent>mouseEvent = do_QueryInterface(aMouseEvent); 1.1949 + NS_ENSURE_TRUE(mouseEvent, NS_OK); 1.1950 + 1.1951 + // ignore any errors from HandleNavigationEvent as we don't want to prevent 1.1952 + // anyone else from seeing this event. 1.1953 + int16_t button; 1.1954 + mouseEvent->GetButton(&button); 1.1955 + HandleNavigationEvent(button != 0); 1.1956 + return NS_OK; 1.1957 +} 1.1958 + 1.1959 +nsresult mozInlineSpellChecker::KeyPress(nsIDOMEvent* aKeyEvent) 1.1960 +{ 1.1961 + nsCOMPtr<nsIDOMKeyEvent>keyEvent = do_QueryInterface(aKeyEvent); 1.1962 + NS_ENSURE_TRUE(keyEvent, NS_OK); 1.1963 + 1.1964 + uint32_t keyCode; 1.1965 + keyEvent->GetKeyCode(&keyCode); 1.1966 + 1.1967 + // we only care about navigation keys that moved selection 1.1968 + switch (keyCode) 1.1969 + { 1.1970 + case nsIDOMKeyEvent::DOM_VK_RIGHT: 1.1971 + case nsIDOMKeyEvent::DOM_VK_LEFT: 1.1972 + HandleNavigationEvent(false, keyCode == nsIDOMKeyEvent::DOM_VK_RIGHT ? 1 : -1); 1.1973 + break; 1.1974 + case nsIDOMKeyEvent::DOM_VK_UP: 1.1975 + case nsIDOMKeyEvent::DOM_VK_DOWN: 1.1976 + case nsIDOMKeyEvent::DOM_VK_HOME: 1.1977 + case nsIDOMKeyEvent::DOM_VK_END: 1.1978 + case nsIDOMKeyEvent::DOM_VK_PAGE_UP: 1.1979 + case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: 1.1980 + HandleNavigationEvent(true /* force a spelling correction */); 1.1981 + break; 1.1982 + } 1.1983 + 1.1984 + return NS_OK; 1.1985 +} 1.1986 + 1.1987 +// Used as the nsIEditorSpellCheck::UpdateCurrentDictionary callback. 1.1988 +class UpdateCurrentDictionaryCallback MOZ_FINAL : public nsIEditorSpellCheckCallback 1.1989 +{ 1.1990 +public: 1.1991 + NS_DECL_ISUPPORTS 1.1992 + 1.1993 + explicit UpdateCurrentDictionaryCallback(mozInlineSpellChecker* aSpellChecker, 1.1994 + uint32_t aDisabledAsyncToken) 1.1995 + : mSpellChecker(aSpellChecker), mDisabledAsyncToken(aDisabledAsyncToken) {} 1.1996 + 1.1997 + NS_IMETHOD EditorSpellCheckDone() 1.1998 + { 1.1999 + // Ignore this callback if SetEnableRealTimeSpell(false) was called after 1.2000 + // the UpdateCurrentDictionary call that triggered it. 1.2001 + return mSpellChecker->mDisabledAsyncToken > mDisabledAsyncToken ? 1.2002 + NS_OK : 1.2003 + mSpellChecker->CurrentDictionaryUpdated(); 1.2004 + } 1.2005 + 1.2006 +private: 1.2007 + nsRefPtr<mozInlineSpellChecker> mSpellChecker; 1.2008 + uint32_t mDisabledAsyncToken; 1.2009 +}; 1.2010 +NS_IMPL_ISUPPORTS(UpdateCurrentDictionaryCallback, nsIEditorSpellCheckCallback) 1.2011 + 1.2012 +NS_IMETHODIMP mozInlineSpellChecker::UpdateCurrentDictionary() 1.2013 +{ 1.2014 + // mSpellCheck is null and mPendingSpellCheck is nonnull while the spell 1.2015 + // checker is being initialized. Calling UpdateCurrentDictionary on 1.2016 + // mPendingSpellCheck simply queues the dictionary update after the init. 1.2017 + nsCOMPtr<nsIEditorSpellCheck> spellCheck = mSpellCheck ? mSpellCheck : 1.2018 + mPendingSpellCheck; 1.2019 + if (!spellCheck) { 1.2020 + return NS_OK; 1.2021 + } 1.2022 + 1.2023 + if (NS_FAILED(spellCheck->GetCurrentDictionary(mPreviousDictionary))) { 1.2024 + mPreviousDictionary.Truncate(); 1.2025 + } 1.2026 + 1.2027 + nsRefPtr<UpdateCurrentDictionaryCallback> cb = 1.2028 + new UpdateCurrentDictionaryCallback(this, mDisabledAsyncToken); 1.2029 + NS_ENSURE_STATE(cb); 1.2030 + nsresult rv = spellCheck->UpdateCurrentDictionary(cb); 1.2031 + if (NS_FAILED(rv)) { 1.2032 + cb = nullptr; 1.2033 + NS_ENSURE_SUCCESS(rv, rv); 1.2034 + } 1.2035 + mNumPendingUpdateCurrentDictionary++; 1.2036 + ChangeNumPendingSpellChecks(1); 1.2037 + 1.2038 + return NS_OK; 1.2039 +} 1.2040 + 1.2041 +// Called when nsIEditorSpellCheck::UpdateCurrentDictionary completes. 1.2042 +nsresult mozInlineSpellChecker::CurrentDictionaryUpdated() 1.2043 +{ 1.2044 + mNumPendingUpdateCurrentDictionary--; 1.2045 + NS_ASSERTION(mNumPendingUpdateCurrentDictionary >= 0, 1.2046 + "CurrentDictionaryUpdated called without corresponding " 1.2047 + "UpdateCurrentDictionary call!"); 1.2048 + ChangeNumPendingSpellChecks(-1); 1.2049 + 1.2050 + nsAutoString currentDictionary; 1.2051 + if (!mSpellCheck || 1.2052 + NS_FAILED(mSpellCheck->GetCurrentDictionary(currentDictionary))) { 1.2053 + currentDictionary.Truncate(); 1.2054 + } 1.2055 + 1.2056 + if (!mPreviousDictionary.Equals(currentDictionary)) { 1.2057 + nsresult rv = SpellCheckRange(nullptr); 1.2058 + NS_ENSURE_SUCCESS(rv, rv); 1.2059 + } 1.2060 + 1.2061 + return NS_OK; 1.2062 +} 1.2063 + 1.2064 +NS_IMETHODIMP 1.2065 +mozInlineSpellChecker::GetSpellCheckPending(bool* aPending) 1.2066 +{ 1.2067 + *aPending = mNumPendingSpellChecks > 0; 1.2068 + return NS_OK; 1.2069 +}