1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
|
// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "ui/base/ime/win/tsf_bridge.h"
#include <msctf.h>
#include <map>
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/no_destructor.h"
#include "base/task/current_thread.h"
#include "base/threading/thread_local.h"
#include "base/trace_event/trace_event.h"
#include "base/win/scoped_variant.h"
#include "ui/base/ime/ime_key_event_dispatcher.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/win/mock_tsf_bridge.h"
#include "ui/base/ime/win/tsf_text_store.h"
#include "ui/base/ui_base_features.h"
namespace ui {
namespace {
// TSFBridgeImpl -----------------------------------------------------------
// A TLS implementation of TSFBridge.
class TSFBridgeImpl : public TSFBridge {
public:
TSFBridgeImpl();
TSFBridgeImpl(const TSFBridgeImpl&) = delete;
TSFBridgeImpl& operator=(const TSFBridgeImpl&) = delete;
~TSFBridgeImpl() override;
HRESULT Initialize();
// TsfBridge:
void OnTextInputTypeChanged(const TextInputClient* client) override;
void OnTextLayoutChanged() override;
bool CancelComposition() override;
bool ConfirmComposition() override;
void SetFocusedClient(HWND focused_window, TextInputClient* client) override;
void RemoveFocusedClient(TextInputClient* client) override;
void SetImeKeyEventDispatcher(
ImeKeyEventDispatcher* ime_key_event_dispatcher) override;
void RemoveImeKeyEventDispatcher(
ImeKeyEventDispatcher* ime_key_event_dispatcher) override;
bool IsInputLanguageCJK() override;
Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() override;
TextInputClient* GetFocusedTextInputClient() const override;
void OnUrlChanged() override;
private:
// Returns S_OK if |tsf_document_map_| is successfully initialized. This
// method should be called from and only from Initialize().
HRESULT InitializeDocumentMapInternal();
// Returns S_OK if |context| is successfully updated to be a disabled
// context, where an IME should be deactivated. This is suitable for some
// special input context such as password fields.
HRESULT InitializeDisabledContext(ITfContext* context);
// Returns S_OK if a TSF document manager and a TSF context is successfully
// created with associating with given |text_store|. The returned
// |source_cookie| indicates the binding between |text_store| and |context|.
// You can pass nullptr to |text_store| and |source_cookie| when text store is
// not necessary.
HRESULT CreateDocumentManager(TSFTextStore* text_store,
ITfDocumentMgr** document_manager,
ITfContext** context,
DWORD* source_cookie,
DWORD* key_trace_sink_cookie,
DWORD* language_profile_cookie);
// Returns true if |document_manager| is the focused document manager.
bool IsFocused(ITfDocumentMgr* document_manager);
// Returns true if already initialized.
bool IsInitialized();
// Updates or clears the association maintained in the TSF runtime between
// |attached_window_handle_| and the current document manager. Keeping this
// association updated solves some tricky event ordering issues between
// logical text input focus managed by Chrome and native text input focus
// managed by the OS.
// Background:
// TSF runtime monitors some Win32 messages such as WM_ACTIVATE to
// change the focused document manager. This is problematic when
// TSFBridge::SetFocusedClient is called first then the target window
// receives WM_ACTIVATE. This actually occurs in Aura environment where
// WM_NCACTIVATE is used as a trigger to restore text input focus.
// Caveats:
// TSF runtime does not increment the reference count of the attached
// document manager. See the comment inside the method body for
// details.
void UpdateAssociateFocus();
void ClearAssociateFocus();
// A triple of document manager, text store and binding cookie between
// a context owned by the document manager and the text store. This is a
// minimum working set of an editable document in TSF.
struct TSFDocument {
public:
TSFDocument()
: source_cookie(TF_INVALID_COOKIE),
key_trace_sink_cookie(TF_INVALID_COOKIE),
language_profile_cookie(TF_INVALID_COOKIE) {}
TSFDocument(const TSFDocument& src)
: document_manager(src.document_manager),
source_cookie(src.source_cookie),
key_trace_sink_cookie(src.key_trace_sink_cookie),
language_profile_cookie(src.language_profile_cookie) {}
Microsoft::WRL::ComPtr<ITfDocumentMgr> document_manager;
scoped_refptr<TSFTextStore> text_store;
DWORD source_cookie;
DWORD key_trace_sink_cookie;
DWORD language_profile_cookie;
};
// Returns a pointer to TSFDocument that is associated with the current
// TextInputType of |client_|.
TSFDocument* GetAssociatedDocument();
// An ITfThreadMgr object to be used in focus and document management.
Microsoft::WRL::ComPtr<ITfThreadMgr> thread_manager_;
// An ITfInputProcessorProfiles object to be used to get current language
// locale profile.
Microsoft::WRL::ComPtr<ITfInputProcessorProfiles> input_processor_profiles_;
// A map from TextInputType to an editable document for TSF. We use multiple
// TSF documents that have different InputScopes and TSF attributes based on
// the TextInputType associated with the target document. For a TextInputType
// that is not coverted by this map, a default document, e.g. the document
// for TEXT_INPUT_TYPE_TEXT, should be used.
// Note that some IMEs don't change their state unless the document focus is
// changed. This is why we use multiple documents instead of changing TSF
// metadata of a single document on the fly.
typedef std::map<TextInputType, TSFDocument> TSFDocumentMap;
TSFDocumentMap tsf_document_map_;
// An identifier of TSF client.
TfClientId client_id_ = TF_CLIENTID_NULL;
// Current focused text input client. Do not free |client_|.
raw_ptr<TextInputClient> client_ = nullptr;
// Input Type of current focused text input client.
TextInputType input_type_ = TEXT_INPUT_TYPE_NONE;
// Represents the window that is currently owns text input focus.
HWND attached_window_handle_ = nullptr;
// Tracks Windows OS support for empty TSF text stores, available on win11+.
bool empty_tsf_support_ = false;
};
TSFBridgeImpl::TSFBridgeImpl() = default;
TSFBridgeImpl::~TSFBridgeImpl() {
DCHECK(base::CurrentUIThread::IsSet());
if (!IsInitialized())
return;
for (auto& pair : tsf_document_map_) {
TSFDocument& document = pair.second;
Microsoft::WRL::ComPtr<ITfContext> context;
Microsoft::WRL::ComPtr<ITfSource> source;
if (document.source_cookie != TF_INVALID_COOKIE &&
SUCCEEDED(document.document_manager->GetBase(&context)) &&
SUCCEEDED(context.As(&source))) {
source->UnadviseSink(document.source_cookie);
}
if (thread_manager_ != nullptr) {
Microsoft::WRL::ComPtr<ITfSource> key_trace_sink_source;
if (document.key_trace_sink_cookie != TF_INVALID_COOKIE &&
SUCCEEDED(thread_manager_.As(&key_trace_sink_source))) {
key_trace_sink_source->UnadviseSink(document.key_trace_sink_cookie);
}
Microsoft::WRL::ComPtr<ITfSource> language_profile_source;
if (document.language_profile_cookie != TF_INVALID_COOKIE &&
SUCCEEDED(input_processor_profiles_.As(&language_profile_source))) {
language_profile_source->UnadviseSink(document.language_profile_cookie);
}
}
}
tsf_document_map_.clear();
client_id_ = TF_CLIENTID_NULL;
}
HRESULT TSFBridgeImpl::Initialize() {
DCHECK(base::CurrentUIThread::IsSet());
if (client_id_ != TF_CLIENTID_NULL) {
DVLOG(1) << "Already initialized.";
return S_OK; // shouldn't return error code in this case.
}
HRESULT hr =
::CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_ALL,
IID_PPV_ARGS(&input_processor_profiles_));
if (FAILED(hr)) {
DVLOG(1) << "Failed to create InputProcessorProfiles instance.";
return hr;
}
hr = ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_ALL,
IID_PPV_ARGS(&thread_manager_));
if (FAILED(hr)) {
DVLOG(1) << "Failed to create ThreadManager instance.";
return hr;
}
hr = thread_manager_->Activate(&client_id_);
if (FAILED(hr)) {
DVLOG(1) << "Failed to activate Thread Manager.";
return hr;
}
hr = InitializeDocumentMapInternal();
if (FAILED(hr))
return hr;
// Japanese IME expects the default value of this compartment is
// TF_SENTENCEMODE_PHRASEPREDICT like IMM32 implementation. This value is
// managed per thread, so that it is enough to set this value at once. This
// value does not affect other language's IME behaviors.
Microsoft::WRL::ComPtr<ITfCompartmentMgr> thread_compartment_manager;
hr = thread_manager_.As(&thread_compartment_manager);
if (FAILED(hr)) {
DVLOG(1) << "Failed to get ITfCompartmentMgr.";
return hr;
}
Microsoft::WRL::ComPtr<ITfCompartment> sentence_compartment;
hr = thread_compartment_manager->GetCompartment(
GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE, &sentence_compartment);
if (FAILED(hr)) {
DVLOG(1) << "Failed to get sentence compartment.";
return hr;
}
base::win::ScopedVariant sentence_variant;
sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT);
hr = sentence_compartment->SetValue(client_id_, sentence_variant.ptr());
if (FAILED(hr)) {
DVLOG(1) << "Failed to change the sentence mode.";
return hr;
}
return S_OK;
}
void TSFBridgeImpl::OnTextInputTypeChanged(const TextInputClient* client) {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(IsInitialized());
if (client != client_) {
// Called from not focusing client. Do nothing.
return;
}
// Since we reuse TSF document for same text input type, there is a case where
// focus is switched between two text fields with same input type. We should
// prepare the TSF document for reuse by clearing focus first.
if (input_type_ != TEXT_INPUT_TYPE_NONE &&
input_type_ == client_->GetTextInputType()) {
if (empty_tsf_support_) {
// Switch focus to empty doc. This optimizes the reuse, since here TSF
// changes the text store's edit context state. So switching
// the focus back to the edit context helps to reuse the TSF document.
// It also reduces focus noise to TSF's default document which is only
// there for app compat with embedded controls.
TSFDocumentMap::iterator it =
tsf_document_map_.find(TEXT_INPUT_TYPE_NONE);
if (it != tsf_document_map_.end()) {
thread_manager_->SetFocus(it->second.document_manager.Get());
} else {
// If we don't have an empty document, set focus to TSF default
// document.
thread_manager_->SetFocus(nullptr);
}
} else {
thread_manager_->SetFocus(nullptr);
}
}
input_type_ = client_->GetTextInputType();
TSFDocument* document = GetAssociatedDocument();
if (!document)
return;
// We call AssociateFocus for text input type none that also
// triggers SetFocus internally. We don't want to send multiple
// focus notifications for the same text input type so we don't
// call AssociateFocus and SetFocus together. Just calling SetFocus
// should be sufficient for setting focus on a textstore.
if (input_type_ != TEXT_INPUT_TYPE_NONE)
thread_manager_->SetFocus(document->document_manager.Get());
else
UpdateAssociateFocus();
OnTextLayoutChanged();
}
void TSFBridgeImpl::OnTextLayoutChanged() {
TSFDocument* document = GetAssociatedDocument();
if (!document)
return;
if (!document->text_store)
return;
document->text_store->SendOnLayoutChange();
}
bool TSFBridgeImpl::CancelComposition() {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(IsInitialized());
TSFDocument* document = GetAssociatedDocument();
if (!document)
return false;
if (!document->text_store)
return false;
return document->text_store->CancelComposition();
}
bool TSFBridgeImpl::ConfirmComposition() {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(IsInitialized());
TSFDocument* document = GetAssociatedDocument();
if (!document)
return false;
if (!document->text_store)
return false;
return document->text_store->ConfirmComposition();
}
void TSFBridgeImpl::SetFocusedClient(HWND focused_window,
TextInputClient* client) {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(client);
DCHECK(IsInitialized());
if (attached_window_handle_ != focused_window)
ClearAssociateFocus();
client_ = client;
attached_window_handle_ = focused_window;
for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
it != tsf_document_map_.end(); ++it) {
if (it->second.text_store.get() == nullptr)
continue;
it->second.text_store->SetFocusedTextInputClient(focused_window, client);
}
// Synchronize text input type state.
OnTextInputTypeChanged(client);
}
void TSFBridgeImpl::RemoveFocusedClient(TextInputClient* client) {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(IsInitialized());
if (client_ != client)
return;
ClearAssociateFocus();
client_ = nullptr;
attached_window_handle_ = nullptr;
for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
it != tsf_document_map_.end(); ++it) {
if (it->second.text_store.get() == nullptr)
continue;
it->second.text_store->SetFocusedTextInputClient(nullptr, nullptr);
}
}
void TSFBridgeImpl::SetImeKeyEventDispatcher(
ImeKeyEventDispatcher* ime_key_event_dispatcher) {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(ime_key_event_dispatcher);
DCHECK(IsInitialized());
for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
it != tsf_document_map_.end(); ++it) {
if (it->second.text_store.get() == nullptr)
continue;
it->second.text_store->SetImeKeyEventDispatcher(ime_key_event_dispatcher);
}
}
void TSFBridgeImpl::RemoveImeKeyEventDispatcher(
ImeKeyEventDispatcher* ime_key_event_dispatcher) {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(IsInitialized());
for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
it != tsf_document_map_.end(); ++it) {
if (it->second.text_store.get() == nullptr)
continue;
it->second.text_store->RemoveImeKeyEventDispatcher(
ime_key_event_dispatcher);
}
}
bool TSFBridgeImpl::IsInputLanguageCJK() {
// See the following article about how LANGID in HKL is determined.
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeyboardlayout
LANGID lang_locale =
PRIMARYLANGID(LOWORD(HandleToLong(GetKeyboardLayout(0))));
return lang_locale == LANG_CHINESE || lang_locale == LANG_JAPANESE ||
lang_locale == LANG_KOREAN;
}
TextInputClient* TSFBridgeImpl::GetFocusedTextInputClient() const {
return client_;
}
void TSFBridgeImpl::OnUrlChanged() {
TSFDocument* document = GetAssociatedDocument();
if (!document || !document->text_store) {
return;
}
document->text_store->MaybeSendOnUrlChanged();
}
Microsoft::WRL::ComPtr<ITfThreadMgr> TSFBridgeImpl::GetThreadManager() {
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(IsInitialized());
return thread_manager_;
}
HRESULT TSFBridgeImpl::CreateDocumentManager(TSFTextStore* text_store,
ITfDocumentMgr** document_manager,
ITfContext** context,
DWORD* source_cookie,
DWORD* key_trace_sink_cookie,
DWORD* language_profile_cookie) {
HRESULT hr = thread_manager_->CreateDocumentMgr(document_manager);
if (FAILED(hr)) {
DVLOG(1) << "Failed to create Document Manager.";
return hr;
}
if (!text_store || !source_cookie || !key_trace_sink_cookie ||
!language_profile_cookie) {
return S_OK;
}
DWORD edit_cookie = TF_INVALID_EDIT_COOKIE;
hr = (*document_manager)
->CreateContext(client_id_, 0,
static_cast<ITextStoreACP*>(text_store), context,
&edit_cookie);
if (FAILED(hr)) {
DVLOG(1) << "Failed to create Context.";
return hr;
}
hr = (*document_manager)->Push(*context);
if (FAILED(hr)) {
DVLOG(1) << "Failed to push context.";
return hr;
}
Microsoft::WRL::ComPtr<ITfSource> source;
hr = (*context)->QueryInterface(IID_PPV_ARGS(&source));
if (FAILED(hr)) {
DVLOG(1) << "Failed to get source.";
return hr;
}
hr = source->AdviseSink(IID_ITfTextEditSink,
static_cast<ITfTextEditSink*>(text_store),
source_cookie);
if (FAILED(hr)) {
DVLOG(1) << "AdviseSink failed.";
return hr;
}
Microsoft::WRL::ComPtr<ITfSource> source_ITfThreadMgr;
hr = thread_manager_->QueryInterface(IID_PPV_ARGS(&source_ITfThreadMgr));
if (FAILED(hr)) {
DVLOG(1) << "Failed to get source_ITfThreadMgr.";
return hr;
}
hr = source_ITfThreadMgr->AdviseSink(
IID_ITfKeyTraceEventSink, static_cast<ITfKeyTraceEventSink*>(text_store),
key_trace_sink_cookie);
if (FAILED(hr)) {
DVLOG(1) << "AdviseSink for ITfKeyTraceEventSink failed.";
return hr;
}
Microsoft::WRL::ComPtr<ITfSource> language_source;
hr =
input_processor_profiles_->QueryInterface(IID_PPV_ARGS(&language_source));
if (FAILED(hr)) {
DVLOG(1) << "Failed to get source_ITfInputProcessorProfiles.";
return hr;
}
hr = language_source->AdviseSink(IID_ITfLanguageProfileNotifySink,
static_cast<ITfTextEditSink*>(text_store),
language_profile_cookie);
if (FAILED(hr)) {
DVLOG(1) << "AdviseSink for language profile notify sink failed.";
return hr;
}
if (*source_cookie == TF_INVALID_COOKIE) {
DVLOG(1) << "The result of cookie is invalid.";
return E_FAIL;
}
return S_OK;
}
HRESULT TSFBridgeImpl::InitializeDocumentMapInternal() {
const TextInputType kTextInputTypes[] = {
TEXT_INPUT_TYPE_NONE, TEXT_INPUT_TYPE_TEXT,
TEXT_INPUT_TYPE_PASSWORD, TEXT_INPUT_TYPE_SEARCH,
TEXT_INPUT_TYPE_EMAIL, TEXT_INPUT_TYPE_NUMBER,
TEXT_INPUT_TYPE_TELEPHONE, TEXT_INPUT_TYPE_URL,
TEXT_INPUT_TYPE_TEXT_AREA,
};
// Query TSF for empty TSF text store support, introduced with Windows 11.
// If support is present, as indicated by successful return of an interface
// for the IID value GUID_COMPARTMENT_EMPTYCONTEXT, we use a dummy/empty Text
// store when there is no text.
Microsoft::WRL::ComPtr<IUnknown> flag_empty_context;
HRESULT res = thread_manager_->QueryInterface(GUID_COMPARTMENT_EMPTYCONTEXT,
&flag_empty_context);
if (SUCCEEDED(res)) {
empty_tsf_support_ = true;
}
for (size_t i = 0; i < std::size(kTextInputTypes); ++i) {
const TextInputType input_type = kTextInputTypes[i];
Microsoft::WRL::ComPtr<ITfContext> context;
Microsoft::WRL::ComPtr<ITfDocumentMgr> document_manager;
DWORD source_cookie = TF_INVALID_COOKIE;
DWORD key_trace_sink_cookie = TF_INVALID_COOKIE;
DWORD language_profile_cookie = TF_INVALID_COOKIE;
// Use a null text store if empty tsf text store is not supported.
const bool use_null_text_store =
(input_type == TEXT_INPUT_TYPE_NONE && !empty_tsf_support_);
DWORD* source_cookie_ptr = use_null_text_store ? nullptr : &source_cookie;
DWORD* key_trace_sink_cookie_ptr =
use_null_text_store ? nullptr : &key_trace_sink_cookie;
DWORD* language_profile_cookie_ptr =
use_null_text_store ? nullptr : &language_profile_cookie;
scoped_refptr<TSFTextStore> text_store =
use_null_text_store ? nullptr : new TSFTextStore();
if (text_store && input_type != TEXT_INPUT_TYPE_NONE) {
// No need to initialize for TEXT_INPUT_TYPE_NONE.
HRESULT hr = text_store->Initialize();
if (FAILED(hr))
return hr;
}
HRESULT hr = CreateDocumentManager(
text_store.get(), &document_manager, &context, source_cookie_ptr,
key_trace_sink_cookie_ptr, language_profile_cookie_ptr);
if (FAILED(hr))
return hr;
if (input_type == TEXT_INPUT_TYPE_PASSWORD ||
(empty_tsf_support_ && input_type == TEXT_INPUT_TYPE_NONE)) {
// Disable context for TEXT_INPUT_TYPE_NONE, if empty text store is
// supported.
hr = InitializeDisabledContext(context.Get());
if (FAILED(hr))
return hr;
}
tsf_document_map_[input_type].text_store = text_store;
tsf_document_map_[input_type].document_manager = document_manager;
tsf_document_map_[input_type].source_cookie = source_cookie;
tsf_document_map_[input_type].key_trace_sink_cookie = key_trace_sink_cookie;
tsf_document_map_[input_type].language_profile_cookie =
language_profile_cookie;
if (text_store)
text_store->OnContextInitialized(context.Get());
// Set the flag for empty text store.
if (text_store && input_type == TEXT_INPUT_TYPE_NONE) {
text_store->UseEmptyTextStore(empty_tsf_support_);
}
}
return S_OK;
}
HRESULT TSFBridgeImpl::InitializeDisabledContext(ITfContext* context) {
Microsoft::WRL::ComPtr<ITfCompartmentMgr> compartment_mgr;
HRESULT hr = context->QueryInterface(IID_PPV_ARGS(&compartment_mgr));
if (FAILED(hr)) {
DVLOG(1) << "Failed to get CompartmentMgr.";
return hr;
}
Microsoft::WRL::ComPtr<ITfCompartment> disabled_compartment;
hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_DISABLED,
&disabled_compartment);
if (FAILED(hr)) {
DVLOG(1) << "Failed to get keyboard disabled compartment.";
return hr;
}
base::win::ScopedVariant variant;
variant.Set(1);
hr = disabled_compartment->SetValue(client_id_, variant.ptr());
if (FAILED(hr)) {
DVLOG(1) << "Failed to disable the DocumentMgr.";
return hr;
}
Microsoft::WRL::ComPtr<ITfCompartment> empty_context;
hr = compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT,
&empty_context);
if (FAILED(hr)) {
DVLOG(1) << "Failed to get empty context compartment.";
return hr;
}
base::win::ScopedVariant empty_context_variant;
empty_context_variant.Set(static_cast<int32_t>(1));
hr = empty_context->SetValue(client_id_, empty_context_variant.ptr());
if (FAILED(hr)) {
DVLOG(1) << "Failed to set empty context.";
return hr;
}
return S_OK;
}
bool TSFBridgeImpl::IsFocused(ITfDocumentMgr* document_manager) {
if (!IsInitialized()) {
// Hasn't been initialized yet. Return false.
return false;
}
Microsoft::WRL::ComPtr<ITfDocumentMgr> focused_document_manager;
if (FAILED(thread_manager_->GetFocus(&focused_document_manager)))
return false;
return focused_document_manager.Get() == document_manager;
}
bool TSFBridgeImpl::IsInitialized() {
return client_id_ != TF_CLIENTID_NULL;
}
void TSFBridgeImpl::UpdateAssociateFocus() {
if (!IsInitialized()) {
// Hasn't been initialized yet. Do nothing.
return;
}
if (attached_window_handle_ == nullptr)
return;
TSFDocument* document = GetAssociatedDocument();
if (document == nullptr) {
ClearAssociateFocus();
return;
}
// NOTE: ITfThreadMgr::AssociateFocus does not increment the ref count of
// the document manager to be attached. It is our responsibility to make sure
// the attached document manager will not be destroyed while it is attached.
// This should be true as long as TSFBridge::Shutdown() is called late phase
// of UI thread shutdown.
// AssociateFocus calls SetFocus on the document manager internally
Microsoft::WRL::ComPtr<ITfDocumentMgr> previous_focus;
thread_manager_->AssociateFocus(attached_window_handle_,
document->document_manager.Get(),
&previous_focus);
}
void TSFBridgeImpl::ClearAssociateFocus() {
if (!IsInitialized()) {
// Hasn't been initialized yet. Do nothing.
return;
}
if (attached_window_handle_ == nullptr)
return;
Microsoft::WRL::ComPtr<ITfDocumentMgr> previous_focus;
thread_manager_->AssociateFocus(attached_window_handle_, nullptr,
&previous_focus);
}
TSFBridgeImpl::TSFDocument* TSFBridgeImpl::GetAssociatedDocument() {
if (!client_)
return nullptr;
TSFDocumentMap::iterator it = tsf_document_map_.find(input_type_);
if (it == tsf_document_map_.end()) {
it = tsf_document_map_.find(TEXT_INPUT_TYPE_TEXT);
// This check is necessary because it's possible that we failed to
// initialize |tsf_document_map_| and it has no TEXT_INPUT_TYPE_TEXT.
if (it == tsf_document_map_.end())
return nullptr;
}
return &it->second;
}
base::ThreadLocalOwnedPointer<TSFBridge>& GetThreadLocalTSFBridge() {
static base::NoDestructor<base::ThreadLocalOwnedPointer<TSFBridge>>
tsf_bridge;
return *tsf_bridge;
}
} // namespace
// TsfBridge -----------------------------------------------------------------
// static
HRESULT TSFBridge::Initialize() {
TRACE_EVENT0("ime", "TSFBridge::Initialize");
if (!base::CurrentUIThread::IsSet()) {
return E_FAIL;
}
if (GetThreadLocalTSFBridge().Get()) {
return S_OK;
}
auto delegate = std::make_unique<TSFBridgeImpl>();
HRESULT hr = delegate->Initialize();
if (SUCCEEDED(hr)) {
ReplaceThreadLocalTSFBridge(std::move(delegate));
}
return hr;
}
// static
void TSFBridge::InitializeForTesting() {
if (!base::CurrentUIThread::IsSet()) {
return;
}
ReplaceThreadLocalTSFBridge(std::make_unique<MockTSFBridge>());
}
// static
void TSFBridge::ReplaceThreadLocalTSFBridge(
std::unique_ptr<TSFBridge> new_instance) {
if (!base::CurrentUIThread::IsSet()) {
return;
}
GetThreadLocalTSFBridge().Set(std::move(new_instance));
}
// static
void TSFBridge::Shutdown() {
TRACE_EVENT0("ime", "TSFBridge::Shutdown");
ReplaceThreadLocalTSFBridge(nullptr);
}
// static
TSFBridge* TSFBridge::GetInstance() {
return base::CurrentUIThread::IsSet() ? GetThreadLocalTSFBridge().Get()
: nullptr;
}
} // namespace ui
|