blob: 3362b973d6549c5b8ff3099aa247c6379fa20856 [file] [log] [blame]
// Copyright 2010-2014, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "win32/base/focus_hierarchy_observer.h"
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
// Workaround against KB813540
#include <atlbase_mozc.h>
#include <atlcom.h>
#include <memory>
#include "base/logging.h"
#include "base/port.h"
#include "base/util.h"
#include "win32/base/accessible_object.h"
#include "win32/base/browser_info.h"
namespace mozc {
namespace win32 {
namespace {
using ::ATL::CComBSTR;
using ::ATL::CComPtr;
using ::ATL::CComQIPtr;
using ::ATL::CComVariant;
using ::std::unique_ptr;
const size_t kMaxHierarchyLevel = 50;
DWORD g_tls_index = TLS_OUT_OF_INDEXES;
HMODULE g_module_handle = nullptr;
string UTF16ToUTF8(const wstring &str) {
string utf8;
Util::WideToUTF8(str, &utf8);
return utf8;
}
string GetWindowTestAsUTF8(HWND window_handle) {
const int text_len = ::GetWindowTextLengthW(window_handle);
if (text_len <= 0) {
return "";
}
const int buffer_len = text_len + 1;
unique_ptr<wchar_t[]> buffer(new wchar_t[buffer_len]);
const int copied_len_without_null =
::GetWindowText(window_handle, buffer.get(), buffer_len);
if (copied_len_without_null <= 0 ||
copied_len_without_null + 1 > buffer_len) {
return "";
}
return UTF16ToUTF8(wstring(buffer.get(), copied_len_without_null));
}
string GetWindowClassNameAsUTF8(HWND window_handle) {
const int kBufferLen = 256 + 1;
unique_ptr<wchar_t[]> buffer(new wchar_t[kBufferLen]);
const int copied_len_without_null =
::GetClassNameW(window_handle, buffer.get(), kBufferLen);
if (copied_len_without_null <= 0 ||
copied_len_without_null + 1 > kBufferLen) {
return "";
}
return UTF16ToUTF8(wstring(buffer.get(), copied_len_without_null));
}
DWORD GetProcessIdFromWindow(HWND window_handle) {
DWORD process_id = 0;
if (::GetWindowThreadProcessId(window_handle, &process_id) == 0) {
return 0;
}
return process_id;
}
void FillWindowInfo(
HWND window_handle,
vector<FocusHierarchyObserver::WindowInfo> *window_hierarchy) {
if (window_handle == nullptr) {
// error
window_hierarchy->clear();
return;
}
const HWND ancestor = ::GetAncestor(window_handle, GA_ROOT);
if (ancestor == nullptr) {
// error
window_hierarchy->clear();
return;
}
while (true) {
if (window_hierarchy->size() > kMaxHierarchyLevel) {
window_hierarchy->clear();
return;
}
FocusHierarchyObserver::WindowInfo element;
element.window_handle = window_handle;
element.title = GetWindowTestAsUTF8(window_handle);
element.class_name = GetWindowClassNameAsUTF8(window_handle);
element.process_id = GetProcessIdFromWindow(window_handle);
window_hierarchy->push_back(element);
if (window_handle == ancestor) {
return;
}
window_handle = ::GetParent(window_handle);
if (window_handle == nullptr) {
return;
}
}
DCHECK(false) << "must not reach here.";
}
void FillAccessibleInfo(
AccessibleObject accessible,
HWND focused_window_handle,
vector<AccessibleObjectInfo> *hierarchy) {
if (!accessible.IsValid()) {
return;
}
hierarchy->push_back(accessible.GetInfo());
while (true) {
if (hierarchy->size() > kMaxHierarchyLevel) {
hierarchy->clear();
break;
}
AccessibleObject parent = accessible.GetParent();
if (!parent.IsValid()) {
break;
}
HWND parent_window_handle = nullptr;
if (!parent.GetWindowHandle(&parent_window_handle) ||
focused_window_handle != parent_window_handle) {
// finish!
break;
}
hierarchy->push_back(parent.GetInfo());
accessible = parent;
}
}
class ThreadLocalInfo {
public:
vector<AccessibleObjectInfo> ui_hierarchy() const {
return ui_hierarchy_;
}
vector<FocusHierarchyObserver::WindowInfo> window_hierarchy() const {
return window_hierarchy_;
}
string root_window_name() const {
return root_window_name_;
}
void SyncFocusHierarchy() {
const HWND focused_window = ::GetFocus();
if (GetProcessIdFromWindow(focused_window) != ::GetCurrentProcessId()) {
return;
}
AccessibleObject accesible = AccessibleObject::FromWindow(focused_window);
if (accesible.IsValid()) {
AccessibleObject focused_accesible = accesible.GetFocus();
if (focused_accesible.IsValid()) {
accesible = focused_accesible;
}
}
OnInitialize(focused_window, accesible);
}
void AddRef() {
++ref_count_;
}
void Release() {
--ref_count_;
if (ref_count_ <= 0) {
delete this;
}
}
static ThreadLocalInfo *Self() {
if (g_tls_index == TLS_OUT_OF_INDEXES) {
return nullptr;
}
return static_cast<ThreadLocalInfo *>(::TlsGetValue(g_tls_index));
}
static ThreadLocalInfo *EnsureExists() {
DCHECK_NE(TLS_OUT_OF_INDEXES, g_tls_index);
auto *info = static_cast<ThreadLocalInfo *>(::TlsGetValue(g_tls_index));
if (info == nullptr) {
info = ThreadLocalInfo::Create();
}
return info;
}
private:
explicit ThreadLocalInfo(HWINEVENTHOOK hook_handle)
: ref_count_(0),
hook_handle_(hook_handle) {
::TlsSetValue(g_tls_index, this);
}
~ThreadLocalInfo() {
if (hook_handle_ != nullptr) {
::UnhookWinEvent(hook_handle_);
hook_handle_ = nullptr;
}
::TlsSetValue(g_tls_index, nullptr);
}
static ThreadLocalInfo *Create() {
if (g_module_handle == nullptr) {
return nullptr;
}
auto hook_handle = ::SetWinEventHook(
EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS, g_module_handle,
WinEventProc, ::GetCurrentProcessId(), ::GetCurrentThreadId(),
WINEVENT_INCONTEXT);
if (hook_handle == nullptr) {
return nullptr;
}
return new ThreadLocalInfo(hook_handle);
}
static void CALLBACK WinEventProc(HWINEVENTHOOK hook_handle,
DWORD event,
HWND window_handle,
LONG object_id,
LONG child_id,
DWORD event_thread,
DWORD event_time) {
if (g_module_handle == nullptr) {
return;
}
if (!::IsWindow(window_handle)) {
return;
}
if (event != EVENT_OBJECT_FOCUS) {
return;
}
const DWORD window_process_id = GetProcessIdFromWindow(window_handle);
if (window_process_id != ::GetCurrentProcessId()) {
// avoid interprocess call.
return;
}
auto *self = Self();
if (self == nullptr) {
return;
}
AccessibleObject accessible;
{
CComPtr<IAccessible> container;
CComVariant child;
HRESULT result = ::AccessibleObjectFromEvent(
window_handle, object_id, child_id, &container, &child);
if (SUCCEEDED(result) && (container != nullptr) && (child.vt == VT_I4)) {
accessible = AccessibleObject(container, child.lVal);
}
}
self->OnInitialize(window_handle, accessible);
}
void OnInitialize(HWND window_handle, AccessibleObject accessible) {
ui_hierarchy_.clear();
window_hierarchy_.clear();
root_window_name_.clear();
FillWindowInfo(window_handle, &window_hierarchy_);
FillAccessibleInfo(accessible, window_handle, &ui_hierarchy_);
if (!window_hierarchy_.empty()) {
const HWND root_window_handle = window_hierarchy_.back().window_handle;
const DWORD root_window_process_id =
GetProcessIdFromWindow(root_window_handle);
if (root_window_process_id != ::GetCurrentProcessId()) {
// avoid interprocess call
root_window_name_ = GetWindowTestAsUTF8(root_window_handle);
} else {
const AccessibleObject root_object =
AccessibleObject::FromWindow(root_window_handle);
if (root_object.IsValid()) {
root_window_name_ = root_object.GetInfo().name;
} else {
root_window_name_ = GetWindowTestAsUTF8(root_window_handle);
}
}
}
}
int ref_count_;
HWINEVENTHOOK hook_handle_;
vector<AccessibleObjectInfo> ui_hierarchy_;
vector<FocusHierarchyObserver::WindowInfo> window_hierarchy_;
string root_window_name_;
};
bool TlsAvailable() {
return g_tls_index != TLS_OUT_OF_INDEXES;
}
class FocusHierarchyObserverImpl : public FocusHierarchyObserver {
public:
static FocusHierarchyObserver *Create() {
if (!TlsAvailable()) {
return nullptr;
}
auto *info = ThreadLocalInfo::EnsureExists();
info->AddRef();
return new FocusHierarchyObserverImpl();
}
private:
FocusHierarchyObserverImpl() {
}
virtual ~FocusHierarchyObserverImpl() {
auto *self = ThreadLocalInfo::Self();
if (self != nullptr) {
self->Release();
}
}
// FocusHierarchyObserver overrides:
virtual void SyncFocusHierarchy() {
auto *self = ThreadLocalInfo::Self();
if (self == nullptr) {
return;
}
self->SyncFocusHierarchy();
}
virtual bool IsAbailable() const {
return ThreadLocalInfo::Self() != nullptr;
}
virtual vector<AccessibleObjectInfo> GetUIHierarchy() const {
auto *self = ThreadLocalInfo::Self();
if (self == nullptr) {
return vector<AccessibleObjectInfo>();
}
return self->ui_hierarchy();
}
virtual vector<WindowInfo> GetWindowHierarchy() const {
auto *self = ThreadLocalInfo::Self();
if (self == nullptr) {
return vector<FocusHierarchyObserver::WindowInfo>();
}
return self->window_hierarchy();
}
virtual string GetRootWindowName() const {
auto *self = ThreadLocalInfo::Self();
if (self == nullptr) {
return "";
}
return self->root_window_name();
}
private:
DISALLOW_COPY_AND_ASSIGN(FocusHierarchyObserverImpl);
};
class FocusHierarchyObserverNullImpl : public FocusHierarchyObserver {
public:
FocusHierarchyObserverNullImpl() {
}
virtual ~FocusHierarchyObserverNullImpl() {
}
private:
// FocusHierarchyObserver overrides:
virtual void SyncFocusHierarchy() {
}
virtual bool IsAbailable() const {
return false;
}
virtual vector<AccessibleObjectInfo> GetUIHierarchy() const {
return vector<AccessibleObjectInfo>();
}
virtual vector<WindowInfo> GetWindowHierarchy() const {
return vector<WindowInfo>();
}
virtual string GetRootWindowName() const {
return "";
}
private:
DISALLOW_COPY_AND_ASSIGN(FocusHierarchyObserverNullImpl);
};
} // namespace
FocusHierarchyObserver::~FocusHierarchyObserver() {
}
FocusHierarchyObserver::WindowInfo::WindowInfo()
: window_handle(nullptr),
process_id(0) {
}
// static
void FocusHierarchyObserver::OnDllProcessAttach(HINSTANCE module_handle,
bool static_loading) {
g_tls_index = ::TlsAlloc();
g_module_handle = module_handle;
}
// static
void FocusHierarchyObserver::OnDllProcessDetach(HINSTANCE module_handle,
bool process_shutdown) {
if (g_tls_index != TLS_OUT_OF_INDEXES) {
::TlsFree(g_tls_index);
g_tls_index = TLS_OUT_OF_INDEXES;
}
g_module_handle = nullptr;
}
// static
FocusHierarchyObserver *FocusHierarchyObserver::Create() {
// Note: Currently FocusHierarchyObserver is enabled only with Chromium.
// TODO(yukawa): Extend the target applications.
if (BrowserInfo::GetBrowerType() != BrowserInfo::kBrowserTypeChrome) {
return new FocusHierarchyObserverNullImpl();
}
auto *impl = FocusHierarchyObserverImpl::Create();
if (impl != nullptr) {
return impl;
}
return new FocusHierarchyObserverNullImpl();
}
} // namespace win32
} // namespace mozc