// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/ime/win/input_method_win_tsf.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/virtual_keyboard_controller.h"
#include "ui/base/ime/win/tsf_bridge.h"
#include "ui/base/ime/win/tsf_event_router.h"
namespace ui {
class InputMethodWinTSF::TSFEventObserver : public TSFEventRouterObserver {
public:
TSFEventObserver() = default;
TSFEventObserver(const TSFEventObserver&) = delete;
TSFEventObserver& operator=(const TSFEventObserver&) = delete;
// Returns true if we know for sure that a candidate window (or IME suggest,
// etc.) is open.
bool IsCandidatePopupOpen() const { return is_candidate_popup_open_; }
// Overridden from TSFEventRouterObserver:
void OnCandidateWindowCountChanged(size_t window_count) override {
is_candidate_popup_open_ = (window_count != 0);
}
private:
// True if we know for sure that a candidate window is open.
bool is_candidate_popup_open_ = false;
};
InputMethodWinTSF::InputMethodWinTSF(
ImeKeyEventDispatcher* ime_key_event_dispatcher,
HWND attached_window_handle)
: InputMethodWinBase(ime_key_event_dispatcher, attached_window_handle),
tsf_event_observer_(new TSFEventObserver()),
tsf_event_router_(new TSFEventRouter(tsf_event_observer_.get())) {}
InputMethodWinTSF::~InputMethodWinTSF() {
// A pointer to |this| might have been passed to ui::TSFBridge::GetInstance()
// in InputMethodWinTSF::OnFocus(). This TSFBridge can sometime be called
// asynchronously with the following stack:
// chrome.dll ui::TSFTextStore::DispatchKeyEvent
// chrome.dll ui::TSFTextStore::RequestLock
// textinputframework.dll SafeRequestLock
// textinputframework.dll CInputContext::RequestLock
//
// This will cause the TSFBridge to try to access this pointer, which could
// cause a UAF if this object is already destroyed. To avoid this, we need to
// explicitly remove the dispatcher before destroying this object. The code in
// ui::TSFTextStore::DispatchKeyEvent does properly check the pointer before
// trying to use it. Note that everything happens on the same thread here.
//
// See crbug.com/41488962
if (ui::TSFBridge::GetInstance()) {
ui::TSFBridge::GetInstance()->RemoveImeKeyEventDispatcher(
InputMethodBase::ime_key_event_dispatcher());
}
}
void InputMethodWinTSF::OnFocus() {
InputMethodBase::OnFocus();
if (!ui::TSFBridge::GetInstance()) {
// TSFBridge can be null for tests.
return;
}
tsf_event_router_->SetManager(
ui::TSFBridge::GetInstance()->GetThreadManager().Get());
ui::TSFBridge::GetInstance()->SetImeKeyEventDispatcher(
InputMethodBase::ime_key_event_dispatcher());
}
void InputMethodWinTSF::OnBlur() {
InputMethodBase::OnBlur();
if (!ui::TSFBridge::GetInstance()) {
// TSFBridge can be null for tests.
return;
}
tsf_event_router_->SetManager(nullptr);
ui::TSFBridge::GetInstance()->RemoveImeKeyEventDispatcher(
InputMethodBase::ime_key_event_dispatcher());
}
bool InputMethodWinTSF::OnUntranslatedIMEMessage(
const CHROME_MSG event,
InputMethod::NativeEventResult* result) {
LRESULT original_result = 0;
BOOL handled = FALSE;
// Even when TSF is enabled, following IMM32/Win32 messages must be handled.
switch (event.message) {
case WM_IME_REQUEST:
// Some TSF-native TIPs (Text Input Processors) such as ATOK and Mozc
// still rely on WM_IME_REQUEST message to implement reverse conversion.
original_result =
OnImeRequest(event.message, event.wParam, event.lParam, &handled);
break;
case WM_CHAR:
case WM_SYSCHAR:
// ui::InputMethod interface is responsible for handling Win32 character
// messages. For instance, we will be here in the following cases.
// - TIP is not activated. (e.g, the current language profile is English)
// - TIP does not handle and WM_KEYDOWN and WM_KEYDOWN is translated into
// WM_CHAR by TranslateMessage API. (e.g, TIP is turned off)
// - Another application sends WM_CHAR through SendMessage API.
original_result = OnChar(event.hwnd, event.message, event.wParam,
event.lParam, event, &handled);
break;
}
if (result)
*result = original_result;
return !!handled;
}
void InputMethodWinTSF::OnTextInputTypeChanged(TextInputClient* client) {
InputMethodBase::OnTextInputTypeChanged(client);
if (!ui::TSFBridge::GetInstance() || !IsTextInputClientFocused(client) ||
!IsWindowFocused(client)) {
return;
}
ui::TSFBridge::GetInstance()->CancelComposition();
ui::TSFBridge::GetInstance()->OnTextInputTypeChanged(client);
}
void InputMethodWinTSF::OnCaretBoundsChanged(const TextInputClient* client) {
if (!ui::TSFBridge::GetInstance() || !IsTextInputClientFocused(client) ||
!IsWindowFocused(client)) {
return;
}
NotifyTextInputCaretBoundsChanged(client);
ui::TSFBridge::GetInstance()->OnTextLayoutChanged();
}
void InputMethodWinTSF::CancelComposition(const TextInputClient* client) {
if (ui::TSFBridge::GetInstance() && IsTextInputClientFocused(client) &&
IsWindowFocused(client)) {
ui::TSFBridge::GetInstance()->CancelComposition();
}
}
void InputMethodWinTSF::DetachTextInputClient(TextInputClient* client) {
if (!ui::TSFBridge::GetInstance()) {
// TSFBridge can be null for tests.
return;
}
InputMethodWinBase::DetachTextInputClient(client);
ui::TSFBridge::GetInstance()->RemoveFocusedClient(client);
}
void InputMethodWinTSF::OnInputLocaleChanged() {}
bool InputMethodWinTSF::IsInputLocaleCJK() const {
if (!ui::TSFBridge::GetInstance()) {
return false;
}
return ui::TSFBridge::GetInstance()->IsInputLanguageCJK();
}
bool InputMethodWinTSF::IsCandidatePopupOpen() const {
return tsf_event_observer_->IsCandidatePopupOpen();
}
void InputMethodWinTSF::OnWillChangeFocusedClient(
TextInputClient* focused_before,
TextInputClient* focused) {
if (ui::TSFBridge::GetInstance() && IsWindowFocused(focused_before)) {
ConfirmCompositionText();
ui::TSFBridge::GetInstance()->RemoveFocusedClient(focused_before);
}
}
void InputMethodWinTSF::OnDidChangeFocusedClient(
TextInputClient* focused_before,
TextInputClient* focused) {
if (ui::TSFBridge::GetInstance() && IsWindowFocused(focused) &&
IsTextInputClientFocused(focused)) {
ui::TSFBridge::GetInstance()->SetFocusedClient(attached_window_handle_,
focused);
// Force to update the input type since client's TextInputStateChanged()
// function might not be called if text input types before the client loses
// focus and after it acquires focus again are the same.
OnTextInputTypeChanged(focused);
// Force to update caret bounds, in case the client thinks that the caret
// bounds has not changed.
OnCaretBoundsChanged(focused);
}
InputMethodWinBase::OnDidChangeFocusedClient(focused_before, focused);
}
void InputMethodWinTSF::ConfirmCompositionText() {
if (IsTextInputTypeNone())
return;
if (ui::TSFBridge::GetInstance())
ui::TSFBridge::GetInstance()->ConfirmComposition();
}
void InputMethodWinTSF::OnUrlChanged() {
if (!ui::TSFBridge::GetInstance()) {
return;
}
ui::TSFBridge::GetInstance()->OnUrlChanged();
}
} // namespace ui