Bug 1729653 - Make `mozInlineSpellChecker` handle events in the system group at capturing phase r=m_kato

Currently, `mozInlineSpellChecker` handles events in the default group.  This
means that the listener may not run if web app stops the propagation.  For
keeping the event listener order as far as possible, this patch changes to
listen to the event at capturing phase in the system group (editor handles
the events at bubbling phase in the system group).

Additionally, it listens to `keypress` events for handling caret/selection
changes.  However, they should be handled at `keydown` because those keys
are not printable.  Therefore, this patch changes to listen to `keydown`
events, but this could change the race between running the scheduled spellcheck
and following `keypress` event which is dispatched only for chrome code and
C++ event listeners.  However, this may not change it actually because the
race is changed only when the following `keypress` event delays too much.
https://searchfox.org/mozilla-central/rev/2eebd6e256fa0355e08421265e57ee1307836d92/extensions/spellcheck/src/mozInlineSpellChecker.cpp#503-504

Differential Revision: https://phabricator.services.mozilla.com/D126635
This commit is contained in:
Masayuki Nakano 2021-09-28 02:47:15 +00:00
Родитель 86b3598e72
Коммит d7fa9ea462
4 изменённых файлов: 158 добавлений и 56 удалений

Просмотреть файл

@ -40,4 +40,5 @@ skip-if = e10s
[test_bug1418629.html]
[test_bug1497480.html]
[test_bug1602526.html]
[test_spellcheck_after_pressing_navigation_key.html]
[test_suggest.html]

Просмотреть файл

