Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / accessibility / accessibility_event_recorder_win.cc
blobb90fc8c23ea1ff16350399c9f7d8dec69de6c954
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/accessibility/accessibility_event_recorder.h"
7 #include <oleacc.h>
9 #include <string>
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_piece.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/win/scoped_bstr.h"
17 #include "base/win/scoped_comptr.h"
18 #include "base/win/scoped_variant.h"
19 #include "content/browser/accessibility/accessibility_tree_formatter_utils_win.h"
20 #include "content/browser/accessibility/browser_accessibility_manager.h"
21 #include "content/browser/accessibility/browser_accessibility_win.h"
22 #include "third_party/iaccessible2/ia2_api_all.h"
23 #include "ui/base/win/atl_module.h"
25 namespace content {
27 namespace {
29 std::string RoleVariantToString(const base::win::ScopedVariant& role) {
30 if (role.type() == VT_I4) {
31 return base::UTF16ToUTF8(IAccessibleRoleToString(V_I4(role.ptr())));
32 } else if (role.type() == VT_BSTR) {
33 return base::UTF16ToUTF8(
34 base::string16(V_BSTR(role.ptr()), SysStringLen(V_BSTR(role.ptr()))));
36 return std::string();
39 HRESULT QueryIAccessible2(IAccessible* accessible, IAccessible2** accessible2) {
40 base::win::ScopedComPtr<IServiceProvider> service_provider;
41 HRESULT hr = accessible->QueryInterface(service_provider.Receive());
42 return SUCCEEDED(hr) ?
43 service_provider->QueryService(IID_IAccessible2, accessible2) : hr;
46 HRESULT QueryIAccessibleText(IAccessible* accessible,
47 IAccessibleText** accessible_text) {
48 base::win::ScopedComPtr<IServiceProvider> service_provider;
49 HRESULT hr = accessible->QueryInterface(service_provider.Receive());
50 return SUCCEEDED(hr) ?
51 service_provider->QueryService(IID_IAccessibleText, accessible_text) : hr;
54 std::string BstrToUTF8(BSTR bstr) {
55 base::string16 str16(bstr, SysStringLen(bstr));
57 // IAccessibleText returns the text you get by appending all static text
58 // children, with an "embedded object character" for each non-text child.
59 // Pretty-print the embedded object character as <obj> so that test output
60 // is human-readable.
61 base::StringPiece16 embedded_character(
62 &BrowserAccessibilityWin::kEmbeddedCharacter, 1);
63 base::ReplaceChars(str16, embedded_character, L"<obj>", &str16);
65 return base::UTF16ToUTF8(str16);
68 std::string AccessibilityEventToStringUTF8(int32 event_id) {
69 return base::UTF16ToUTF8(AccessibilityEventToString(event_id));
72 } // namespace
74 class AccessibilityEventRecorderWin : public AccessibilityEventRecorder {
75 public:
76 explicit AccessibilityEventRecorderWin(BrowserAccessibilityManager* manager);
77 ~AccessibilityEventRecorderWin() override;
79 // Callback registered by SetWinEventHook. Just calls OnWinEventHook.
80 static void CALLBACK WinEventHookThunk(
81 HWINEVENTHOOK handle,
82 DWORD event,
83 HWND hwnd,
84 LONG obj_id,
85 LONG child_id,
86 DWORD event_thread,
87 DWORD event_time);
89 private:
90 // Called by the thunk registered by SetWinEventHook. Retrives accessibility
91 // info about the node the event was fired on and appends a string to
92 // the event log.
93 void OnWinEventHook(HWINEVENTHOOK handle,
94 DWORD event,
95 HWND hwnd,
96 LONG obj_id,
97 LONG child_id,
98 DWORD event_thread,
99 DWORD event_time);
101 // Wrapper around AccessibleObjectFromWindow because the function call
102 // inexplicably flakes sometimes on build/trybots.
103 HRESULT AccessibleObjectFromWindowWrapper(
104 HWND hwnd, DWORD dwId, REFIID riid, void **ppvObject);
106 HWINEVENTHOOK win_event_hook_handle_;
107 static AccessibilityEventRecorderWin* instance_;
110 // static
111 AccessibilityEventRecorderWin*
112 AccessibilityEventRecorderWin::instance_ = nullptr;
114 // static
115 AccessibilityEventRecorder* AccessibilityEventRecorder::Create(
116 BrowserAccessibilityManager* manager) {
117 return new AccessibilityEventRecorderWin(manager);
120 // static
121 void CALLBACK AccessibilityEventRecorderWin::WinEventHookThunk(
122 HWINEVENTHOOK handle,
123 DWORD event,
124 HWND hwnd,
125 LONG obj_id,
126 LONG child_id,
127 DWORD event_thread,
128 DWORD event_time) {
129 if (instance_) {
130 instance_->OnWinEventHook(handle, event, hwnd, obj_id, child_id,
131 event_thread, event_time);
135 AccessibilityEventRecorderWin::AccessibilityEventRecorderWin(
136 BrowserAccessibilityManager* manager)
137 : AccessibilityEventRecorder(manager) {
138 CHECK(!instance_) << "There can be only one instance of"
139 << " WinAccessibilityEventMonitor at a time.";
140 instance_ = this;
141 win_event_hook_handle_ = SetWinEventHook(
142 EVENT_MIN,
143 EVENT_MAX,
144 GetModuleHandle(NULL),
145 &AccessibilityEventRecorderWin::WinEventHookThunk,
146 GetCurrentProcessId(),
147 0, // Hook all threads
148 WINEVENT_INCONTEXT);
149 CHECK(win_event_hook_handle_);
152 AccessibilityEventRecorderWin::~AccessibilityEventRecorderWin() {
153 UnhookWinEvent(win_event_hook_handle_);
154 instance_ = NULL;
157 void AccessibilityEventRecorderWin::OnWinEventHook(
158 HWINEVENTHOOK handle,
159 DWORD event,
160 HWND hwnd,
161 LONG obj_id,
162 LONG child_id,
163 DWORD event_thread,
164 DWORD event_time) {
165 base::win::ScopedComPtr<IAccessible> browser_accessible;
166 HRESULT hr = AccessibleObjectFromWindowWrapper(
167 hwnd,
168 obj_id,
169 IID_IAccessible,
170 reinterpret_cast<void**>(browser_accessible.Receive()));
171 if (!SUCCEEDED(hr)) {
172 // Note: our event hook will pick up some superfluous events we
173 // don't care about, so it's safe to just ignore these failures.
174 // Same below for other HRESULT checks.
175 VLOG(1) << "Ignoring result " << hr << " from AccessibleObjectFromWindow";
176 return;
179 base::win::ScopedVariant childid_variant(child_id);
180 base::win::ScopedComPtr<IDispatch> dispatch;
181 hr = browser_accessible->get_accChild(childid_variant, dispatch.Receive());
182 if (!SUCCEEDED(hr) || !dispatch) {
183 VLOG(1) << "Ignoring result " << hr << " and result " << dispatch
184 << " from get_accChild";
185 return;
188 base::win::ScopedComPtr<IAccessible> iaccessible;
189 hr = dispatch.QueryInterface(iaccessible.Receive());
190 if (!SUCCEEDED(hr)) {
191 VLOG(1) << "Ignoring result " << hr << " from QueryInterface";
192 return;
195 std::string event_str = AccessibilityEventToStringUTF8(event);
196 if (event_str.empty()) {
197 VLOG(1) << "Ignoring event " << event;
198 return;
201 base::win::ScopedVariant childid_self(CHILDID_SELF);
202 base::win::ScopedVariant role;
203 iaccessible->get_accRole(childid_self, role.Receive());
204 base::win::ScopedBstr name_bstr;
205 iaccessible->get_accName(childid_self, name_bstr.Receive());
206 base::win::ScopedBstr value_bstr;
207 iaccessible->get_accValue(childid_self, value_bstr.Receive());
208 base::win::ScopedVariant state;
209 iaccessible->get_accState(childid_self, state.Receive());
210 int ia_state = V_I4(state.ptr());
212 // Avoid flakiness. Events fired on a WINDOW are out of the control
213 // of a test.
214 if (role.type() == VT_I4 && ROLE_SYSTEM_WINDOW == V_I4(role.ptr())) {
215 VLOG(1) << "Ignoring event " << event << " on ROLE_SYSTEM_WINDOW";
216 return;
219 // Avoid flakiness. The "offscreen" state depends on whether the browser
220 // window is frontmost or not, and "hottracked" depends on whether the
221 // mouse cursor happens to be over the element.
222 ia_state &= (~STATE_SYSTEM_OFFSCREEN & ~STATE_SYSTEM_HOTTRACKED);
224 // The "readonly" state is set on almost every node and doesn't typically
225 // change, so filter it out to keep the output less verbose.
226 ia_state &= ~STATE_SYSTEM_READONLY;
228 AccessibleStates ia2_state = 0;
229 base::win::ScopedComPtr<IAccessible2> iaccessible2;
230 hr = QueryIAccessible2(iaccessible.get(), iaccessible2.Receive());
231 if (SUCCEEDED(hr))
232 iaccessible2->get_states(&ia2_state);
234 std::string log = base::StringPrintf(
235 "%s on role=%s", event_str.c_str(), RoleVariantToString(role).c_str());
236 if (name_bstr.Length() > 0)
237 log += base::StringPrintf(" name=\"%s\"", BstrToUTF8(name_bstr).c_str());
238 if (value_bstr.Length() > 0)
239 log += base::StringPrintf(" value=\"%s\"", BstrToUTF8(value_bstr).c_str());
240 log += " ";
241 log += base::UTF16ToUTF8(IAccessibleStateToString(ia_state));
242 log += " ";
243 log += base::UTF16ToUTF8(IAccessible2StateToString(ia2_state));
245 // For TEXT_REMOVED and TEXT_INSERTED events, query the text that was
246 // inserted or removed and include that in the log.
247 base::win::ScopedComPtr<IAccessibleText> accessible_text;
248 hr = QueryIAccessibleText(iaccessible.get(), accessible_text.Receive());
249 if (SUCCEEDED(hr)) {
250 if (event == IA2_EVENT_TEXT_REMOVED) {
251 IA2TextSegment old_text;
252 if (SUCCEEDED(accessible_text->get_oldText(&old_text))) {
253 log += base::StringPrintf(" old_text={'%s' start=%d end=%d}",
254 BstrToUTF8(old_text.text).c_str(),
255 old_text.start,
256 old_text.end);
259 if (event == IA2_EVENT_TEXT_INSERTED) {
260 IA2TextSegment new_text;
261 if (SUCCEEDED(accessible_text->get_newText(&new_text))) {
262 log += base::StringPrintf(" new_text={'%s' start=%d end=%d}",
263 BstrToUTF8(new_text.text).c_str(),
264 new_text.start,
265 new_text.end);
270 log = base::UTF16ToUTF8(
271 base::CollapseWhitespace(base::UTF8ToUTF16(log), true));
272 event_logs_.push_back(log);
275 HRESULT AccessibilityEventRecorderWin::AccessibleObjectFromWindowWrapper(
276 HWND hwnd, DWORD dw_id, REFIID riid, void** ppv_object) {
277 HRESULT hr = ::AccessibleObjectFromWindow(hwnd, dw_id, riid, ppv_object);
278 if (SUCCEEDED(hr))
279 return hr;
281 // The above call to ::AccessibleObjectFromWindow fails for unknown
282 // reasons every once in a while on the bots. Work around it by grabbing
283 // the object directly from the BrowserAccessibilityManager.
284 HWND accessibility_hwnd =
285 manager_->delegate()->AccessibilityGetAcceleratedWidget();
286 if (accessibility_hwnd != hwnd)
287 return E_FAIL;
289 IAccessible* obj = manager_->GetRoot()->ToBrowserAccessibilityWin();
290 obj->AddRef();
291 *ppv_object = obj;
292 return S_OK;
295 } // namespace content