1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/windows/WinIMEHandler.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,455 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "WinIMEHandler.h" 1.10 + 1.11 +#include "mozilla/Preferences.h" 1.12 +#include "nsIMM32Handler.h" 1.13 +#include "nsWindowDefs.h" 1.14 + 1.15 +#ifdef NS_ENABLE_TSF 1.16 +#include "nsTextStore.h" 1.17 +#endif // #ifdef NS_ENABLE_TSF 1.18 + 1.19 +#include "nsWindow.h" 1.20 +#include "WinUtils.h" 1.21 + 1.22 +namespace mozilla { 1.23 +namespace widget { 1.24 + 1.25 +/****************************************************************************** 1.26 + * IMEHandler 1.27 + ******************************************************************************/ 1.28 + 1.29 +#ifdef NS_ENABLE_TSF 1.30 +bool IMEHandler::sIsInTSFMode = false; 1.31 +bool IMEHandler::sIsIMMEnabled = true; 1.32 +bool IMEHandler::sPluginHasFocus = false; 1.33 +decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr; 1.34 +#endif // #ifdef NS_ENABLE_TSF 1.35 + 1.36 +// static 1.37 +void 1.38 +IMEHandler::Initialize() 1.39 +{ 1.40 +#ifdef NS_ENABLE_TSF 1.41 + nsTextStore::Initialize(); 1.42 + sIsInTSFMode = nsTextStore::IsInTSFMode(); 1.43 + sIsIMMEnabled = 1.44 + !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true); 1.45 + if (!sIsInTSFMode) { 1.46 + // When full nsTextStore is not available, try to use SetInputScopes API 1.47 + // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to 1.48 + // ensure that msctf.dll will not be unloaded. 1.49 + HMODULE module = nullptr; 1.50 + if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll", 1.51 + &module)) { 1.52 + sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>( 1.53 + GetProcAddress(module, "SetInputScopes")); 1.54 + } 1.55 + } 1.56 +#endif // #ifdef NS_ENABLE_TSF 1.57 + 1.58 + nsIMM32Handler::Initialize(); 1.59 +} 1.60 + 1.61 +// static 1.62 +void 1.63 +IMEHandler::Terminate() 1.64 +{ 1.65 +#ifdef NS_ENABLE_TSF 1.66 + if (sIsInTSFMode) { 1.67 + nsTextStore::Terminate(); 1.68 + sIsInTSFMode = false; 1.69 + } 1.70 +#endif // #ifdef NS_ENABLE_TSF 1.71 + 1.72 + nsIMM32Handler::Terminate(); 1.73 +} 1.74 + 1.75 +// static 1.76 +void* 1.77 +IMEHandler::GetNativeData(uint32_t aDataType) 1.78 +{ 1.79 +#ifdef NS_ENABLE_TSF 1.80 + void* result = nsTextStore::GetNativeData(aDataType); 1.81 + if (!result || !(*(static_cast<void**>(result)))) { 1.82 + return nullptr; 1.83 + } 1.84 + // XXX During the TSF module test, sIsInTSFMode must be true. After that, 1.85 + // the value should be restored but currently, there is no way for that. 1.86 + // When the TSF test is enabled again, we need to fix this. Perhaps, 1.87 + // sending a message can fix this. 1.88 + sIsInTSFMode = true; 1.89 + return result; 1.90 +#else // #ifdef NS_ENABLE_TSF 1.91 + return nullptr; 1.92 +#endif // #ifdef NS_ENABLE_TSF #else 1.93 +} 1.94 + 1.95 +// static 1.96 +bool 1.97 +IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) 1.98 +{ 1.99 +#ifdef NS_ENABLE_TSF 1.100 + if (IsTSFAvailable()) { 1.101 + return nsTextStore::ProcessRawKeyMessage(aMsg); 1.102 + } 1.103 +#endif // #ifdef NS_ENABLE_TSF 1.104 + return false; // noting to do in IMM mode. 1.105 +} 1.106 + 1.107 +// static 1.108 +bool 1.109 +IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage, 1.110 + WPARAM& aWParam, LPARAM& aLParam, 1.111 + MSGResult& aResult) 1.112 +{ 1.113 +#ifdef NS_ENABLE_TSF 1.114 + if (IsTSFAvailable()) { 1.115 + nsTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult); 1.116 + if (aResult.mConsumed) { 1.117 + return true; 1.118 + } 1.119 + // If we don't support IMM in TSF mode, we don't use nsIMM32Handler. 1.120 + if (!sIsIMMEnabled) { 1.121 + return false; 1.122 + } 1.123 + // IME isn't implemented with IMM, nsIMM32Handler shouldn't handle any 1.124 + // messages. 1.125 + if (!nsTextStore::IsIMM_IME()) { 1.126 + return false; 1.127 + } 1.128 + } 1.129 +#endif // #ifdef NS_ENABLE_TSF 1.130 + 1.131 + return nsIMM32Handler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, 1.132 + aResult); 1.133 +} 1.134 + 1.135 +// static 1.136 +bool 1.137 +IMEHandler::IsComposing() 1.138 +{ 1.139 +#ifdef NS_ENABLE_TSF 1.140 + if (IsTSFAvailable()) { 1.141 + return nsTextStore::IsComposing(); 1.142 + } 1.143 +#endif // #ifdef NS_ENABLE_TSF 1.144 + 1.145 + return nsIMM32Handler::IsComposing(); 1.146 +} 1.147 + 1.148 +// static 1.149 +bool 1.150 +IMEHandler::IsComposingOn(nsWindow* aWindow) 1.151 +{ 1.152 +#ifdef NS_ENABLE_TSF 1.153 + if (IsTSFAvailable()) { 1.154 + return nsTextStore::IsComposingOn(aWindow); 1.155 + } 1.156 +#endif // #ifdef NS_ENABLE_TSF 1.157 + 1.158 + return nsIMM32Handler::IsComposingOn(aWindow); 1.159 +} 1.160 + 1.161 +// static 1.162 +nsresult 1.163 +IMEHandler::NotifyIME(nsWindow* aWindow, 1.164 + const IMENotification& aIMENotification) 1.165 +{ 1.166 +#ifdef NS_ENABLE_TSF 1.167 + if (IsTSFAvailable()) { 1.168 + switch (aIMENotification.mMessage) { 1.169 + case NOTIFY_IME_OF_SELECTION_CHANGE: 1.170 + return nsTextStore::OnSelectionChange(); 1.171 + case NOTIFY_IME_OF_TEXT_CHANGE: 1.172 + return nsTextStore::OnTextChange(aIMENotification); 1.173 + case NOTIFY_IME_OF_FOCUS: 1.174 + return nsTextStore::OnFocusChange(true, aWindow, 1.175 + aWindow->GetInputContext().mIMEState.mEnabled); 1.176 + case NOTIFY_IME_OF_BLUR: 1.177 + return nsTextStore::OnFocusChange(false, aWindow, 1.178 + aWindow->GetInputContext().mIMEState.mEnabled); 1.179 + case REQUEST_TO_COMMIT_COMPOSITION: 1.180 + if (nsTextStore::IsComposingOn(aWindow)) { 1.181 + nsTextStore::CommitComposition(false); 1.182 + } 1.183 + return NS_OK; 1.184 + case REQUEST_TO_CANCEL_COMPOSITION: 1.185 + if (nsTextStore::IsComposingOn(aWindow)) { 1.186 + nsTextStore::CommitComposition(true); 1.187 + } 1.188 + return NS_OK; 1.189 + case NOTIFY_IME_OF_POSITION_CHANGE: 1.190 + return nsTextStore::OnLayoutChange(); 1.191 + default: 1.192 + return NS_ERROR_NOT_IMPLEMENTED; 1.193 + } 1.194 + } 1.195 +#endif //NS_ENABLE_TSF 1.196 + 1.197 + switch (aIMENotification.mMessage) { 1.198 + case REQUEST_TO_COMMIT_COMPOSITION: 1.199 + nsIMM32Handler::CommitComposition(aWindow); 1.200 + return NS_OK; 1.201 + case REQUEST_TO_CANCEL_COMPOSITION: 1.202 + nsIMM32Handler::CancelComposition(aWindow); 1.203 + return NS_OK; 1.204 + case NOTIFY_IME_OF_POSITION_CHANGE: 1.205 + case NOTIFY_IME_OF_COMPOSITION_UPDATE: 1.206 + nsIMM32Handler::OnUpdateComposition(aWindow); 1.207 + return NS_OK; 1.208 +#ifdef NS_ENABLE_TSF 1.209 + case NOTIFY_IME_OF_BLUR: 1.210 + // If a plugin gets focus while TSF has focus, we need to notify TSF of 1.211 + // the blur. 1.212 + if (nsTextStore::ThinksHavingFocus()) { 1.213 + return nsTextStore::OnFocusChange(false, aWindow, 1.214 + aWindow->GetInputContext().mIMEState.mEnabled); 1.215 + } 1.216 + return NS_ERROR_NOT_IMPLEMENTED; 1.217 +#endif //NS_ENABLE_TSF 1.218 + default: 1.219 + return NS_ERROR_NOT_IMPLEMENTED; 1.220 + } 1.221 +} 1.222 + 1.223 +// static 1.224 +nsIMEUpdatePreference 1.225 +IMEHandler::GetUpdatePreference() 1.226 +{ 1.227 +#ifdef NS_ENABLE_TSF 1.228 + if (IsTSFAvailable()) { 1.229 + return nsTextStore::GetIMEUpdatePreference(); 1.230 + } 1.231 +#endif //NS_ENABLE_TSF 1.232 + 1.233 + return nsIMM32Handler::GetIMEUpdatePreference(); 1.234 +} 1.235 + 1.236 +// static 1.237 +bool 1.238 +IMEHandler::GetOpenState(nsWindow* aWindow) 1.239 +{ 1.240 +#ifdef NS_ENABLE_TSF 1.241 + if (IsTSFAvailable()) { 1.242 + return nsTextStore::GetIMEOpenState(); 1.243 + } 1.244 +#endif //NS_ENABLE_TSF 1.245 + 1.246 + nsIMEContext IMEContext(aWindow->GetWindowHandle()); 1.247 + return IMEContext.GetOpenState(); 1.248 +} 1.249 + 1.250 +// static 1.251 +void 1.252 +IMEHandler::OnDestroyWindow(nsWindow* aWindow) 1.253 +{ 1.254 +#ifdef NS_ENABLE_TSF 1.255 + // We need to do nothing here for TSF. Just restore the default context 1.256 + // if it's been disassociated. 1.257 + if (!sIsInTSFMode) { 1.258 + // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use 1.259 + // SetInputScopes API. Use an empty string to do this. 1.260 + SetInputScopeForIMM32(aWindow, EmptyString()); 1.261 + } 1.262 +#endif // #ifdef NS_ENABLE_TSF 1.263 + AssociateIMEContext(aWindow, true); 1.264 +} 1.265 + 1.266 +// static 1.267 +void 1.268 +IMEHandler::SetInputContext(nsWindow* aWindow, 1.269 + InputContext& aInputContext, 1.270 + const InputContextAction& aAction) 1.271 +{ 1.272 + // FYI: If there is no composition, this call will do nothing. 1.273 + NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION)); 1.274 + 1.275 + const InputContext& oldInputContext = aWindow->GetInputContext(); 1.276 + 1.277 + // Assume that SetInputContext() is called only when aWindow has focus. 1.278 + sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN); 1.279 + 1.280 + bool enable = WinUtils::IsIMEEnabled(aInputContext); 1.281 + bool adjustOpenState = (enable && 1.282 + aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE); 1.283 + bool open = (adjustOpenState && 1.284 + aInputContext.mIMEState.mOpen == IMEState::OPEN); 1.285 + 1.286 + aInputContext.mNativeIMEContext = nullptr; 1.287 + 1.288 +#ifdef NS_ENABLE_TSF 1.289 + // Note that even while a plugin has focus, we need to notify TSF of that. 1.290 + if (sIsInTSFMode) { 1.291 + nsTextStore::SetInputContext(aWindow, aInputContext, aAction); 1.292 + if (IsTSFAvailable()) { 1.293 + aInputContext.mNativeIMEContext = nsTextStore::GetTextStore(); 1.294 + if (sIsIMMEnabled) { 1.295 + // Associate IME context for IMM-IMEs. 1.296 + AssociateIMEContext(aWindow, enable); 1.297 + } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) { 1.298 + // Disassociate the IME context from the window when plugin loses focus 1.299 + // in pure TSF mode. 1.300 + AssociateIMEContext(aWindow, false); 1.301 + } 1.302 + if (adjustOpenState) { 1.303 + nsTextStore::SetIMEOpenState(open); 1.304 + } 1.305 + return; 1.306 + } 1.307 + } else { 1.308 + // Set at least InputScope even when TextStore is not available. 1.309 + SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType); 1.310 + } 1.311 +#endif // #ifdef NS_ENABLE_TSF 1.312 + 1.313 + AssociateIMEContext(aWindow, enable); 1.314 + 1.315 + nsIMEContext IMEContext(aWindow->GetWindowHandle()); 1.316 + if (adjustOpenState) { 1.317 + IMEContext.SetOpenState(open); 1.318 + } 1.319 + 1.320 + if (aInputContext.mNativeIMEContext) { 1.321 + return; 1.322 + } 1.323 + 1.324 + // The old InputContext must store the default IMC or old TextStore. 1.325 + // When IME context is disassociated from the window, use it. 1.326 + aInputContext.mNativeIMEContext = enable ? 1.327 + static_cast<void*>(IMEContext.get()) : oldInputContext.mNativeIMEContext; 1.328 +} 1.329 + 1.330 +// static 1.331 +void 1.332 +IMEHandler::AssociateIMEContext(nsWindow* aWindow, bool aEnable) 1.333 +{ 1.334 + nsIMEContext IMEContext(aWindow->GetWindowHandle()); 1.335 + if (aEnable) { 1.336 + IMEContext.AssociateDefaultContext(); 1.337 + return; 1.338 + } 1.339 + // Don't disassociate the context after the window is destroyed. 1.340 + if (aWindow->Destroyed()) { 1.341 + return; 1.342 + } 1.343 + IMEContext.Disassociate(); 1.344 +} 1.345 + 1.346 +// static 1.347 +void 1.348 +IMEHandler::InitInputContext(nsWindow* aWindow, InputContext& aInputContext) 1.349 +{ 1.350 + // For a11y, the default enabled state should be 'enabled'. 1.351 + aInputContext.mIMEState.mEnabled = IMEState::ENABLED; 1.352 + 1.353 +#ifdef NS_ENABLE_TSF 1.354 + if (sIsInTSFMode) { 1.355 + nsTextStore::SetInputContext(aWindow, aInputContext, 1.356 + InputContextAction(InputContextAction::CAUSE_UNKNOWN, 1.357 + InputContextAction::GOT_FOCUS)); 1.358 + aInputContext.mNativeIMEContext = nsTextStore::GetTextStore(); 1.359 + MOZ_ASSERT(aInputContext.mNativeIMEContext); 1.360 + // IME context isn't necessary in pure TSF mode. 1.361 + if (!sIsIMMEnabled) { 1.362 + AssociateIMEContext(aWindow, false); 1.363 + } 1.364 + return; 1.365 + } 1.366 +#endif // #ifdef NS_ENABLE_TSF 1.367 + 1.368 + // NOTE: mNativeIMEContext may be null if IMM module isn't installed. 1.369 + nsIMEContext IMEContext(aWindow->GetWindowHandle()); 1.370 + aInputContext.mNativeIMEContext = static_cast<void*>(IMEContext.get()); 1.371 + MOZ_ASSERT(aInputContext.mNativeIMEContext || !CurrentKeyboardLayoutHasIME()); 1.372 + // If no IME context is available, we should set the widget's pointer since 1.373 + // nullptr indicates there is only one context per process on the platform. 1.374 + if (!aInputContext.mNativeIMEContext) { 1.375 + aInputContext.mNativeIMEContext = static_cast<void*>(aWindow); 1.376 + } 1.377 +} 1.378 + 1.379 +#ifdef DEBUG 1.380 +// static 1.381 +bool 1.382 +IMEHandler::CurrentKeyboardLayoutHasIME() 1.383 +{ 1.384 +#ifdef NS_ENABLE_TSF 1.385 + if (sIsInTSFMode) { 1.386 + return nsTextStore::CurrentKeyboardLayoutHasIME(); 1.387 + } 1.388 +#endif // #ifdef NS_ENABLE_TSF 1.389 + 1.390 + return nsIMM32Handler::IsIMEAvailable(); 1.391 +} 1.392 +#endif // #ifdef DEBUG 1.393 + 1.394 +// static 1.395 +void 1.396 +IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow, 1.397 + const nsAString& aHTMLInputType) 1.398 +{ 1.399 + if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) { 1.400 + return; 1.401 + } 1.402 + UINT arraySize = 0; 1.403 + const InputScope* scopes = nullptr; 1.404 + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html 1.405 + if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) { 1.406 + static const InputScope inputScopes[] = { IS_DEFAULT }; 1.407 + scopes = &inputScopes[0]; 1.408 + arraySize = ArrayLength(inputScopes); 1.409 + } else if (aHTMLInputType.EqualsLiteral("url")) { 1.410 + static const InputScope inputScopes[] = { IS_URL }; 1.411 + scopes = &inputScopes[0]; 1.412 + arraySize = ArrayLength(inputScopes); 1.413 + } else if (aHTMLInputType.EqualsLiteral("search")) { 1.414 + static const InputScope inputScopes[] = { IS_SEARCH }; 1.415 + scopes = &inputScopes[0]; 1.416 + arraySize = ArrayLength(inputScopes); 1.417 + } else if (aHTMLInputType.EqualsLiteral("email")) { 1.418 + static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS }; 1.419 + scopes = &inputScopes[0]; 1.420 + arraySize = ArrayLength(inputScopes); 1.421 + } else if (aHTMLInputType.EqualsLiteral("password")) { 1.422 + static const InputScope inputScopes[] = { IS_PASSWORD }; 1.423 + scopes = &inputScopes[0]; 1.424 + arraySize = ArrayLength(inputScopes); 1.425 + } else if (aHTMLInputType.EqualsLiteral("datetime") || 1.426 + aHTMLInputType.EqualsLiteral("datetime-local")) { 1.427 + static const InputScope inputScopes[] = { 1.428 + IS_DATE_FULLDATE, IS_TIME_FULLTIME }; 1.429 + scopes = &inputScopes[0]; 1.430 + arraySize = ArrayLength(inputScopes); 1.431 + } else if (aHTMLInputType.EqualsLiteral("date") || 1.432 + aHTMLInputType.EqualsLiteral("month") || 1.433 + aHTMLInputType.EqualsLiteral("week")) { 1.434 + static const InputScope inputScopes[] = { IS_DATE_FULLDATE }; 1.435 + scopes = &inputScopes[0]; 1.436 + arraySize = ArrayLength(inputScopes); 1.437 + } else if (aHTMLInputType.EqualsLiteral("time")) { 1.438 + static const InputScope inputScopes[] = { IS_TIME_FULLTIME }; 1.439 + scopes = &inputScopes[0]; 1.440 + arraySize = ArrayLength(inputScopes); 1.441 + } else if (aHTMLInputType.EqualsLiteral("tel")) { 1.442 + static const InputScope inputScopes[] = { 1.443 + IS_TELEPHONE_FULLTELEPHONENUMBER, IS_TELEPHONE_LOCALNUMBER }; 1.444 + scopes = &inputScopes[0]; 1.445 + arraySize = ArrayLength(inputScopes); 1.446 + } else if (aHTMLInputType.EqualsLiteral("number")) { 1.447 + static const InputScope inputScopes[] = { IS_NUMBER }; 1.448 + scopes = &inputScopes[0]; 1.449 + arraySize = ArrayLength(inputScopes); 1.450 + } 1.451 + if (scopes && arraySize > 0) { 1.452 + sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0, 1.453 + nullptr, nullptr); 1.454 + } 1.455 +} 1.456 + 1.457 +} // namespace widget 1.458 +} // namespace mozilla