@ -0,0 +1,75 @@
<!doctype html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1729653
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1729653</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<textarea rows="20" cols="50">That undfgdfg seems OK.</textarea>
<script>
let {onSpellCheck} = SpecialPowers.Cu.import("resource://testing-common/AsyncSpellCheckTestHelper.jsm", {});
function waitForTick() {
return new Promise(resolve => SimpleTest.executeSoon(resolve));
}
function waitForOnSpellCheck(aTextArea) {
info("Waiting for onSpellCheck...");
return new Promise(resolve => onSpellCheck(aTextArea, resolve));
}
/** Test for Bug 1729653 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async () => {
const textarea = document.querySelector("textarea");
textarea.focus();
textarea.selectionStart = textarea.selectionEnd = "That undfgdfg".length;
const editor = SpecialPowers.wrap(textarea).editor;
const nsISelectionController = SpecialPowers.Ci.nsISelectionController;
const selection = editor.selectionController.getSelection(nsISelectionController.SELECTION_SPELLCHECK);
const spellChecker = SpecialPowers.Cu.createSpellChecker();
spellChecker.InitSpellChecker(editor, false);
info("Waiting for current dictionary update...");
await new Promise(resolve => spellChecker.UpdateCurrentDictionary(resolve));
if (selection.rangeCount === 0) {
await waitForOnSpellCheck(textarea);
}
if (selection.rangeCount == 1) {
is(
selection.getRangeAt(0).toString(),
"undfgdfg",
"\"undfgdfg\" should be marked as misspelled word at start"
);
} else {
is(selection.rangeCount, 1, "We should have a misspelled word at start");
}
synthesizeKey(" ");
synthesizeKey("KEY_Backspace");
textarea.addEventListener("keydown", event => {
event.stopImmediatePropagation(); // This shouldn't block spellchecker to handle it.
}, {once: true});
synthesizeKey("KEY_End");
await waitForTick();
if (selection.rangeCount === 0) {
await waitForOnSpellCheck(textarea);
}
if (selection.rangeCount == 1) {
is(
selection.getRangeAt(0).toString(),
"undfgdfg",
"\"undfgdfg\" should be marked as misspelled word at end"
);
} else {
is(selection.rangeCount, 1, "We should have a misspelled word at end");
}
SimpleTest.finish();
});
</script>
</body>
</html>

Просмотреть файл

@ -38,9 +38,11 @@
#include "mozilla/EditorBase.h"
#include "mozilla/EditorSpellCheck.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/Logging.h"
#include "mozilla/RangeUtils.h"
#include "mozilla/Services.h"
#include "mozilla/TextEvents.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/KeyboardEvent.h"
#include "mozilla/dom/KeyboardEventBinding.h"
@ -698,38 +700,54 @@ mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking() {
// events.
nsresult mozInlineSpellChecker::RegisterEventListeners() {
if (NS_WARN_IF(!mEditorBase)) {
if (MOZ_UNLIKELY(NS_WARN_IF(!mEditorBase))) {
return NS_ERROR_FAILURE;
}
StartToListenToEditSubActions();
RefPtr<Document> doc = mEditorBase->GetDocument();
if (NS_WARN_IF(!doc)) {
if (MOZ_UNLIKELY(NS_WARN_IF(!doc))) {
return NS_ERROR_FAILURE;
}
doc->AddEventListener(u"blur"_ns, this, true, false);
doc->AddEventListener(u"click"_ns, this, false, false);
doc->AddEventListener(u"keypress"_ns, this, false, false);
EventListenerManager* eventListenerManager =
doc->GetOrCreateListenerManager();
if (MOZ_UNLIKELY(NS_WARN_IF(!eventListenerManager))) {
return NS_ERROR_FAILURE;
}
eventListenerManager->AddEventListenerByType(
this, u"blur"_ns, TrustedEventsAtSystemGroupCapture());
eventListenerManager->AddEventListenerByType(
this, u"click"_ns, TrustedEventsAtSystemGroupCapture());
eventListenerManager->AddEventListenerByType(
this, u"keydown"_ns, TrustedEventsAtSystemGroupCapture());
return NS_OK;
}
// mozInlineSpellChecker::UnregisterEventListeners
nsresult mozInlineSpellChecker::UnregisterEventListeners() {
if (NS_WARN_IF(!mEditorBase)) {
if (MOZ_UNLIKELY(NS_WARN_IF(!mEditorBase))) {
return NS_ERROR_FAILURE;
}
EndListeningToEditSubActions();
RefPtr<Document> doc = mEditorBase->GetDocument();
if (NS_WARN_IF(!doc)) {
if (MOZ_UNLIKELY(NS_WARN_IF(!doc))) {
return NS_ERROR_FAILURE;
}
doc->RemoveEventListener(u"blur"_ns, this, true);
doc->RemoveEventListener(u"click"_ns, this, false);
doc->RemoveEventListener(u"keypress"_ns, this, false);
EventListenerManager* eventListenerManager =
doc->GetOrCreateListenerManager();
if (MOZ_UNLIKELY(NS_WARN_IF(!eventListenerManager))) {
return NS_ERROR_FAILURE;
}
eventListenerManager->RemoveEventListenerByType(
this, u"blur"_ns, TrustedEventsAtSystemGroupCapture());
eventListenerManager->RemoveEventListenerByType(
this, u"click"_ns, TrustedEventsAtSystemGroupCapture());
eventListenerManager->RemoveEventListenerByType(
this, u"keydown"_ns, TrustedEventsAtSystemGroupCapture());
return NS_OK;
}
@ -1959,64 +1977,72 @@ nsresult mozInlineSpellChecker::HandleNavigationEvent(
return NS_OK;
}
NS_IMETHODIMP
mozInlineSpellChecker::HandleEvent(Event* aEvent) {
nsAutoString eventType;
aEvent->GetType(eventType);
if (eventType.EqualsLiteral("blur")) {
return OnBlur(aEvent);
}
if (eventType.EqualsLiteral("click")) {
return OnMouseClick(aEvent);
}
if (eventType.EqualsLiteral("keypress")) {
return OnKeyPress(aEvent);
NS_IMETHODIMP mozInlineSpellChecker::HandleEvent(Event* aEvent) {
WidgetEvent* widgetEvent = aEvent->WidgetEventPtr();
if (MOZ_UNLIKELY(!widgetEvent)) {
return NS_OK;
}
return NS_OK;
switch (widgetEvent->mMessage) {
case eBlur:
OnBlur(*aEvent);
return NS_OK;
case eMouseClick:
OnMouseClick(*aEvent);
return NS_OK;
case eKeyDown:
OnKeyDown(*aEvent);
return NS_OK;
default:
MOZ_ASSERT_UNREACHABLE("You must forgot to handle new event type");
return NS_OK;
}
}
nsresult mozInlineSpellChecker::OnBlur(Event* aEvent) {
void mozInlineSpellChecker::OnBlur(Event& aEvent) {
// force spellcheck on blur, for instance when tabbing out of a textbox
HandleNavigationEvent(true);
return NS_OK;
}
nsresult mozInlineSpellChecker::OnMouseClick(Event* aMouseEvent) {
MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
NS_ENSURE_TRUE(mouseEvent, NS_OK);
void mozInlineSpellChecker::OnMouseClick(Event& aMouseEvent) {
MouseEvent* mouseEvent = aMouseEvent.AsMouseEvent();
if (MOZ_UNLIKELY(!mouseEvent)) {
return;
}
// ignore any errors from HandleNavigationEvent as we don't want to prevent
// anyone else from seeing this event.
HandleNavigationEvent(mouseEvent->Button() != 0);
return NS_OK;
}
nsresult mozInlineSpellChecker::OnKeyPress(Event* aKeyEvent) {
RefPtr<KeyboardEvent> keyEvent = aKeyEvent->AsKeyboardEvent();
NS_ENSURE_TRUE(keyEvent, NS_OK);
uint32_t keyCode = keyEvent->KeyCode();
// we only care about navigation keys that moved selection
switch (keyCode) {
case KeyboardEvent_Binding::DOM_VK_RIGHT:
case KeyboardEvent_Binding::DOM_VK_LEFT:
HandleNavigationEvent(
false, keyCode == KeyboardEvent_Binding::DOM_VK_RIGHT ? 1 : -1);
break;
case KeyboardEvent_Binding::DOM_VK_UP:
case KeyboardEvent_Binding::DOM_VK_DOWN:
case KeyboardEvent_Binding::DOM_VK_HOME:
case KeyboardEvent_Binding::DOM_VK_END:
case KeyboardEvent_Binding::DOM_VK_PAGE_UP:
case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN:
HandleNavigationEvent(true /* force a spelling correction */);
break;
void mozInlineSpellChecker::OnKeyDown(Event& aKeyEvent) {
WidgetKeyboardEvent* widgetKeyboardEvent =
aKeyEvent.WidgetEventPtr()->AsKeyboardEvent();
if (MOZ_UNLIKELY(!widgetKeyboardEvent)) {
return;
}
return NS_OK;
// we only care about navigation keys that moved selection
switch (widgetKeyboardEvent->mKeyNameIndex) {
case KEY_NAME_INDEX_ArrowRight:
// XXX Does this work with RTL text?
HandleNavigationEvent(false, 1);
return;
case KEY_NAME_INDEX_ArrowLeft:
// XXX Does this work with RTL text?
HandleNavigationEvent(false, -1);
return;
case KEY_NAME_INDEX_ArrowUp:
case KEY_NAME_INDEX_ArrowDown:
case KEY_NAME_INDEX_Home:
case KEY_NAME_INDEX_End:
case KEY_NAME_INDEX_PageDown:
case KEY_NAME_INDEX_PageUp:
HandleNavigationEvent(true /* force a spelling correction */);
return;
default:
return;
}
}
// Used as the nsIEditorSpellCheck::UpdateCurrentDictionary callback.

Просмотреть файл

@ -220,10 +220,6 @@ class mozInlineSpellChecker final : public nsIInlineSpellChecker,
// update the cached value whenever the list of available dictionaries changes
static void UpdateCanEnableInlineSpellChecking();
nsresult OnBlur(mozilla::dom::Event* aEvent);
nsresult OnMouseClick(mozilla::dom::Event* aMouseEvent);
nsresult OnKeyPress(mozilla::dom::Event* aKeyEvent);
mozInlineSpellChecker();
// spell checks all of the words between two nodes
@ -336,6 +332,10 @@ class mozInlineSpellChecker final : public nsIInlineSpellChecker,
void StartToListenToEditSubActions() { mIsListeningToEditSubActions = true; }
void EndListeningToEditSubActions() { mIsListeningToEditSubActions = false; }
void OnBlur(mozilla::dom::Event& aEvent);
void OnMouseClick(mozilla::dom::Event& aMouseEvent);
void OnKeyDown(mozilla::dom::Event& aKeyEvent);
};
#endif // #ifndef mozilla_mozInlineSpellChecker_h