extensions/spellcheck/src/mozInlineSpellChecker.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial