mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 07:45:30 +00:00
Bug 1502661 - Part 3. Use async API for spell checking. r=masayuki
Previous implementation is - Use idle runnable for spellchecker - If spellchecker runnable spends 1ms (with 5 words), spellchecker is suspended, then resume it after 1s. - If misspell is 3/4 of extensions.spellchecker.inline.max-misspellings per DoSpellCheck, spellchecker is stopped. After this change, IPC is async, so we cannot count misspell words without waiting the result of async call. So I would like to change to - Use idle runnable for spellchecker - If spellchecker runnable spends 1ms (with 5 words), spellchecker is suspended, then resume it after 1s. - If misspell reaches extensions.spellchecker.inline.max-misspellings, stop spellcheck. - spellchecker IPC is called per maximum 25 (INLINESPELL_MAXIMUM_CHUNKED_WORDS_PER_TASK) words. As long as my MacBook (Core m3), spellchecker can check 100-150 words per 1ms after this fix. Also, INLINESPELL_MAXIMUM_CHUNKED_WORDS_PER_TASK define is less than 5, a lot of a11y tests will be failed. Because it uses selection listener to count all misspell words without waiting completion. Differential Revision: https://phabricator.services.mozilla.com/D14837 --HG-- extra : rebase_source : 59185554236409d3320cc77714a776f2228214af
This commit is contained in:
parent
7bc8f8b726
commit
bf48e8ad9d
@ -68,6 +68,7 @@
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::ipc;
|
||||
|
||||
// Set to spew messages to the console about what is happening.
|
||||
//#define DEBUG_INLINESPELL
|
||||
@ -81,6 +82,9 @@ using namespace mozilla::dom;
|
||||
// be too short to a low-end machine.
|
||||
#define INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT 5
|
||||
|
||||
// The maximum number of words to check word via IPC.
|
||||
#define INLINESPELL_MAXIMUM_CHUNKED_WORDS_PER_TASK 25
|
||||
|
||||
// These notifications are broadcast when spell check starts and ends. STARTED
|
||||
// must always be followed by ENDED.
|
||||
#define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started"
|
||||
@ -92,7 +96,7 @@ static const PRTime kMaxSpellCheckTimeInUsec =
|
||||
INLINESPELL_CHECK_TIMEOUT * PR_USEC_PER_MSEC;
|
||||
|
||||
mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
|
||||
: mSpellChecker(aSpellChecker), mWordCount(0) {}
|
||||
: mSpellChecker(aSpellChecker) {}
|
||||
|
||||
// mozInlineSpellStatus::InitForEditorChange
|
||||
//
|
||||
@ -520,7 +524,6 @@ mozInlineSpellChecker::mozInlineSpellChecker()
|
||||
if (prefs)
|
||||
prefs->GetIntPref(kMaxSpellCheckSelectionSize,
|
||||
&mMaxNumWordsInSpellSelection);
|
||||
mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4;
|
||||
}
|
||||
|
||||
mozInlineSpellChecker::~mozInlineSpellChecker() {}
|
||||
@ -1195,8 +1198,6 @@ nsresult mozInlineSpellChecker::DoSpellCheckSelection(
|
||||
MOZ_ASSERT(
|
||||
doneChecking,
|
||||
"We gave the spellchecker one word, but it didn't finish checking?!?!");
|
||||
|
||||
status->mWordCount = 0;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@ -1238,12 +1239,18 @@ nsresult mozInlineSpellChecker::DoSpellCheck(
|
||||
const UniquePtr<mozInlineSpellStatus>& aStatus, bool* aDoneChecking) {
|
||||
*aDoneChecking = true;
|
||||
|
||||
NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
|
||||
if (NS_WARN_IF(!mSpellCheck)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (SpellCheckSelectionIsFull()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// get the editor for ShouldSpellCheckNode, this may fail in reasonable
|
||||
// circumstances since the editor could have gone away
|
||||
RefPtr<TextEditor> textEditor = mTextEditor;
|
||||
if (!textEditor) {
|
||||
if (!textEditor || textEditor->Destroyed()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
@ -1291,25 +1298,34 @@ nsresult mozInlineSpellChecker::DoSpellCheck(
|
||||
int32_t wordsChecked = 0;
|
||||
PRTime beginTime = PR_Now();
|
||||
|
||||
nsTArray<nsString> words;
|
||||
nsTArray<NodeOffsetRange> checkRanges;
|
||||
nsAutoString wordText;
|
||||
NodeOffsetRange wordNodeOffsetRange;
|
||||
bool dontCheckWord;
|
||||
// XXX Spellchecker API isn't async on chrome process.
|
||||
static const size_t requestChunkSize =
|
||||
XRE_IsContentProcess() ? INLINESPELL_MAXIMUM_CHUNKED_WORDS_PER_TASK : 1;
|
||||
while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText, &wordNodeOffsetRange,
|
||||
&dontCheckWord)) &&
|
||||
!wordNodeOffsetRange.Empty()) {
|
||||
// get the range for the current word.
|
||||
nsINode* beginNode = wordNodeOffsetRange.Begin().mNode;
|
||||
nsINode* endNode = wordNodeOffsetRange.End().mNode;
|
||||
int32_t beginOffset = wordNodeOffsetRange.Begin().mOffset;
|
||||
int32_t endOffset = wordNodeOffsetRange.End().mOffset;
|
||||
nsINode* beginNode = wordNodeOffsetRange.Begin().Node();
|
||||
nsINode* endNode = wordNodeOffsetRange.End().Node();
|
||||
int32_t beginOffset = wordNodeOffsetRange.Begin().Offset();
|
||||
int32_t endOffset = wordNodeOffsetRange.End().Offset();
|
||||
|
||||
// see if we've done enough words in this round and run out of time.
|
||||
if (wordsChecked >= INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT &&
|
||||
PR_Now() > PRTime(beginTime + kMaxSpellCheckTimeInUsec)) {
|
||||
// stop checking, our time limit has been exceeded.
|
||||
#ifdef DEBUG_INLINESPELL
|
||||
printf("We have run out of the time, schedule next round.");
|
||||
printf("We have run out of the time, schedule next round.\n");
|
||||
#endif
|
||||
CheckCurrentWordsNoSuggest(aSpellCheckSelection, words, checkRanges);
|
||||
words.Clear();
|
||||
checkRanges.Clear();
|
||||
|
||||
// move the range to encompass the stuff that needs checking.
|
||||
nsresult rv = aStatus->mRange->SetStart(beginNode, beginOffset);
|
||||
if (NS_FAILED(rv)) {
|
||||
@ -1366,39 +1382,31 @@ nsresult mozInlineSpellChecker::DoSpellCheck(
|
||||
}
|
||||
|
||||
// check spelling and add to selection if misspelled
|
||||
bool isMisspelled;
|
||||
mozInlineSpellWordUtil::NormalizeWord(wordText);
|
||||
nsresult rv =
|
||||
mSpellCheck->CheckCurrentWordNoSuggest(wordText, &isMisspelled);
|
||||
if (NS_FAILED(rv)) continue;
|
||||
|
||||
words.AppendElement(wordText);
|
||||
checkRanges.AppendElement(wordNodeOffsetRange);
|
||||
wordsChecked++;
|
||||
if (isMisspelled) {
|
||||
// misspelled words count extra toward the max
|
||||
RefPtr<nsRange> wordRange;
|
||||
// If we somehow can't make a range for this word, just ignore it.
|
||||
if (NS_SUCCEEDED(aWordUtil.MakeRange(wordNodeOffsetRange.Begin(),
|
||||
wordNodeOffsetRange.End(),
|
||||
getter_AddRefs(wordRange)))) {
|
||||
AddRange(aSpellCheckSelection, wordRange);
|
||||
aStatus->mWordCount++;
|
||||
if (aStatus->mWordCount >= mMaxMisspellingsPerCheck ||
|
||||
SpellCheckSelectionIsFull()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (words.Length() >= requestChunkSize) {
|
||||
CheckCurrentWordsNoSuggest(aSpellCheckSelection, words, checkRanges);
|
||||
words.Clear();
|
||||
checkRanges.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
CheckCurrentWordsNoSuggest(aSpellCheckSelection, words, checkRanges);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// An RAII helper that calls ChangeNumPendingSpellChecks on destruction.
|
||||
class AutoChangeNumPendingSpellChecks {
|
||||
class MOZ_RAII AutoChangeNumPendingSpellChecks final {
|
||||
public:
|
||||
AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker,
|
||||
int32_t aDelta)
|
||||
: mSpellChecker(aSpellChecker), mDelta(aDelta) {}
|
||||
explicit AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker,
|
||||
int32_t aDelta
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: mSpellChecker(aSpellChecker), mDelta(aDelta) {
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
~AutoChangeNumPendingSpellChecks() {
|
||||
mSpellChecker->ChangeNumPendingSpellChecks(mDelta);
|
||||
@ -1407,8 +1415,68 @@ class AutoChangeNumPendingSpellChecks {
|
||||
private:
|
||||
RefPtr<mozInlineSpellChecker> mSpellChecker;
|
||||
int32_t mDelta;
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
};
|
||||
|
||||
void mozInlineSpellChecker::CheckCurrentWordsNoSuggest(
|
||||
Selection* aSpellCheckSelection, const nsTArray<nsString>& aWords,
|
||||
const nsTArray<NodeOffsetRange>& aRanges) {
|
||||
if (aWords.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChangeNumPendingSpellChecks(1);
|
||||
|
||||
RefPtr<mozInlineSpellChecker> self = this;
|
||||
RefPtr<Selection> spellCheckerSelection = aSpellCheckSelection;
|
||||
uint32_t token = mDisabledAsyncToken;
|
||||
mSpellCheck->CheckCurrentWordsNoSuggest(aWords)->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
[self, spellCheckerSelection, aRanges,
|
||||
token](const nsTArray<bool>& aIsMisspelled) {
|
||||
if (token != self->mDisabledAsyncToken) {
|
||||
// This result is never used
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->mTextEditor || self->mTextEditor->Destroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutoChangeNumPendingSpellChecks pendingChecks(self, -1);
|
||||
|
||||
if (self->SpellCheckSelectionIsFull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < aIsMisspelled.Length(); i++) {
|
||||
if (!aIsMisspelled[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RefPtr<nsRange> wordRange =
|
||||
mozInlineSpellWordUtil::MakeRange(aRanges[i]);
|
||||
// If we somehow can't make a range for this word, just ignore
|
||||
// it.
|
||||
if (wordRange) {
|
||||
self->AddRange(spellCheckerSelection, wordRange);
|
||||
}
|
||||
}
|
||||
},
|
||||
[self, token](nsresult aRv) {
|
||||
if (!self->mTextEditor || self->mTextEditor->Destroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (token != self->mDisabledAsyncToken) {
|
||||
// This result is never used
|
||||
return;
|
||||
}
|
||||
|
||||
self->ChangeNumPendingSpellChecks(-1);
|
||||
});
|
||||
}
|
||||
|
||||
// mozInlineSpellChecker::ResumeCheck
|
||||
//
|
||||
// Called by the resume event when it fires. We will try to pick up where
|
||||
|
@ -10,11 +10,11 @@
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsIEditorSpellCheck.h"
|
||||
#include "nsIInlineSpellChecker.h"
|
||||
#include "mozInlineSpellWordUtil.h"
|
||||
#include "nsRange.h"
|
||||
#include "nsWeakReference.h"
|
||||
|
||||
class InitEditorSpellCheckCallback;
|
||||
class mozInlineSpellWordUtil;
|
||||
class mozInlineSpellChecker;
|
||||
class mozInlineSpellResume;
|
||||
class UpdateCurrentDictionaryCallback;
|
||||
@ -52,11 +52,6 @@ class mozInlineSpellStatus {
|
||||
|
||||
RefPtr<mozInlineSpellChecker> mSpellChecker;
|
||||
|
||||
// The total number of words checked in this sequence, using this tally tells
|
||||
// us when to stop. This count is preserved as we continue checking in new
|
||||
// messages.
|
||||
int32_t mWordCount;
|
||||
|
||||
// what happened?
|
||||
enum Operation {
|
||||
eOpChange, // for SpellCheckAfterEditorChange except
|
||||
@ -139,13 +134,6 @@ class mozInlineSpellChecker final : public nsIInlineSpellChecker,
|
||||
int32_t mNumWordsInSpellSelection;
|
||||
int32_t mMaxNumWordsInSpellSelection;
|
||||
|
||||
// How many misspellings we can add at once. This is often less than the max
|
||||
// total number of misspellings. When you have a large textarea prepopulated
|
||||
// with text with many misspellings, we can hit this limit. By making it
|
||||
// lower than the total number of misspelled words, new text typed by the
|
||||
// user can also have spellchecking in it.
|
||||
int32_t mMaxMisspellingsPerCheck;
|
||||
|
||||
// we need to keep track of the current text position in the document
|
||||
// so we can spell check the old word when the user clicks around the
|
||||
// document.
|
||||
@ -282,6 +270,10 @@ class mozInlineSpellChecker final : public nsIInlineSpellChecker,
|
||||
|
||||
void StartToListenToEditSubActions() { mIsListeningToEditSubActions = true; }
|
||||
void EndListeningToEditSubActions() { mIsListeningToEditSubActions = false; }
|
||||
|
||||
void CheckCurrentWordsNoSuggest(mozilla::dom::Selection* aSpellCheckSelection,
|
||||
const nsTArray<nsString>& aWords,
|
||||
const nsTArray<NodeOffsetRange>& aRanges);
|
||||
};
|
||||
|
||||
#endif // #ifndef mozilla_mozInlineSpellChecker_h
|
||||
|
@ -330,6 +330,19 @@ nsresult mozInlineSpellWordUtil::MakeRange(NodeOffset aBegin, NodeOffset aEnd,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<nsRange> mozInlineSpellWordUtil::MakeRange(
|
||||
const NodeOffsetRange& aRange) {
|
||||
RefPtr<nsRange> range = new nsRange(aRange.Begin().Node());
|
||||
nsresult rv =
|
||||
range->SetStartAndEnd(aRange.Begin().Node(), aRange.Begin().Offset(),
|
||||
aRange.End().Node(), aRange.End().Offset());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return nullptr;
|
||||
}
|
||||
return range.forget();
|
||||
}
|
||||
|
||||
/*********** Word Splitting ************/
|
||||
|
||||
// classifies a given character in the DOM word
|
||||
|
@ -22,10 +22,10 @@ class TextEditor;
|
||||
} // namespace mozilla
|
||||
|
||||
struct NodeOffset {
|
||||
nsINode* mNode;
|
||||
nsCOMPtr<nsINode> mNode;
|
||||
int32_t mOffset;
|
||||
|
||||
NodeOffset() : mNode(nullptr), mOffset(0) {}
|
||||
NodeOffset() : mOffset(0) {}
|
||||
NodeOffset(nsINode* aNode, int32_t aOffset)
|
||||
: mNode(aNode), mOffset(aOffset) {}
|
||||
|
||||
@ -34,6 +34,9 @@ struct NodeOffset {
|
||||
}
|
||||
|
||||
bool operator!=(const NodeOffset& aOther) const { return !(*this == aOther); }
|
||||
|
||||
nsINode* Node() const { return mNode.get(); }
|
||||
int32_t Offset() const { return mOffset; }
|
||||
};
|
||||
|
||||
class NodeOffsetRange {
|
||||
@ -47,11 +50,11 @@ class NodeOffsetRange {
|
||||
NodeOffsetRange(NodeOffset b, NodeOffset e)
|
||||
: mBegin(b), mEnd(e), mEmpty(false) {}
|
||||
|
||||
NodeOffset Begin() { return mBegin; }
|
||||
NodeOffset Begin() const { return mBegin; }
|
||||
|
||||
NodeOffset End() { return mEnd; }
|
||||
NodeOffset End() const { return mEnd; }
|
||||
|
||||
bool Empty() { return mEmpty; }
|
||||
bool Empty() const { return mEmpty; }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -104,6 +107,7 @@ class MOZ_STACK_CLASS mozInlineSpellWordUtil {
|
||||
|
||||
// Convenience functions, object must be initialized
|
||||
nsresult MakeRange(NodeOffset aBegin, NodeOffset aEnd, nsRange** aRange);
|
||||
static already_AddRefed<nsRange> MakeRange(const NodeOffsetRange& aRange);
|
||||
|
||||
// Moves to the the next word in the range, and retrieves it's text and range.
|
||||
// An empty word and a nullptr range are returned when we are done checking.
|
||||
|
Loading…
Reference in New Issue
Block a user