/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sts=2 sw=2 et cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#define IMEMOUSE_NONE 0x00 // no mouse button was pushed #define IMEMOUSE_LDOWN 0x01 #define IMEMOUSE_RDOWN 0x02 #define IMEMOUSE_MDOWN 0x04 #define IMEMOUSE_WUP 0x10 // wheel up #define IMEMOUSE_WDOWN 0x20 // wheel down
// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync` // rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too // big file. // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior. extern mozilla::LazyLogModule gIMELog;
IMPL_IS_IME_ACTIVE(ATOK2006, u"ATOK 2006")
IMPL_IS_IME_ACTIVE(ATOK2007, u"ATOK 2007")
IMPL_IS_IME_ACTIVE(ATOK2008, u"ATOK 2008")
IMPL_IS_IME_ACTIVE(ATOK2009, u"ATOK 2009")
IMPL_IS_IME_ACTIVE(ATOK2010, u"ATOK 2010") // NOTE: Even on Windows for en-US, the name of Google Japanese Input is // written in Japanese.
IMPL_IS_IME_ACTIVE(GoogleJapaneseInput,
u"Google \x65E5\x672C\x8A9E\x5165\x529B "
u"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB")
IMPL_IS_IME_ACTIVE(Japanist2003, u"Japanist 2003")
#undef IMPL_IS_IME_ACTIVE
// static bool IMMHandler::IsActiveIMEInBlockList() { if (sIMEName.IsEmpty()) { returnfalse;
} #ifdef _WIN64 // ATOK started to be TIP of TSF since 2011. Older than it, i.e., ATOK 2010 // and earlier have a lot of problems even for daily use. Perhaps, the // reason is Win 8 has a lot of changes around IMM-IME support and TSF, // and ATOK 2010 is released earlier than Win 8. // ATOK 2006 crashes while converting a word with candidate window. // ATOK 2007 doesn't paint and resize suggest window and candidate window // correctly (showing white window or too big window). // ATOK 2008 and ATOK 2009 crash when user just opens their open state. // ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of // crash reports. if ((IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
IsATOK2009Active() || IsATOK2010Active())) { returntrue;
} #endif// #ifdef _WIN64 returnfalse;
}
// static void IMMHandler::EnsureHandlerInstance() { if (!gIMMHandler) {
gIMMHandler = new IMMHandler();
}
}
// static bool IMMHandler::ShouldDrawCompositionStringOurselves() { // If current IME has special UI or its composition window should not // positioned to caret position, we should now draw composition string // ourselves. return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
(sIMEProperty & IME_PROP_AT_CARET);
}
// static bool IMMHandler::IsVerticalWritingSupported() { // Even if IME claims that they support vertical writing mode but it may not // support vertical writing mode for its candidate window. if (sAssumeVerticalWritingModeNotSupported) { returnfalse;
} // Google Japanese Input doesn't support vertical writing mode. We should // return false if it's active IME. if (IsGoogleJapaneseInputActive()) { returnfalse;
} return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
}
// static void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) {
UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0); if (IMENameLength) { // Add room for the terminating null character
sIMEName.SetLength(++IMENameLength);
IMENameLength =
::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength); // Adjust the length to ignore the terminating null character
sIMEName.SetLength(IMENameLength);
} else {
sIMEName.Truncate();
}
// If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API. // For hacking some bugs of some TIP, we should set an IME name from the // pref. if (sCodePage == 932 && sIMEName.IsEmpty()) {
Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
sIMEName);
}
// Whether the IME supports vertical writing mode might be changed or // some IMEs may need specific font for their UI. Therefore, we should // update composition font forcibly here. if (aWindow) {
MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true);
}
// used for checking the lParam of WM_IME_COMPOSITION #define IS_COMPOSING_LPARAM(lParam) \
((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS)) #define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR) // Some IMEs (e.g., the standard IME for Korean) don't have caret position, // then, we should not set caret position to compositionchange event. #define NO_IME_CARET -1
IMMHandler::~IMMHandler() { if (mIsComposing) {
MOZ_LOG(
gIMELog, LogLevel::Error,
(" IMMHandler::~IMMHandler, ERROR, the instance is still composing"));
}
MOZ_LOG(gIMELog, LogLevel::Debug, ("IMMHandler::IMMHandler is destroyed"));
}
// static void IMMHandler::OnSelectionChange(nsWindow* aWindow, const IMENotification& aIMENotification, bool aIsIMMActive) { if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
aIsIMMActive) {
MaybeAdjustCompositionFont(
aWindow, aIMENotification.mSelectionChangeData.GetWritingMode());
} // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it // after a call of MaybeAdjustCompositionFont(). if (gIMMHandler) {
gIMMHandler->mContentSelection =
Some(ContentSelection(aIMENotification.mSelectionChangeData));
}
}
// static void IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow, const WritingMode& aWritingMode, bool aForceUpdate) { switch (sCodePage) { case 932: // Japanese Shift-JIS case 936: // Simlified Chinese GBK case 949: // Korean case 950: // Traditional Chinese Big5
EnsureHandlerInstance(); break; default: // If there is no instance of nsIMM32Hander, we shouldn't waste footprint. if (!gIMMHandler) { return;
}
}
// Like Navi-Bar of ATOK, some IMEs may require proper composition font even // before sending WM_IME_STARTCOMPOSITION.
IMEContext context(aWindow);
gIMMHandler->AdjustCompositionFont(aWindow, context, aWritingMode,
aForceUpdate);
}
// static bool IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
LPARAM lParam,
MSGResult& aResult) {
aResult.mResult = 0;
aResult.mConsumed = false; // We don't need to create the instance of the handler here. if (gIMMHandler) {
gIMMHandler->OnInputLangChange(aWindow, wParam, lParam, aResult);
}
InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam)); // We can release the instance here, because the instance may be never // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
Terminate(); // Don't return as "processed", the messages should be processed on nsWindow // too. returnfalse;
}
// static bool IMMHandler::ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
LPARAM& lParam, MSGResult& aResult) { // XXX We store the composing window in mComposingWindow. If IME messages are // sent to different window, we should commit the old transaction. And also // if the new window handle is not focused, probably, we should not start // the composition, however, such case should not be, it's just bad scenario.
aResult.mResult = 0; switch (msg) { case WM_INPUTLANGCHANGE: return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); case WM_IME_STARTCOMPOSITION:
EnsureHandlerInstance(); return gIMMHandler->OnIMEStartComposition(aWindow, aResult); case WM_IME_COMPOSITION:
EnsureHandlerInstance(); return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult); case WM_IME_ENDCOMPOSITION:
EnsureHandlerInstance(); return gIMMHandler->OnIMEEndComposition(aWindow, aResult); case WM_IME_CHAR: return OnIMEChar(aWindow, wParam, lParam, aResult); case WM_IME_NOTIFY: return OnIMENotify(aWindow, wParam, lParam, aResult); case WM_IME_REQUEST:
EnsureHandlerInstance(); return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult); case WM_IME_SELECT: return OnIMESelect(aWindow, wParam, lParam, aResult); case WM_IME_SETCONTEXT: return OnIMESetContext(aWindow, wParam, lParam, aResult); case WM_KEYDOWN: return OnKeyDownEvent(aWindow, wParam, lParam, aResult); case WM_CHAR: if (!gIMMHandler) { returnfalse;
} return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult); default: returnfalse;
};
}
aResult.mConsumed = ShouldDrawCompositionStringOurselves(); if (!mIsComposing) { returntrue;
}
// Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during // composition. Then, we should ignore the message and commit the composition // string at following WM_IME_COMPOSITION.
MSG compositionMsg; if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
PM_NOREMOVE) &&
compositionMsg.message == WM_IME_COMPOSITION &&
IS_COMMITTING_LPARAM(compositionMsg.lParam)) {
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::OnIMEEndComposition, WM_IME_ENDCOMPOSITION is " "followed by WM_IME_COMPOSITION, ignoring the message...")); returntrue;
}
// Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before // WM_IME_ENDCOMPOSITION when composition string becomes empty. // Then, we should dispatch a compositionupdate event, a compositionchange // event and a compositionend event. // XXX Shouldn't we dispatch the compositionchange event with actual or // latest composition string?
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::OnIMEEndComposition, mCompositionString=\"%s\"%s",
NS_ConvertUTF16toUTF8(mCompositionString).get(),
mCompositionString.IsEmpty() ? "" : ", but canceling it..."));
// We don't need to fire any compositionchange events from here. This method // will be called when the composition string of the current IME is not drawn // by us and some characters are committed. In that case, the committed // string was processed in nsWindow::OnIMEComposition already.
// We need to consume the message so that Windows don't send two WM_CHAR msgs
aResult.mConsumed = true; returntrue;
}
// NOTE: If the aWindow is top level window of the composing window because // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is // TRUE) is sent to the top level window first. After that, // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window. // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window. // The top level window never becomes composing window, so, we can ignore // the WM_IME_SETCONTEXT on the top level window. if (IsTopLevelWindowOfComposition(aWindow)) {
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::OnIMESetContext, hWnd=%p is top level window",
aWindow->GetWindowHandle())); returntrue;
}
// When IME context is activating on another window, // we should commit the old composition on the old window. bool cancelComposition = false; if (wParam && gIMMHandler) {
cancelComposition = gIMMHandler->CommitCompositionOnPreviousWindow(aWindow);
}
// We should sent WM_IME_SETCONTEXT to the DefWndProc here because the // ancestor windows shouldn't receive this message. If they receive the // message, we cannot know whether which window is the target of the message.
aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
WM_IME_SETCONTEXT, wParam, lParam);
// Cancel composition on the new window if we committed our composition on // another window. if (cancelComposition) {
CancelComposition(aWindow, true);
}
aResult.mConsumed = true; returntrue;
}
bool IMMHandler::OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
MSGResult& aResult) { // The return value must be same as aResult.mConsumed because only when we // consume the message, the caller shouldn't do anything anymore but // otherwise, the caller should handle the message.
aResult.mConsumed = false; if (IsIMECharRecordsEmpty()) { return aResult.mConsumed;
}
WPARAM recWParam;
LPARAM recLParam;
DequeueIMECharRecords(recWParam, recLParam);
MOZ_LOG(
gIMELog, LogLevel::Info,
("IMMHandler::OnChar, aWindow=%p, wParam=%08zx, lParam=%08" PRIxLPTR ", " "recorded: wParam=%08zx, lParam=%08" PRIxLPTR,
aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam)); // If an unexpected char message comes, we should reset the records, // of course, this shouldn't happen. if (recWParam != wParam || recLParam != lParam) {
ResetIMECharRecords(); return aResult.mConsumed;
} // Eat the char message which is caused by WM_IME_CHAR because we should // have processed the IME messages, so, this message could be come from // a windowless plug-in.
aResult.mConsumed = true; return aResult.mConsumed;
}
void IMMHandler::HandleStartComposition(nsWindow* aWindow, const IMEContext& aContext) {
MOZ_ASSERT(!mIsComposing, "HandleStartComposition is called but mIsComposing is TRUE");
const Maybe<ContentSelection>& contentSelection =
GetContentSelectionWithQueryIfNothing(aWindow); if (contentSelection.isNothing()) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::HandleStartComposition, FAILED, due to " "Selection::GetContentSelectionWithQueryIfNothing() failure")); return;
} if (!contentSelection->HasRange()) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::HandleStartComposition, FAILED, due to " "there is no selection")); return;
}
bool IMMHandler::HandleComposition(nsWindow* aWindow, const IMEContext& aContext, LPARAM lParam) { // for bug #60050 // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion // mode before it send WM_IME_STARTCOMPOSITION. // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION, // and if we access ATOK via some APIs, ATOK will sometimes fail to // initialize its state. If WM_IME_STARTCOMPOSITION is already in the // message queue, we should ignore the strange WM_IME_COMPOSITION message and // skip to the next. So, we should look for next composition message // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION), // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we // should start composition forcibly. if (!mIsComposing) {
MSG msg1, msg2;
HWND wnd = aWindow->GetWindowHandle(); if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION,
WM_IME_COMPOSITION, PM_NOREMOVE) &&
msg1.message == WM_IME_STARTCOMPOSITION &&
WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION,
WM_IME_COMPOSITION, PM_NOREMOVE) &&
msg2.message == WM_IME_COMPOSITION) {
MOZ_LOG(gIMELog, LogLevel::Info,
("IMMHandler::HandleComposition, Ignores due to find a " "WM_IME_STARTCOMPOSITION")); return ShouldDrawCompositionStringOurselves();
}
}
// If composition string isn't changed, we can trust the lParam. // So, we need to do nothing. if (previousCompositionString == mCompositionString) { return ShouldDrawCompositionStringOurselves();
}
// IME may send WM_IME_COMPOSITION without composing lParam values // when composition string becomes empty (e.g., using Backspace key). // If composition string is empty, we should dispatch a compositionchange // event with empty string and clear the clause information. if (mCompositionString.IsEmpty()) {
mClauseArray.Clear();
mAttributeArray.Clear();
mCursorPosition = 0;
DispatchCompositionChangeEvent(aWindow, aContext); return ShouldDrawCompositionStringOurselves();
}
// Otherwise, we cannot trust the lParam value. We might need to // dispatch compositionchange event with the latest composition string // information.
}
// See https://bugzilla.mozilla.org/show_bug.cgi?id=296339 if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) { // In this case, maybe, the sender is MSPinYin. That sends *only* // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when // user inputted the Chinese full stop. So, that doesn't send // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION. // If WM_IME_STARTCOMPOSITION was not sent and the composition // string is null (it indicates the composition transaction ended), // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run // HandleEndComposition() in other place.
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::HandleComposition, Aborting GCS_COMPSTR"));
HandleEndComposition(aWindow); return IS_COMMITTING_LPARAM(lParam);
}
//-------------------------------------------------------- // 2. Get GCS_COMPCLAUSE //-------------------------------------------------------- long clauseArrayLength =
::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, nullptr, 0);
clauseArrayLength /= sizeof(uint32_t);
// Intelligent ABC IME (Simplified Chinese IME, the code page is 936) // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663). // See comment 35 of the bug for the detail. Therefore, we should use A // API for it, however, we should not kill Unicode support on all IMEs. bool useA_API = !(sIMEProperty & IME_PROP_UNICODE);
if (clauseArrayLength != clauseArrayLength2) {
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::HandleComposition, GCS_COMPCLAUSE, " "clauseArrayLength=%ld but clauseArrayLength2=%ld",
clauseArrayLength, clauseArrayLength2)); if (clauseArrayLength > clauseArrayLength2)
clauseArrayLength = clauseArrayLength2;
}
if (useA_API && clauseArrayLength > 0) { // Convert each values of sIMECompClauseArray. The values mean offset of // the clauses in ANSI string. But we need the values in Unicode string.
nsAutoCString compANSIStr; if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(),
compANSIStr)) {
uint32_t maxlen = compANSIStr.Length();
mClauseArray.SetLength(clauseArrayLength);
mClauseArray[0] = 0; // first value must be 0 for (int32_t i = 1; i < clauseArrayLength; i++) {
uint32_t len = std::min(mClauseArray[i], maxlen);
mClauseArray[i] =
::MultiByteToWideChar(GetKeyboardCodePage(), MB_PRECOMPOSED,
(LPCSTR)compANSIStr.get(), len, nullptr, 0);
}
}
}
} // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW // may return an error code.
mClauseArray.SetLength(std::max<long>(0, clauseArrayLength));
//-------------------------------------------------------- // 3. Get GCS_COMPATTR //-------------------------------------------------------- // This provides us with the attribute string necessary // for doing hiliting long attrArrayLength =
::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, nullptr, 0);
attrArrayLength /= sizeof(uint8_t);
// attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an // error code.
mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
//-------------------------------------------------------- // 4. Get GCS_CURSOPOS //-------------------------------------------------------- // Some IMEs (e.g., the standard IME for Korean) don't have caret position. if (lParam & GCS_CURSORPOS) {
mCursorPosition =
::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0); if (mCursorPosition < 0) {
mCursorPosition = NO_IME_CARET; // The result is error
}
} else {
mCursorPosition = NO_IME_CARET;
}
//-------------------------------------------------------- // 5. Send the compositionchange event //--------------------------------------------------------
DispatchCompositionChangeEvent(aWindow, aContext);
return ShouldDrawCompositionStringOurselves();
}
void IMMHandler::HandleEndComposition(nsWindow* aWindow, const nsAString* aCommitString) {
MOZ_ASSERT(mIsComposing, "HandleEndComposition is called but mIsComposing is FALSE");
LayoutDeviceIntRect r; bool ret =
GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
NS_ENSURE_TRUE(ret, false);
LayoutDeviceIntRect screenRect; // We always need top level window that is owner window of the popup window // even if the content of the popup window has focus.
ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), r, nullptr, screenRect);
// XXX This might need to check writing mode. However, MSDN doesn't explain // how to set the values in vertical writing mode. Additionally, IME // doesn't work well with top-left of the character (this is explicitly // documented) and its horizontal width. So, it might be better to set // top-right corner of the character and horizontal width, but we're not // sure if it doesn't cause any problems with a lot of IMEs...
pCharPosition->pt.x = screenRect.X();
pCharPosition->pt.y = screenRect.Y();
int32_t targetOffset, targetLength; if (!hasCompositionString) { const Maybe<ContentSelection>& contentSelection =
GetContentSelectionWithQueryIfNothing(aWindow); if (contentSelection.isNothing()) {
MOZ_LOG(gIMELog, LogLevel::Error,
("IMMHandler::HandleDocumentFeed, FAILED, due to " "Selection::GetContentSelectionWithQueryIfNothing() failure")); returnfalse;
} if (contentSelection->HasRange()) {
targetOffset = static_cast<int32_t>(
contentSelection->OffsetAndDataRef().StartOffset());
targetLength = static_cast<int32_t>(contentSelection->OffsetAndDataRef().Length());
} else { // If there is no selection range, let's return all text in the editor.
targetOffset = 0;
targetLength = INT32_MAX;
}
} else {
targetOffset = int32_t(mCompositionStart);
targetLength = int32_t(mCompositionString.Length());
}
// XXX nsString::Find and nsString::RFind take int32_t for offset, so, // we cannot support this message when the current offset is larger than // INT32_MAX. if (targetOffset < 0 || targetLength < 0 || targetOffset + targetLength < 0) {
MOZ_LOG(gIMELog, LogLevel::Error,
("IMMHandler::HandleDocumentFeed, FAILED, " "due to the selection is out of range")); returnfalse;
}
// Get all contents of the focused editor.
WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
aWindow);
queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
aWindow->InitEvent(queryTextContentEvent, &point);
DispatchEvent(aWindow, queryTextContentEvent); if (NS_WARN_IF(queryTextContentEvent.Failed())) {
MOZ_LOG(gIMELog, LogLevel::Error,
("IMMHandler::HandleDocumentFeed, FAILED, " "due to eQueryTextContent failure")); returnfalse;
}
nsAutoString str(queryTextContentEvent.mReply->DataRef()); if (targetOffset > static_cast<int32_t>(str.Length())) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::HandleDocumentFeed, FAILED, " "due to the caret offset is invalid")); returnfalse;
}
// Get the focused paragraph, we decide that it starts from the previous CRLF // (or start of the editor) to the next one (or the end of the editor).
int32_t paragraphStart = 0; if (targetOffset > 0) {
paragraphStart = Substring(str, 0, targetOffset).RFind(u"\n") + 1;
}
int32_t paragraphEnd = str.Find(u"\r", targetOffset + targetLength); if (paragraphEnd < 0) {
paragraphEnd = str.Length();
}
nsDependentSubstring paragraph(str, paragraphStart,
paragraphEnd - paragraphStart);
uint32_t len = paragraph.Length();
uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
// If we have composition, we should dispatch composition events internally. if (mIsComposing) {
IMEContext context(mComposingWindow);
NS_ASSERTION(context.IsValid(), "IME context must be valid");
// If we don't need to draw composition string ourselves, we don't need to // fire compositionchange event during composing. if (!ShouldDrawCompositionStringOurselves()) { // But we need to adjust composition window pos and native caret pos, here.
SetIMERelatedWindowsPos(aWindow, aContext); return;
}
RefPtr<nsWindow> kungFuDeathGrip(aWindow);
RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
nsresult rv = dispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::DispatchCompositionChangeEvent, FAILED due to " "TextEventDispatcher::BeginNativeInputTransaction() failure")); return;
}
// NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure // in e10s mode. compositionchange event will notify this of // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then // SetIMERelatedWindowsPos() will be called.
// XXX Sogou (Simplified Chinese IME) returns contradictory values: // The cursor position is actual cursor position. However, other values // (composition string and attributes) are empty.
if (mCompositionString.IsEmpty()) { // Don't append clause information if composition string is empty.
} elseif (mClauseArray.IsEmpty()) { // Some IMEs don't return clause array information, then, we assume that // all characters in the composition string are in one clause.
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::DispatchCompositionChangeEvent, " "mClauseArray.Length()=0"));
rv = dispatcher->SetPendingComposition(mCompositionString, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::DispatchCompositionChangeEvent, FAILED due to" "TextEventDispatcher::SetPendingComposition() failure")); return;
}
} else { // iterate over the attributes
rv = dispatcher->SetPendingCompositionString(mCompositionString); if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::DispatchCompositionChangeEvent, FAILED due to" "TextEventDispatcher::SetPendingCompositionString() failure")); return;
}
uint32_t lastOffset = 0; for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) {
uint32_t current = mClauseArray[i + 1]; if (current > mCompositionString.Length()) {
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::DispatchCompositionChangeEvent, " "mClauseArray[%u]=%u. " "This is larger than mCompositionString.Length()=%zu",
i + 1, current, mCompositionString.Length()));
current = int32_t(mCompositionString.Length());
}
uint32_t length = current - lastOffset; if (NS_WARN_IF(lastOffset >= mAttributeArray.Length())) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::DispatchCompositionChangeEvent, FAILED due to " "invalid data of mClauseArray or mAttributeArray")); return;
}
TextRangeType textRangeType =
PlatformToNSAttr(mAttributeArray[lastOffset]);
rv = dispatcher->AppendClauseToPendingComposition(length, textRangeType); if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gIMELog, LogLevel::Error,
(" IMMHandler::DispatchCompositionChangeEvent, FAILED due to" "TextEventDispatcher::AppendClauseToPendingComposition() " "failure")); return;
}
lastOffset = current;
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::DispatchCompositionChangeEvent, index=%u, " "rangeType=%s, range length=%u",
i, ToChar(textRangeType), length));
}
}
if (mCursorPosition == NO_IME_CARET) {
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::DispatchCompositionChangeEvent, no caret"));
} else {
uint32_t cursor = static_cast<uint32_t>(mCursorPosition); if (cursor > mCompositionString.Length()) {
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::CreateTextRangeArray, mCursorPosition=%d. " "This is larger than mCompositionString.Length()=%zu",
mCursorPosition, mCompositionString.Length()));
cursor = mCompositionString.Length();
}
// If caret is in the target clause, the target clause will be painted as // normal selection range. Since caret shouldn't be in selection range on // Windows, we shouldn't append caret range in such case. const TextRangeArray* clauses = dispatcher->GetPendingCompositionClauses(); const TextRange* targetClause =
clauses ? clauses->GetTargetClause() : nullptr; if (targetClause && cursor >= targetClause->mStartOffset &&
cursor <= targetClause->mEndOffset) { // Forget the caret position specified by IME since Gecko's caret position // will be at the end of composition string.
mCursorPosition = NO_IME_CARET;
MOZ_LOG(gIMELog, LogLevel::Info,
(" IMMHandler::CreateTextRangeArray, no caret due to it's in " "the target clause, now, mCursorPosition is NO_IME_CARET"));
}
if (mCursorPosition != NO_IME_CARET) {
rv = dispatcher->SetCaretInPendingComposition(cursor, 0); if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(
gIMELog, LogLevel::Error,
(" IMMHandler::DispatchCompositionChangeEvent, FAILED due to" "TextEventDispatcher::SetCaretInPendingComposition() failure")); return;
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.