зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
86b3598e72
Коммит
d7fa9ea462
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче