Bug 1259692 - Make TSFTextStore dispatch eKeyDown or eKeyUp event when TIP processes a WM_KEYDOWN or WM_KEYUP message r=m_kato

TSF doesn't send WM_KEYDOWN nor WM_KEYUP to us while it handles a key message
with ITfKeystrokeMgr::KeyDown() or ITfKeystrokeMgr::KeyUp().  Therefore,
TSFTextStore needs to store handling key event message during calling
those methods and if it does something, we need to dispatch eKeyDown event
or eKeyUp event before dispatching any events.

However, we shouldn't dispatch WidgetKeyboardEvent during a document lock
because TSF/TIP do not assume that document is broken during a document lock.
Therefore, TSFTextStore needs to put it as a pending action into the queue.

So, this patch wraps this with
TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME().  It checks if
there is a document lock when it's called.  If it's locked (and not yet
dispatched keyboard event for the handling key message), it adds pending
action to dispatch keyboard event later.  Otherwise, (and not yet dispatched
one), it dispatches keyboard event directly.

MozReview-Commit-ID: 9rJTJykVLyf

--HG--
extra : rebase_source : 4f8297b2b9fe2905e4cd1f64086fcdbe3d0b6035
This commit is contained in:
Masayuki Nakano 2018-02-28 21:53:23 +09:00
parent 77731f0d87
commit f54903a9ee
2 changed files with 239 additions and 52 deletions

View File

