michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WinIMEHandler.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsIMM32Handler.h" michael@0: #include "nsWindowDefs.h" michael@0: michael@0: #ifdef NS_ENABLE_TSF michael@0: #include "nsTextStore.h" michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: #include "nsWindow.h" michael@0: #include "WinUtils.h" michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: michael@0: /****************************************************************************** michael@0: * IMEHandler michael@0: ******************************************************************************/ michael@0: michael@0: #ifdef NS_ENABLE_TSF michael@0: bool IMEHandler::sIsInTSFMode = false; michael@0: bool IMEHandler::sIsIMMEnabled = true; michael@0: bool IMEHandler::sPluginHasFocus = false; michael@0: decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr; michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: // static michael@0: void michael@0: IMEHandler::Initialize() michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: nsTextStore::Initialize(); michael@0: sIsInTSFMode = nsTextStore::IsInTSFMode(); michael@0: sIsIMMEnabled = michael@0: !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true); michael@0: if (!sIsInTSFMode) { michael@0: // When full nsTextStore is not available, try to use SetInputScopes API michael@0: // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to michael@0: // ensure that msctf.dll will not be unloaded. michael@0: HMODULE module = nullptr; michael@0: if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll", michael@0: &module)) { michael@0: sSetInputScopes = reinterpret_cast( michael@0: GetProcAddress(module, "SetInputScopes")); michael@0: } michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: nsIMM32Handler::Initialize(); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEHandler::Terminate() michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (sIsInTSFMode) { michael@0: nsTextStore::Terminate(); michael@0: sIsInTSFMode = false; michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: nsIMM32Handler::Terminate(); michael@0: } michael@0: michael@0: // static michael@0: void* michael@0: IMEHandler::GetNativeData(uint32_t aDataType) michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: void* result = nsTextStore::GetNativeData(aDataType); michael@0: if (!result || !(*(static_cast(result)))) { michael@0: return nullptr; michael@0: } michael@0: // XXX During the TSF module test, sIsInTSFMode must be true. After that, michael@0: // the value should be restored but currently, there is no way for that. michael@0: // When the TSF test is enabled again, we need to fix this. Perhaps, michael@0: // sending a message can fix this. michael@0: sIsInTSFMode = true; michael@0: return result; michael@0: #else // #ifdef NS_ENABLE_TSF michael@0: return nullptr; michael@0: #endif // #ifdef NS_ENABLE_TSF #else michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (IsTSFAvailable()) { michael@0: return nsTextStore::ProcessRawKeyMessage(aMsg); michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: return false; // noting to do in IMM mode. michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage, michael@0: WPARAM& aWParam, LPARAM& aLParam, michael@0: MSGResult& aResult) michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (IsTSFAvailable()) { michael@0: nsTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult); michael@0: if (aResult.mConsumed) { michael@0: return true; michael@0: } michael@0: // If we don't support IMM in TSF mode, we don't use nsIMM32Handler. michael@0: if (!sIsIMMEnabled) { michael@0: return false; michael@0: } michael@0: // IME isn't implemented with IMM, nsIMM32Handler shouldn't handle any michael@0: // messages. michael@0: if (!nsTextStore::IsIMM_IME()) { michael@0: return false; michael@0: } michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: return nsIMM32Handler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, michael@0: aResult); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: IMEHandler::IsComposing() michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (IsTSFAvailable()) { michael@0: return nsTextStore::IsComposing(); michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: return nsIMM32Handler::IsComposing(); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: IMEHandler::IsComposingOn(nsWindow* aWindow) michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (IsTSFAvailable()) { michael@0: return nsTextStore::IsComposingOn(aWindow); michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: return nsIMM32Handler::IsComposingOn(aWindow); michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: IMEHandler::NotifyIME(nsWindow* aWindow, michael@0: const IMENotification& aIMENotification) michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (IsTSFAvailable()) { michael@0: switch (aIMENotification.mMessage) { michael@0: case NOTIFY_IME_OF_SELECTION_CHANGE: michael@0: return nsTextStore::OnSelectionChange(); michael@0: case NOTIFY_IME_OF_TEXT_CHANGE: michael@0: return nsTextStore::OnTextChange(aIMENotification); michael@0: case NOTIFY_IME_OF_FOCUS: michael@0: return nsTextStore::OnFocusChange(true, aWindow, michael@0: aWindow->GetInputContext().mIMEState.mEnabled); michael@0: case NOTIFY_IME_OF_BLUR: michael@0: return nsTextStore::OnFocusChange(false, aWindow, michael@0: aWindow->GetInputContext().mIMEState.mEnabled); michael@0: case REQUEST_TO_COMMIT_COMPOSITION: michael@0: if (nsTextStore::IsComposingOn(aWindow)) { michael@0: nsTextStore::CommitComposition(false); michael@0: } michael@0: return NS_OK; michael@0: case REQUEST_TO_CANCEL_COMPOSITION: michael@0: if (nsTextStore::IsComposingOn(aWindow)) { michael@0: nsTextStore::CommitComposition(true); michael@0: } michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_POSITION_CHANGE: michael@0: return nsTextStore::OnLayoutChange(); michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: #endif //NS_ENABLE_TSF michael@0: michael@0: switch (aIMENotification.mMessage) { michael@0: case REQUEST_TO_COMMIT_COMPOSITION: michael@0: nsIMM32Handler::CommitComposition(aWindow); michael@0: return NS_OK; michael@0: case REQUEST_TO_CANCEL_COMPOSITION: michael@0: nsIMM32Handler::CancelComposition(aWindow); michael@0: return NS_OK; michael@0: case NOTIFY_IME_OF_POSITION_CHANGE: michael@0: case NOTIFY_IME_OF_COMPOSITION_UPDATE: michael@0: nsIMM32Handler::OnUpdateComposition(aWindow); michael@0: return NS_OK; michael@0: #ifdef NS_ENABLE_TSF michael@0: case NOTIFY_IME_OF_BLUR: michael@0: // If a plugin gets focus while TSF has focus, we need to notify TSF of michael@0: // the blur. michael@0: if (nsTextStore::ThinksHavingFocus()) { michael@0: return nsTextStore::OnFocusChange(false, aWindow, michael@0: aWindow->GetInputContext().mIMEState.mEnabled); michael@0: } michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif //NS_ENABLE_TSF michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: michael@0: // static michael@0: nsIMEUpdatePreference michael@0: IMEHandler::GetUpdatePreference() michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (IsTSFAvailable()) { michael@0: return nsTextStore::GetIMEUpdatePreference(); michael@0: } michael@0: #endif //NS_ENABLE_TSF michael@0: michael@0: return nsIMM32Handler::GetIMEUpdatePreference(); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: IMEHandler::GetOpenState(nsWindow* aWindow) michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (IsTSFAvailable()) { michael@0: return nsTextStore::GetIMEOpenState(); michael@0: } michael@0: #endif //NS_ENABLE_TSF michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: return IMEContext.GetOpenState(); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEHandler::OnDestroyWindow(nsWindow* aWindow) michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: // We need to do nothing here for TSF. Just restore the default context michael@0: // if it's been disassociated. michael@0: if (!sIsInTSFMode) { michael@0: // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use michael@0: // SetInputScopes API. Use an empty string to do this. michael@0: SetInputScopeForIMM32(aWindow, EmptyString()); michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: AssociateIMEContext(aWindow, true); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEHandler::SetInputContext(nsWindow* aWindow, michael@0: InputContext& aInputContext, michael@0: const InputContextAction& aAction) michael@0: { michael@0: // FYI: If there is no composition, this call will do nothing. michael@0: NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION)); michael@0: michael@0: const InputContext& oldInputContext = aWindow->GetInputContext(); michael@0: michael@0: // Assume that SetInputContext() is called only when aWindow has focus. michael@0: sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN); michael@0: michael@0: bool enable = WinUtils::IsIMEEnabled(aInputContext); michael@0: bool adjustOpenState = (enable && michael@0: aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE); michael@0: bool open = (adjustOpenState && michael@0: aInputContext.mIMEState.mOpen == IMEState::OPEN); michael@0: michael@0: aInputContext.mNativeIMEContext = nullptr; michael@0: michael@0: #ifdef NS_ENABLE_TSF michael@0: // Note that even while a plugin has focus, we need to notify TSF of that. michael@0: if (sIsInTSFMode) { michael@0: nsTextStore::SetInputContext(aWindow, aInputContext, aAction); michael@0: if (IsTSFAvailable()) { michael@0: aInputContext.mNativeIMEContext = nsTextStore::GetTextStore(); michael@0: if (sIsIMMEnabled) { michael@0: // Associate IME context for IMM-IMEs. michael@0: AssociateIMEContext(aWindow, enable); michael@0: } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) { michael@0: // Disassociate the IME context from the window when plugin loses focus michael@0: // in pure TSF mode. michael@0: AssociateIMEContext(aWindow, false); michael@0: } michael@0: if (adjustOpenState) { michael@0: nsTextStore::SetIMEOpenState(open); michael@0: } michael@0: return; michael@0: } michael@0: } else { michael@0: // Set at least InputScope even when TextStore is not available. michael@0: SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType); michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: AssociateIMEContext(aWindow, enable); michael@0: michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: if (adjustOpenState) { michael@0: IMEContext.SetOpenState(open); michael@0: } michael@0: michael@0: if (aInputContext.mNativeIMEContext) { michael@0: return; michael@0: } michael@0: michael@0: // The old InputContext must store the default IMC or old TextStore. michael@0: // When IME context is disassociated from the window, use it. michael@0: aInputContext.mNativeIMEContext = enable ? michael@0: static_cast(IMEContext.get()) : oldInputContext.mNativeIMEContext; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEHandler::AssociateIMEContext(nsWindow* aWindow, bool aEnable) michael@0: { michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: if (aEnable) { michael@0: IMEContext.AssociateDefaultContext(); michael@0: return; michael@0: } michael@0: // Don't disassociate the context after the window is destroyed. michael@0: if (aWindow->Destroyed()) { michael@0: return; michael@0: } michael@0: IMEContext.Disassociate(); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEHandler::InitInputContext(nsWindow* aWindow, InputContext& aInputContext) michael@0: { michael@0: // For a11y, the default enabled state should be 'enabled'. michael@0: aInputContext.mIMEState.mEnabled = IMEState::ENABLED; michael@0: michael@0: #ifdef NS_ENABLE_TSF michael@0: if (sIsInTSFMode) { michael@0: nsTextStore::SetInputContext(aWindow, aInputContext, michael@0: InputContextAction(InputContextAction::CAUSE_UNKNOWN, michael@0: InputContextAction::GOT_FOCUS)); michael@0: aInputContext.mNativeIMEContext = nsTextStore::GetTextStore(); michael@0: MOZ_ASSERT(aInputContext.mNativeIMEContext); michael@0: // IME context isn't necessary in pure TSF mode. michael@0: if (!sIsIMMEnabled) { michael@0: AssociateIMEContext(aWindow, false); michael@0: } michael@0: return; michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: // NOTE: mNativeIMEContext may be null if IMM module isn't installed. michael@0: nsIMEContext IMEContext(aWindow->GetWindowHandle()); michael@0: aInputContext.mNativeIMEContext = static_cast(IMEContext.get()); michael@0: MOZ_ASSERT(aInputContext.mNativeIMEContext || !CurrentKeyboardLayoutHasIME()); michael@0: // If no IME context is available, we should set the widget's pointer since michael@0: // nullptr indicates there is only one context per process on the platform. michael@0: if (!aInputContext.mNativeIMEContext) { michael@0: aInputContext.mNativeIMEContext = static_cast(aWindow); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // static michael@0: bool michael@0: IMEHandler::CurrentKeyboardLayoutHasIME() michael@0: { michael@0: #ifdef NS_ENABLE_TSF michael@0: if (sIsInTSFMode) { michael@0: return nsTextStore::CurrentKeyboardLayoutHasIME(); michael@0: } michael@0: #endif // #ifdef NS_ENABLE_TSF michael@0: michael@0: return nsIMM32Handler::IsIMEAvailable(); michael@0: } michael@0: #endif // #ifdef DEBUG michael@0: michael@0: // static michael@0: void michael@0: IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow, michael@0: const nsAString& aHTMLInputType) michael@0: { michael@0: if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) { michael@0: return; michael@0: } michael@0: UINT arraySize = 0; michael@0: const InputScope* scopes = nullptr; michael@0: // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html michael@0: if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) { michael@0: static const InputScope inputScopes[] = { IS_DEFAULT }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("url")) { michael@0: static const InputScope inputScopes[] = { IS_URL }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("search")) { michael@0: static const InputScope inputScopes[] = { IS_SEARCH }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("email")) { michael@0: static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("password")) { michael@0: static const InputScope inputScopes[] = { IS_PASSWORD }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("datetime") || michael@0: aHTMLInputType.EqualsLiteral("datetime-local")) { michael@0: static const InputScope inputScopes[] = { michael@0: IS_DATE_FULLDATE, IS_TIME_FULLTIME }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("date") || michael@0: aHTMLInputType.EqualsLiteral("month") || michael@0: aHTMLInputType.EqualsLiteral("week")) { michael@0: static const InputScope inputScopes[] = { IS_DATE_FULLDATE }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("time")) { michael@0: static const InputScope inputScopes[] = { IS_TIME_FULLTIME }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("tel")) { michael@0: static const InputScope inputScopes[] = { michael@0: IS_TELEPHONE_FULLTELEPHONENUMBER, IS_TELEPHONE_LOCALNUMBER }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } else if (aHTMLInputType.EqualsLiteral("number")) { michael@0: static const InputScope inputScopes[] = { IS_NUMBER }; michael@0: scopes = &inputScopes[0]; michael@0: arraySize = ArrayLength(inputScopes); michael@0: } michael@0: if (scopes && arraySize > 0) { michael@0: sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0, michael@0: nullptr, nullptr); michael@0: } michael@0: } michael@0: michael@0: } // namespace widget michael@0: } // namespace mozilla