@ -12,6 +12,7 @@
#include "nscore.h"
#include "IMMHandler.h"
#include "KeyboardLayout.h"
#include "WinIMEHandler.h"
#include "WinUtils.h"
#include "mozilla/AutoRestore.h"
@ -1756,7 +1757,9 @@ StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
DWORD TSFTextStore::sClientId = 0;
bool TSFTextStore::sIsKeyboardEventDispatched = false;
#define TEXTSTORE_DEFAULT_VIEW (1)
@ -2254,10 +2257,24 @@ TSFTextStore::FlushPendingActions()
for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
PendingAction& action = mPendingActions[i];
switch (action.mType) {
case PendingAction::COMPOSITION_START: {
case PendingAction::Type::eKeyboardEvent:
if (mDestroyed) {
MOZ_LOG(sTextStoreLog, LogLevel::Warning,
("0x%p TSFTextStore::FlushPendingActions() "
"IGNORED pending KeyboardEvent(%s) due to already destroyed",
action.mKeyMsg->message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp",
this));
}
MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg);
DispatchKeyboardEventAsProcessedByIME(*action.mKeyMsg);
if (!widget || widget->Destroyed()) {
break;
}
break;
case PendingAction::Type::eCompositionStart: {
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::FlushPendingActions() "
"flushing COMPOSITION_START={ mSelectionStart=%d, "
"flushing Type::eCompositionStart={ mSelectionStart=%d, "
"mSelectionLength=%d }, mDestroyed=%s",
this, action.mSelectionStart, action.mSelectionLength,
GetBoolName(mDestroyed)));
@ -2312,10 +2329,10 @@ TSFTextStore::FlushPendingActions()
}
break;
}
case PendingAction::COMPOSITION_UPDATE: {
case PendingAction::Type::eCompositionUpdate: {
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::FlushPendingActions() "
"flushing COMPOSITION_UPDATE={ mData=\"%s\", "
"flushing Type::eCompositionUpdate={ mData=\"%s\", "
"mRanges=0x%p, mRanges->Length()=%d }",
this, GetEscapedUTF8String(action.mData).get(),
action.mRanges.get(),
@ -2359,10 +2376,10 @@ TSFTextStore::FlushPendingActions()
}
break;
}
case PendingAction::COMPOSITION_END: {
case PendingAction::Type::eCompositionEnd: {
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::FlushPendingActions() "
"flushing COMPOSITION_END={ mData=\"%s\" }",
"flushing Type::eCompositionEnd={ mData=\"%s\" }",
this, GetEscapedUTF8String(action.mData).get()));
// Dispatching eCompositionCommit causes a DOM text event, then,
@ -2392,10 +2409,10 @@ TSFTextStore::FlushPendingActions()
}
break;
}
case PendingAction::SET_SELECTION: {
case PendingAction::Type::eSetSelection: {
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::FlushPendingActions() "
"flushing SET_SELECTION={ mSelectionStart=%d, "
"flushing Type::eSetSelection={ mSelectionStart=%d, "
"mSelectionLength=%d, mSelectionReversed=%s }, "
"mDestroyed=%s",
this, action.mSelectionStart, action.mSelectionLength,
@ -2522,6 +2539,78 @@ TSFTextStore::MaybeFlushPendingNotifications()
}
}
void
TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME()
{
// If we've already been destroyed, we cannot do anything.
if (mDestroyed) {
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
"does nothing because it's already been destroyed", this));
return;
}
// If we're not handling key message or we've already dispatched a keyboard
// event for the handling key message, we should do nothing anymore.
if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
"does nothing because not necessary to dispatch keyboard event", this));
return;
}
sIsKeyboardEventDispatched = true;
// If the document is locked, just adding the task to dispatching an event
// to the queue.
if (IsReadLocked()) {
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
"adding to dispatch a keyboard event into the queue...", this));
PendingAction* action = mPendingActions.AppendElement();
action->mType = PendingAction::Type::eKeyboardEvent;
action->mKeyMsg = sHandlingKeyMsg;
return;
}
// Otherwise, dispatch a keyboard event.
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
"trying to dispatch a keyboard event...", this));
DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
}
void
TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg)
{
MOZ_ASSERT(mWidget);
MOZ_ASSERT(!mWidget->Destroyed());
MOZ_ASSERT(!mDestroyed);
ModifierKeyState modKeyState;
MSG msg(aMsg);
msg.wParam = VK_PROCESSKEY;
NativeKey nativeKey(mWidget, msg, modKeyState);
switch (aMsg.message) {
case WM_KEYDOWN:
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
"dispatching an eKeyDown event...", this));
nativeKey.HandleKeyDownMessage();
break;
case WM_KEYUP:
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
"dispatching an eKeyUp event...", this));
nativeKey.HandleKeyUpMessage();
break;
default:
MOZ_LOG(sTextStoreLog, LogLevel::Error,
("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
"ERROR, it doesn't handle the message", this));
break;
}
}
STDMETHODIMP
TSFTextStore::GetStatus(TS_STATUS* pdcs)
{
@ -3375,6 +3464,14 @@ TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
return E_FAIL;
}
MaybeDispatchKeyboardEventAsProcessedByIME();
if (mDestroyed) {
MOZ_LOG(sTextStoreLog, LogLevel::Error,
("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
"destroyed during dispatching a keyboard event", this));
return E_FAIL;
}
// If actually the range is not changing, we should do nothing.
// Perhaps, we can ignore the difference change because it must not be
// important for following edit.
@ -3454,7 +3551,7 @@ TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
CompleteLastActionIfStillIncomplete();
PendingAction* action = mPendingActions.AppendElement();
action->mType = PendingAction::SET_SELECTION;
action->mType = PendingAction::Type::eSetSelection;
action->mSelectionStart = selectionInContent.acpStart;
action->mSelectionLength =
selectionInContent.acpEnd - selectionInContent.acpStart;
@ -4896,18 +4993,26 @@ TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
return false;
}
MaybeDispatchKeyboardEventAsProcessedByIME();
if (mDestroyed) {
MOZ_LOG(sTextStoreLog, LogLevel::Error,
("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
"destroyed during dispatching a keyboard event", this));
return false;
}
TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP();
if (!mComposition.IsComposing()) {
// Use a temporary composition to contain the text
PendingAction* compositionStart = mPendingActions.AppendElement();
compositionStart->mType = PendingAction::COMPOSITION_START;
compositionStart->mType = PendingAction::Type::eCompositionStart;
compositionStart->mSelectionStart = oldSelection.acpStart;
compositionStart->mSelectionLength =
oldSelection.acpEnd - oldSelection.acpStart;
compositionStart->mAdjustSelection = false;
PendingAction* compositionEnd = mPendingActions.AppendElement();
compositionEnd->mType = PendingAction::COMPOSITION_END;
compositionEnd->mType = PendingAction::Type::eCompositionEnd;
compositionEnd->mData = aInsertStr;
MOZ_LOG(sTextStoreLog, LogLevel::Debug,
@ -5003,6 +5108,14 @@ TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
return E_FAIL;
}
MaybeDispatchKeyboardEventAsProcessedByIME();
if (mDestroyed) {
MOZ_LOG(sTextStoreLog, LogLevel::Error,
("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
"destroyed during dispatching a keyboard event", this));
return false;
}
CompleteLastActionIfStillIncomplete();
// TIP may have inserted text at selection before calling
@ -5033,7 +5146,7 @@ TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
}
PendingAction* action = mPendingActions.AppendElement();
action->mType = PendingAction::COMPOSITION_START;
action->mType = PendingAction::Type::eCompositionStart;
action->mSelectionStart = aStart;
action->mSelectionLength = aLength;
@ -5082,9 +5195,17 @@ TSFTextStore::RecordCompositionEndAction()
MOZ_ASSERT(mComposition.IsComposing());
MaybeDispatchKeyboardEventAsProcessedByIME();
if (mDestroyed) {
MOZ_LOG(sTextStoreLog, LogLevel::Error,
("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
"destroyed during dispatching a keyboard event", this));
return false;
}
CompleteLastActionIfStillIncomplete();
PendingAction* action = mPendingActions.AppendElement();
action->mType = PendingAction::COMPOSITION_END;
action->mType = PendingAction::Type::eCompositionEnd;
action->mData = mComposition.mString;
Content& contentForTSF = ContentForTSFRef();
@ -5101,14 +5222,14 @@ TSFTextStore::RecordCompositionEndAction()
// dispatch redundant composition events.
for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
PendingAction& pendingAction = mPendingActions[i - 1];
if (pendingAction.mType == PendingAction::COMPOSITION_START) {
if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
if (pendingAction.mData != action->mData) {
break;
}
// When only setting selection is necessary, we should append it.
if (pendingAction.mAdjustSelection) {
PendingAction* setSelection = mPendingActions.AppendElement();
setSelection->mType = PendingAction::SET_SELECTION;
setSelection->mType = PendingAction::Type::eSetSelection;
setSelection->mSelectionStart = pendingAction.mSelectionStart;
setSelection->mSelectionLength = pendingAction.mSelectionLength;
setSelection->mSelectionReversed = false;
@ -6723,38 +6844,56 @@ TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg)
}
if (aMsg.message == WM_KEYDOWN) {
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
if (textStore) {
textStore->OnStartToHandleKeyMessage();
if (NS_WARN_IF(textStore != sEnabledTextStore)) {
// Let's handle the key message with new focused TSFTextStore.
textStore = sEnabledTextStore;
}
}
AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
sHandlingKeyMsg = &aMsg;
sIsKeyboardEventDispatched = false;
BOOL eaten;
RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
return false;
}
hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
if (textStore) {
textStore->OnEndHandlingKeyMessage(!!eaten);
}
return SUCCEEDED(hr) &&
(eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
}
if (aMsg.message == WM_KEYUP) {
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
if (textStore) {
textStore->OnStartToHandleKeyMessage();
if (NS_WARN_IF(textStore != sEnabledTextStore)) {
// Let's handle the key message with new focused TSFTextStore.
textStore = sEnabledTextStore;
}
}
hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
if (textStore) {
textStore->OnEndHandlingKeyMessage();
}
return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
}
if (aMsg.message == WM_KEYUP) {
AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
sHandlingKeyMsg = &aMsg;
sIsKeyboardEventDispatched = false;
BOOL eaten;
RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
return false;
}
RefPtr<TSFTextStore> textStore(sEnabledTextStore);
if (textStore) {
textStore->OnStartToHandleKeyMessage();
}
hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
if (textStore) {
textStore->OnEndHandlingKeyMessage();
textStore->OnEndHandlingKeyMessage(!!eaten);
}
return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
return SUCCEEDED(hr) &&
(eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
}
return false;
}
@ -6954,7 +7093,7 @@ TSFTextStore::Content::StartComposition(ITfCompositionView* aCompositionView,
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(aCompositionView);
MOZ_ASSERT(!mComposition.mView);
MOZ_ASSERT(aCompStart.mType == PendingAction::COMPOSITION_START);
MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
mComposition.Start(aCompositionView, aCompStart.mSelectionStart,
GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
@ -6979,9 +7118,9 @@ TSFTextStore::Content::RestoreCommittedComposition(
MOZ_ASSERT(aCompositionView);
MOZ_ASSERT(!mComposition.mView);
MOZ_ASSERT(aPendingCompositionStart.mType ==
PendingAction::COMPOSITION_START);
PendingAction::Type::eCompositionStart);
MOZ_ASSERT(aCanceledCompositionEnd.mType ==
PendingAction::COMPOSITION_END);
PendingAction::Type::eCompositionEnd);
MOZ_ASSERT(GetSubstring(
static_cast<uint32_t>(aPendingCompositionStart.mSelectionStart),
static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
@ -7000,7 +7139,7 @@ TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd)
{
MOZ_ASSERT(mInitialized);
MOZ_ASSERT(mComposition.mView);
MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END);
MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
mComposition.End();

View File

@ -399,9 +399,31 @@ protected:
DWORD mLockQueued;
uint32_t mHandlingKeyMessage;
void OnStartToHandleKeyMessage() { ++mHandlingKeyMessage; }
void OnEndHandlingKeyMessage()
void OnStartToHandleKeyMessage()
{
// If we're starting to handle another key message during handling a
// key message, let's assume that the handling key message is handled by
// TIP and it sends another key message for hacking something.
// Let's try to dispatch a keyboard event now.
// FYI: All callers of this method grab this instance with local variable.
// So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
// we're safe to access any members.
if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) {
MaybeDispatchKeyboardEventAsProcessedByIME();
}
++mHandlingKeyMessage;
}
void OnEndHandlingKeyMessage(bool aIsProcessedByTSF)
{
// If sHandlingKeyMsg has been handled by TSF or TIP and we're still
// alive, but we haven't dispatch keyboard event for it, let's fire it now.
// FYI: All callers of this method grab this instance with local variable.
// So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
// we're safe to access any members.
if (!mDestroyed && sHandlingKeyMsg &&
aIsProcessedByTSF && !sIsKeyboardEventDispatched) {
MaybeDispatchKeyboardEventAsProcessedByIME();
}
MOZ_ASSERT(mHandlingKeyMessage);
if (--mHandlingKeyMessage) {
return;
@ -413,6 +435,21 @@ protected:
}
}
/**
* MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown
* event or eKeyUp event for sHandlingKeyMsg and marking the dispatching
* event as "processed by IME". Note that if the document is locked, this
* just adds a pending action into the queue and sets
* sIsKeyboardEventDispatched to true.
*/
void MaybeDispatchKeyboardEventAsProcessedByIME();
/**
* DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or
* eKeyUp event with NativeKey class and aMsg.
*/
void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg);
class Composition final
{
public:
@ -654,26 +691,29 @@ protected:
struct PendingAction final
{
enum ActionType : uint8_t
enum class Type : uint8_t
{
COMPOSITION_START,
COMPOSITION_UPDATE,
COMPOSITION_END,
SET_SELECTION
eCompositionStart,
eCompositionUpdate,
eCompositionEnd,
eSetSelection,
eKeyboardEvent,
};
ActionType mType;
// For compositionstart and selectionset
Type mType;
// For eCompositionStart and eSetSelection
LONG mSelectionStart;
LONG mSelectionLength;
// For compositionstart, compositionupdate and compositionend
// For eCompositionStart, eCompositionUpdate and eCompositionEnd
nsString mData;
// For compositionupdate
// For eCompositionUpdate
RefPtr<TextRangeArray> mRanges;
// For selectionset
// For eKeyboardEvent
const MSG* mKeyMsg;
// For eSetSelection
bool mSelectionReversed;
// For compositionupdate
// For eCompositionUpdate
bool mIncomplete;
// For compositionstart
// For eCompositionStart
bool mAdjustSelection;
};
// Items of mPendingActions are appended when TSF tells us to need to dispatch
@ -686,12 +726,12 @@ protected:
{
if (!mPendingActions.IsEmpty()) {
PendingAction& lastAction = mPendingActions.LastElement();
if (lastAction.mType == PendingAction::COMPOSITION_UPDATE) {
if (lastAction.mType == PendingAction::Type::eCompositionUpdate) {
return &lastAction;
}
}
PendingAction* newAction = mPendingActions.AppendElement();
newAction->mType = PendingAction::COMPOSITION_UPDATE;
newAction->mType = PendingAction::Type::eCompositionUpdate;
newAction->mRanges = new TextRangeArray();
newAction->mIncomplete = true;
return newAction;
@ -704,7 +744,7 @@ protected:
* @param aStart The inserted offset you expected.
* @param aLength The inserted text length you expected.
* @return true if the last pending actions are
* COMPOSITION_START and COMPOSITION_END and
* eCompositionStart and eCompositionEnd and
* aStart and aLength match their information.
*/
bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const
@ -713,13 +753,14 @@ protected:
return false;
}
const PendingAction& pendingLastAction = mPendingActions.LastElement();
if (pendingLastAction.mType != PendingAction::COMPOSITION_END ||
if (pendingLastAction.mType != PendingAction::Type::eCompositionEnd ||
pendingLastAction.mData.Length() != ULONG(aLength)) {
return false;
}
const PendingAction& pendingPreLastAction =
mPendingActions[mPendingActions.Length() - 2];
return pendingPreLastAction.mType == PendingAction::COMPOSITION_START &&
return pendingPreLastAction.mType ==
PendingAction::Type::eCompositionStart &&
pendingPreLastAction.mSelectionStart == aStart;
}
@ -729,7 +770,7 @@ protected:
return false;
}
const PendingAction& lastAction = mPendingActions.LastElement();
return lastAction.mType == PendingAction::COMPOSITION_UPDATE &&
return lastAction.mType == PendingAction::Type::eCompositionUpdate &&
lastAction.mIncomplete;
}
@ -1112,8 +1153,15 @@ private:
static already_AddRefed<ITfInputProcessorProfiles>
GetInputProcessorProfiles();
// Handling key message.
static const MSG* sHandlingKeyMsg;
// TSF client ID for the current application
static DWORD sClientId;
// true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already
// been dispatched.
static bool sIsKeyboardEventDispatched;
};
} // namespace widget