OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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. | |
4 | |
5 #include "chrome_frame/test/chrome_frame_ui_test_utils.h" | |
6 | |
7 #include <windows.h> | |
8 | |
9 #include <sstream> | |
10 #include <stack> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/memory/scoped_ptr.h" | |
14 #include "base/message_loop/message_loop.h" | |
15 #include "base/path_service.h" | |
16 #include "base/strings/string_util.h" | |
17 #include "base/strings/stringprintf.h" | |
18 #include "base/strings/utf_string_conversions.h" | |
19 #include "base/win/scoped_bstr.h" | |
20 #include "chrome/common/chrome_paths.h" | |
21 #include "chrome_frame/test/win_event_receiver.h" | |
22 #include "chrome_frame/utils.h" | |
23 #include "testing/gtest/include/gtest/gtest.h" | |
24 #include "third_party/iaccessible2/ia2_api_all.h" | |
25 #include "ui/gfx/point.h" | |
26 #include "ui/gfx/rect.h" | |
27 | |
28 namespace chrome_frame_test { | |
29 | |
30 // Timeout for waiting on Chrome to create the accessibility tree for the DOM. | |
31 const int kChromeDOMAccessibilityTreeTimeoutMs = 10 * 1000; | |
32 | |
33 // Timeout for waiting on a menu to popup. | |
34 const int kMenuPopupTimeoutMs = 10 * 1000; | |
35 | |
36 // AccObject methods | |
37 AccObject::AccObject(IAccessible* accessible, int child_id) | |
38 : accessible_(accessible), child_id_(child_id) { | |
39 DCHECK(accessible); | |
40 if (child_id != CHILDID_SELF) { | |
41 base::win::ScopedComPtr<IDispatch> dispatch; | |
42 // This class does not support referring to a full MSAA object using the | |
43 // parent object and the child id. | |
44 HRESULT result = accessible_->get_accChild(child_id_, dispatch.Receive()); | |
45 if (result != S_FALSE && result != E_NOINTERFACE) { | |
46 LOG(ERROR) << "AccObject created which refers to full MSAA object using " | |
47 "parent object and child id. This should NOT be done."; | |
48 } | |
49 DCHECK(result == S_FALSE || result == E_NOINTERFACE); | |
50 } | |
51 } | |
52 | |
53 // static | |
54 AccObject* AccObject::CreateFromWindow(HWND hwnd) { | |
55 base::win::ScopedComPtr<IAccessible> accessible; | |
56 ::AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, | |
57 IID_IAccessible, reinterpret_cast<void**>(accessible.Receive())); | |
58 if (accessible) | |
59 return new AccObject(accessible); | |
60 return NULL; | |
61 } | |
62 | |
63 // static | |
64 AccObject* AccObject::CreateFromEvent(HWND hwnd, LONG object_id, | |
65 LONG child_id) { | |
66 base::win::ScopedComPtr<IAccessible> accessible; | |
67 base::win::ScopedVariant acc_child_id; | |
68 ::AccessibleObjectFromEvent(hwnd, object_id, child_id, accessible.Receive(), | |
69 acc_child_id.Receive()); | |
70 if (accessible && acc_child_id.type() == VT_I4) | |
71 return new AccObject(accessible, V_I4(&acc_child_id)); | |
72 return NULL; | |
73 } | |
74 | |
75 // static | |
76 AccObject* AccObject::CreateFromDispatch(IDispatch* dispatch) { | |
77 if (dispatch) { | |
78 base::win::ScopedComPtr<IAccessible> accessible; | |
79 accessible.QueryFrom(dispatch); | |
80 if (accessible) | |
81 return new AccObject(accessible); | |
82 } | |
83 return NULL; | |
84 } | |
85 | |
86 // static | |
87 AccObject* AccObject::CreateFromPoint(int x, int y) { | |
88 base::win::ScopedComPtr<IAccessible> accessible; | |
89 base::win::ScopedVariant child_id; | |
90 POINT point = {x, y}; | |
91 ::AccessibleObjectFromPoint(point, accessible.Receive(), child_id.Receive()); | |
92 if (accessible && child_id.type() == VT_I4) | |
93 return new AccObject(accessible, V_I4(&child_id)); | |
94 return NULL; | |
95 } | |
96 | |
97 bool AccObject::DoDefaultAction() { | |
98 // Prevent clients from using this method to try to select menu items, which | |
99 // does not work with a locked desktop. | |
100 std::wstring class_name; | |
101 if (GetWindowClassName(&class_name)) { | |
102 DCHECK(class_name != L"#32768") << "Do not use DoDefaultAction with menus"; | |
103 } | |
104 | |
105 HRESULT result = accessible_->accDoDefaultAction(child_id_); | |
106 EXPECT_HRESULT_SUCCEEDED(result) | |
107 << "Could not do default action for AccObject: " << GetDescription(); | |
108 return SUCCEEDED(result); | |
109 } | |
110 | |
111 bool AccObject::LeftClick() { | |
112 return PostMouseClickAtCenter(WM_LBUTTONDOWN, WM_LBUTTONUP); | |
113 } | |
114 | |
115 bool AccObject::RightClick() { | |
116 return PostMouseClickAtCenter(WM_RBUTTONDOWN, WM_RBUTTONUP); | |
117 } | |
118 | |
119 bool AccObject::Focus() { | |
120 EXPECT_HRESULT_SUCCEEDED( | |
121 accessible_->accSelect(SELFLAG_TAKEFOCUS, child_id_)); | |
122 | |
123 // Double check that the object actually received focus. In some cases | |
124 // the parent object must have the focus first. | |
125 bool did_focus = false; | |
126 base::win::ScopedVariant focused; | |
127 if (SUCCEEDED(accessible_->get_accFocus(focused.Receive()))) { | |
128 if (focused.type() != VT_EMPTY) | |
129 did_focus = true; | |
130 } | |
131 EXPECT_TRUE(did_focus) << "Could not focus AccObject: " << GetDescription(); | |
132 return did_focus; | |
133 } | |
134 | |
135 bool AccObject::Select() { | |
136 // SELFLAG_TAKESELECTION needs to be combined with the focus in order to | |
137 // take effect. | |
138 int selection_flag = SELFLAG_TAKEFOCUS | SELFLAG_TAKESELECTION; | |
139 EXPECT_HRESULT_SUCCEEDED(accessible_->accSelect(selection_flag, child_id_)); | |
140 | |
141 // Double check that the object actually received selection. | |
142 bool did_select = false; | |
143 base::win::ScopedVariant selected; | |
144 if (SUCCEEDED(accessible_->get_accSelection(selected.Receive()))) { | |
145 if (selected.type() != VT_EMPTY) | |
146 did_select = true; | |
147 } | |
148 EXPECT_TRUE(did_select) << "Could not select AccObject: " << GetDescription(); | |
149 return did_select; | |
150 } | |
151 | |
152 bool AccObject::SetValue(const std::wstring& value) { | |
153 base::win::ScopedBstr value_bstr(value.c_str()); | |
154 EXPECT_HRESULT_SUCCEEDED(accessible_->put_accValue(child_id_, value_bstr)); | |
155 | |
156 // Double check that the object's value has actually changed. Some objects' | |
157 // values can not be changed. | |
158 bool did_set_value = false; | |
159 std::wstring actual_value = L"-"; | |
160 if (GetValue(&actual_value) && value == actual_value) { | |
161 did_set_value = true; | |
162 } | |
163 EXPECT_TRUE(did_set_value) << "Could not set value for AccObject: " | |
164 << GetDescription(); | |
165 return did_set_value; | |
166 } | |
167 | |
168 bool AccObject::GetName(std::wstring* name) { | |
169 DCHECK(name); | |
170 base::win::ScopedBstr name_bstr; | |
171 HRESULT result = accessible_->get_accName(child_id_, name_bstr.Receive()); | |
172 if (SUCCEEDED(result)) | |
173 name->assign(name_bstr, name_bstr.Length()); | |
174 return SUCCEEDED(result); | |
175 } | |
176 | |
177 bool AccObject::GetRoleText(std::wstring* role_text) { | |
178 DCHECK(role_text); | |
179 base::win::ScopedVariant role_variant; | |
180 if (SUCCEEDED(accessible_->get_accRole(child_id_, role_variant.Receive()))) { | |
181 if (role_variant.type() == VT_I4) { | |
182 wchar_t role_text_array[50]; | |
183 UINT characters = ::GetRoleText(V_I4(&role_variant), role_text_array, | |
184 arraysize(role_text_array)); | |
185 if (characters) { | |
186 *role_text = role_text_array; | |
187 return true; | |
188 } else { | |
189 LOG(ERROR) << "GetRoleText failed for role: " << V_I4(&role_variant); | |
190 } | |
191 } else if (role_variant.type() == VT_BSTR) { | |
192 *role_text = V_BSTR(&role_variant); | |
193 return true; | |
194 } else { | |
195 LOG(ERROR) << "Role was unexpected variant type: " | |
196 << role_variant.type(); | |
197 } | |
198 } | |
199 return false; | |
200 } | |
201 | |
202 bool AccObject::GetValue(std::wstring* value) { | |
203 DCHECK(value); | |
204 base::win::ScopedBstr value_bstr; | |
205 HRESULT result = accessible_->get_accValue(child_id_, value_bstr.Receive()); | |
206 if (SUCCEEDED(result)) | |
207 value->assign(value_bstr, value_bstr.Length()); | |
208 return SUCCEEDED(result); | |
209 } | |
210 | |
211 bool AccObject::GetState(int* state) { | |
212 DCHECK(state); | |
213 base::win::ScopedVariant state_variant; | |
214 if (SUCCEEDED(accessible_->get_accState(child_id_, | |
215 state_variant.Receive()))) { | |
216 if (state_variant.type() == VT_I4) { | |
217 *state = V_I4(&state_variant); | |
218 return true; | |
219 } | |
220 } | |
221 return false; | |
222 } | |
223 | |
224 bool AccObject::GetLocation(gfx::Rect* location) { | |
225 DCHECK(location); | |
226 long left, top, width, height; // NOLINT | |
227 HRESULT result = accessible_->accLocation(&left, &top, &width, &height, | |
228 child_id_); | |
229 if (SUCCEEDED(result)) | |
230 *location = gfx::Rect(left, top, width, height); | |
231 return SUCCEEDED(result); | |
232 } | |
233 | |
234 bool AccObject::GetLocationInClient(gfx::Rect* client_location) { | |
235 DCHECK(client_location); | |
236 gfx::Rect location; | |
237 if (!GetLocation(&location)) | |
238 return false; | |
239 HWND container_window = NULL; | |
240 if (!GetWindow(&container_window)) | |
241 return false; | |
242 POINT offset = {0, 0}; | |
243 if (!::ScreenToClient(container_window, &offset)) { | |
244 LOG(ERROR) << "Could not convert from screen to client coordinates for " | |
245 "window containing accessibility object: " | |
246 << GetDescription(); | |
247 return false; | |
248 } | |
249 location.Offset(offset.x, offset.y); | |
250 *client_location = location; | |
251 return true; | |
252 } | |
253 | |
254 AccObject* AccObject::GetParent() { | |
255 if (IsSimpleElement()) | |
256 return new AccObject(accessible_); | |
257 base::win::ScopedComPtr<IDispatch> dispatch; | |
258 if (FAILED(accessible_->get_accParent(dispatch.Receive()))) | |
259 return NULL; | |
260 return AccObject::CreateFromDispatch(dispatch.get()); | |
261 } | |
262 | |
263 bool AccObject::GetChildren(RefCountedAccObjectVector* client_objects) { | |
264 DCHECK(client_objects); | |
265 int child_count; | |
266 if (!GetChildCount(&child_count)) { | |
267 LOG(ERROR) << "Failed to get child count of AccObject"; | |
268 return false; | |
269 } | |
270 if (child_count == 0) | |
271 return true; | |
272 | |
273 RefCountedAccObjectVector objects; | |
274 // Find children using |AccessibleChildren|. | |
275 scoped_ptr<VARIANT[]> children(new VARIANT[child_count]); | |
276 long found_child_count; // NOLINT | |
277 if (FAILED(AccessibleChildren(accessible_, 0L, child_count, | |
278 children.get(), | |
279 &found_child_count))) { | |
280 LOG(ERROR) << "Failed to get children of accessible object"; | |
281 return false; | |
282 } | |
283 if (found_child_count > 0) { | |
284 for (int i = 0; i < found_child_count; i++) { | |
285 scoped_refptr<AccObject> obj = CreateFromVariant(this, children[i]); | |
286 if (obj) | |
287 objects.push_back(obj); | |
288 ::VariantClear(&children[i]); | |
289 } | |
290 } | |
291 | |
292 // In some cases, there are more children which can be found only by using | |
293 // the deprecated |accNavigate| method. Many of the menus, such as | |
294 // 'Favorites', cannot be found in IE6 using |AccessibileChildren|. Here we | |
295 // attempt a best effort at finding some remaining children. | |
296 int remaining_child_count = child_count - found_child_count; | |
297 scoped_refptr<AccObject> child_object; | |
298 if (remaining_child_count > 0) { | |
299 GetFromNavigation(NAVDIR_FIRSTCHILD, &child_object); | |
300 } | |
301 while (remaining_child_count > 0 && child_object) { | |
302 // Add to the children list if this child was not found earlier. | |
303 bool already_found = false; | |
304 for (size_t i = 0; i < objects.size(); ++i) { | |
305 if (child_object->Equals(objects[i])) { | |
306 already_found = true; | |
307 break; | |
308 } | |
309 } | |
310 if (!already_found) { | |
311 objects.push_back(child_object); | |
312 remaining_child_count--; | |
313 } | |
314 scoped_refptr<AccObject> next_child_object; | |
315 child_object->GetFromNavigation(NAVDIR_NEXT, &next_child_object); | |
316 child_object = next_child_object; | |
317 } | |
318 | |
319 client_objects->insert(client_objects->end(), objects.begin(), objects.end()); | |
320 return true; | |
321 } | |
322 | |
323 bool AccObject::GetChildCount(int* child_count) { | |
324 DCHECK(child_count); | |
325 *child_count = 0; | |
326 if (!IsSimpleElement()) { | |
327 long long_child_count; // NOLINT | |
328 if (FAILED(accessible_->get_accChildCount(&long_child_count))) | |
329 return false; | |
330 *child_count = static_cast<int>(long_child_count); | |
331 } | |
332 return true; | |
333 } | |
334 | |
335 bool AccObject::GetFromNavigation(long navigation_type, | |
336 scoped_refptr<AccObject>* object) { | |
337 DCHECK(object); | |
338 bool is_child_navigation = navigation_type == NAVDIR_FIRSTCHILD || | |
339 navigation_type == NAVDIR_LASTCHILD; | |
340 DCHECK(!is_child_navigation || !IsSimpleElement()); | |
341 base::win::ScopedVariant object_variant; | |
342 HRESULT result = accessible_->accNavigate(navigation_type, | |
343 child_id_, | |
344 object_variant.Receive()); | |
345 if (FAILED(result)) { | |
346 LOG(WARNING) << "Navigation from accessibility object failed"; | |
347 return false; | |
348 } | |
349 if (result == S_FALSE || object_variant.type() == VT_EMPTY) { | |
350 // This indicates that there was no accessibility object found by the | |
351 // navigation. | |
352 return true; | |
353 } | |
354 AccObject* navigated_to_object; | |
355 if (!is_child_navigation && !IsSimpleElement()) { | |
356 scoped_refptr<AccObject> parent = GetParent(); | |
357 if (!parent.get()) { | |
358 LOG(WARNING) << "Could not get parent for accessibiliy navigation"; | |
359 return false; | |
360 } | |
361 navigated_to_object = CreateFromVariant(parent, object_variant); | |
362 } else { | |
363 navigated_to_object = CreateFromVariant(this, object_variant); | |
364 } | |
365 if (!navigated_to_object) | |
366 return false; | |
367 *object = navigated_to_object; | |
368 return true; | |
369 } | |
370 | |
371 bool AccObject::GetWindow(HWND* window) { | |
372 DCHECK(window); | |
373 return SUCCEEDED(::WindowFromAccessibleObject(accessible_, window)) && window; | |
374 } | |
375 | |
376 bool AccObject::GetWindowClassName(std::wstring* class_name) { | |
377 DCHECK(class_name); | |
378 HWND container_window = NULL; | |
379 if (GetWindow(&container_window)) { | |
380 wchar_t class_arr[MAX_PATH]; | |
381 if (::GetClassName(container_window, class_arr, arraysize(class_arr))) { | |
382 *class_name = class_arr; | |
383 return true; | |
384 } | |
385 } | |
386 return false; | |
387 } | |
388 | |
389 bool AccObject::GetSelectionRange(int* start_offset, int* end_offset) { | |
390 DCHECK(start_offset); | |
391 DCHECK(end_offset); | |
392 base::win::ScopedComPtr<IAccessibleText> accessible_text; | |
393 HRESULT hr = DoQueryService(IID_IAccessibleText, | |
394 accessible_, | |
395 accessible_text.Receive()); | |
396 if (FAILED(hr)) { | |
397 LOG(ERROR) << "Could not get IAccessibleText interface. Error: " << hr | |
398 << "\nIs IAccessible2Proxy.dll registered?"; | |
399 return false; | |
400 } | |
401 | |
402 LONG selection_count = 0; | |
403 accessible_text->get_nSelections(&selection_count); | |
404 LONG start = 0, end = 0; | |
405 if (selection_count > 0) { | |
406 if (FAILED(accessible_text->get_selection(0, &start, &end))) { | |
407 LOG(WARNING) << "Could not get first selection"; | |
408 return false; | |
409 } | |
410 } | |
411 *start_offset = start; | |
412 *end_offset = end; | |
413 return true; | |
414 } | |
415 | |
416 bool AccObject::GetSelectedText(std::wstring* text) { | |
417 DCHECK(text); | |
418 int start = 0, end = 0; | |
419 if (!GetSelectionRange(&start, &end)) | |
420 return false; | |
421 base::win::ScopedComPtr<IAccessibleText> accessible_text; | |
422 HRESULT hr = DoQueryService(IID_IAccessibleText, | |
423 accessible_, | |
424 accessible_text.Receive()); | |
425 if (FAILED(hr)) { | |
426 LOG(ERROR) << "Could not get IAccessibleText interface. Error: " << hr | |
427 << "\nIs IAccessible2Proxy.dll registered?"; | |
428 return false; | |
429 } | |
430 base::win::ScopedBstr text_bstr; | |
431 if (FAILED(accessible_text->get_text(start, end, text_bstr.Receive()))) { | |
432 LOG(WARNING) << "Could not get text from selection range"; | |
433 return false; | |
434 } | |
435 text->assign(text_bstr, text_bstr.Length()); | |
436 return true; | |
437 } | |
438 | |
439 bool AccObject::IsSimpleElement() { | |
440 return V_I4(&child_id_) != CHILDID_SELF; | |
441 } | |
442 | |
443 bool AccObject::Equals(AccObject* other) { | |
444 if (other) { | |
445 DCHECK(child_id_.type() == VT_I4 && other->child_id_.type() == VT_I4); | |
446 return accessible_.get() == other->accessible_.get() && | |
447 V_I4(&child_id_) == V_I4(&other->child_id_); | |
448 } | |
449 return false; | |
450 } | |
451 | |
452 std::wstring AccObject::GetDescription() { | |
453 std::wstring name = L"-", role_text = L"-", value = L"-"; | |
454 if (GetName(&name)) | |
455 name = L"'" + name + L"'"; | |
456 if (GetRoleText(&role_text)) | |
457 role_text = L"'" + role_text + L"'"; | |
458 if (GetValue(&value)) | |
459 value = L"'" + value + L"'"; | |
460 int state = 0; | |
461 GetState(&state); | |
462 return base::StringPrintf(L"[%ls, %ls, %ls, 0x%x]", name.c_str(), | |
463 role_text.c_str(), value.c_str(), state); | |
464 } | |
465 | |
466 std::wstring AccObject::GetTree() { | |
467 std::wostringstream string_stream; | |
468 string_stream << L"Accessibility object tree:" << std::endl; | |
469 string_stream << L"[name, role_text, value, state]" << std::endl; | |
470 | |
471 std::stack<std::pair<scoped_refptr<AccObject>, int> > pairs; | |
472 pairs.push(std::make_pair(this, 0)); | |
473 while (!pairs.empty()) { | |
474 scoped_refptr<AccObject> object = pairs.top().first; | |
475 int depth = pairs.top().second; | |
476 pairs.pop(); | |
477 | |
478 for (int i = 0; i < depth; ++i) | |
479 string_stream << L" "; | |
480 string_stream << object->GetDescription() << std::endl; | |
481 | |
482 RefCountedAccObjectVector children; | |
483 if (object->GetChildren(&children)) { | |
484 for (int i = static_cast<int>(children.size()) - 1; i >= 0; --i) | |
485 pairs.push(std::make_pair(children[i], depth + 1)); | |
486 } | |
487 } | |
488 return string_stream.str(); | |
489 } | |
490 | |
491 // static | |
492 AccObject* AccObject::CreateFromVariant(AccObject* object, | |
493 const VARIANT& variant) { | |
494 IAccessible* accessible = object->accessible_; | |
495 if (V_VT(&variant) == VT_I4) { | |
496 // According to MSDN, a server is allowed to return a full Accessibility | |
497 // object using the parent object and the child id. If get_accChild is | |
498 // called with the id, the server must return the actual IAccessible | |
499 // interface. Do that here to get an actual IAccessible interface if | |
500 // possible, since this class operates under the assumption that if the | |
501 // child id is not CHILDID_SELF, the object is a simple element. See the | |
502 // DCHECK in the constructor. | |
503 base::win::ScopedComPtr<IDispatch> dispatch; | |
504 HRESULT result = accessible->get_accChild(variant, | |
505 dispatch.Receive()); | |
506 if (result == S_FALSE || result == E_NOINTERFACE) { | |
507 // The object in question really is a simple element. | |
508 return new AccObject(accessible, V_I4(&variant)); | |
509 } else if (SUCCEEDED(result)) { | |
510 // The object in question was actually a full object. | |
511 return CreateFromDispatch(dispatch.get()); | |
512 } | |
513 VLOG(1) << "Failed to determine if child id refers to a full " | |
514 << "object. Error: " << result << std::endl | |
515 << "Parent object: " << base::WideToUTF8(object->GetDescription()) | |
516 << std::endl << "Child ID: " << V_I4(&variant); | |
517 return NULL; | |
518 } else if (V_VT(&variant) == VT_DISPATCH) { | |
519 return CreateFromDispatch(V_DISPATCH(&variant)); | |
520 } | |
521 LOG(WARNING) << "Unrecognizable child type"; | |
522 return NULL; | |
523 } | |
524 | |
525 bool AccObject::PostMouseClickAtCenter(int button_down, int button_up) { | |
526 std::wstring class_name; | |
527 if (!GetWindowClassName(&class_name)) { | |
528 LOG(ERROR) << "Could not get class name of window for accessibility " | |
529 << "object: " << GetDescription(); | |
530 return false; | |
531 } | |
532 gfx::Rect location; | |
533 if (class_name == L"#32768") { | |
534 // For some reason, it seems that menus expect screen coordinates. | |
535 if (!GetLocation(&location)) | |
536 return false; | |
537 } else { | |
538 if (!GetLocationInClient(&location)) | |
539 return false; | |
540 } | |
541 | |
542 gfx::Point center = location.CenterPoint(); | |
543 return PostMouseButtonMessages(button_down, button_up, | |
544 center.x(), center.y()); | |
545 } | |
546 | |
547 bool AccObject::PostMouseButtonMessages( | |
548 int button_down, int button_up, int x, int y) { | |
549 HWND container_window; | |
550 if (!GetWindow(&container_window)) | |
551 return false; | |
552 | |
553 LPARAM coordinates = MAKELPARAM(x, y); | |
554 ::PostMessage(container_window, button_down, 0, coordinates); | |
555 ::PostMessage(container_window, button_up, 0, coordinates); | |
556 return true; | |
557 } | |
558 | |
559 // AccObjectMatcher methods | |
560 AccObjectMatcher::AccObjectMatcher(const std::wstring& name, | |
561 const std::wstring& role_text, | |
562 const std::wstring& value) | |
563 : name_(name), role_text_(role_text), value_(value) { | |
564 } | |
565 | |
566 bool AccObjectMatcher::FindHelper(AccObject* object, | |
567 scoped_refptr<AccObject>* match) const { | |
568 if (DoesMatch(object)) { | |
569 *match = object; | |
570 } else { | |
571 // Try to match the children of |object|. | |
572 AccObject::RefCountedAccObjectVector children; | |
573 if (!object->GetChildren(&children)) { | |
574 LOG(ERROR) << "Could not get children of AccObject"; | |
575 return false; | |
576 } | |
577 for (size_t i = 0; i < children.size(); ++i) { | |
578 if (!FindHelper(children[i], match)) { | |
579 return false; | |
580 } | |
581 if (*match) | |
582 break; | |
583 } | |
584 } | |
585 return true; | |
586 } | |
587 | |
588 bool AccObjectMatcher::Find(AccObject* object, | |
589 scoped_refptr<AccObject>* match) const { | |
590 DCHECK(object); | |
591 DCHECK(match); | |
592 *match = NULL; | |
593 return FindHelper(object, match); | |
594 } | |
595 | |
596 bool AccObjectMatcher::FindInWindow(HWND hwnd, | |
597 scoped_refptr<AccObject>* match) const { | |
598 scoped_refptr<AccObject> object(AccObject::CreateFromWindow(hwnd)); | |
599 if (!object) { | |
600 VLOG(1) << "Failed to get accessible object from window"; | |
601 return false; | |
602 } | |
603 return Find(object.get(), match); | |
604 } | |
605 | |
606 bool AccObjectMatcher::DoesMatch(AccObject* object) const { | |
607 DCHECK(object); | |
608 bool does_match = true; | |
609 std::wstring name, role_text, value; | |
610 if (name_.length()) { | |
611 object->GetName(&name); | |
612 does_match = MatchPattern(StringToUpperASCII(name), | |
613 StringToUpperASCII(name_)); | |
614 } | |
615 if (does_match && role_text_.length()) { | |
616 object->GetRoleText(&role_text); | |
617 does_match = MatchPattern(role_text, role_text_); | |
618 } | |
619 if (does_match && value_.length()) { | |
620 object->GetValue(&value); | |
621 does_match = MatchPattern(value, value_); | |
622 } | |
623 return does_match; | |
624 } | |
625 | |
626 std::wstring AccObjectMatcher::GetDescription() const { | |
627 std::wostringstream ss; | |
628 ss << L"["; | |
629 if (name_.length()) | |
630 ss << L"Name: '" << name_ << L"', "; | |
631 if (role_text_.length()) | |
632 ss << L"Role: '" << role_text_ << L"', "; | |
633 if (value_.length()) | |
634 ss << L"Value: '" << value_ << L"'"; | |
635 ss << L"]"; | |
636 return ss.str(); | |
637 } | |
638 | |
639 // AccEventObserver methods | |
640 AccEventObserver::AccEventObserver() | |
641 : event_handler_(new EventHandler(this)), | |
642 is_watching_(false) { | |
643 event_receiver_.SetListenerForEvents(this, EVENT_SYSTEM_MENUPOPUPSTART, | |
644 EVENT_OBJECT_VALUECHANGE); | |
645 } | |
646 | |
647 AccEventObserver::~AccEventObserver() { | |
648 event_handler_->observer_ = NULL; | |
649 } | |
650 | |
651 void AccEventObserver::WatchForOneValueChange(const AccObjectMatcher& matcher) { | |
652 is_watching_ = true; | |
653 watching_for_matcher_ = matcher; | |
654 } | |
655 | |
656 void AccEventObserver::OnEventReceived(DWORD event, | |
657 HWND hwnd, | |
658 LONG object_id, | |
659 LONG child_id) { | |
660 // Process events in a separate task to stop reentrancy problems. | |
661 DCHECK(base::MessageLoop::current()); | |
662 base::MessageLoop::current()->PostTask( | |
663 FROM_HERE, base::Bind(&EventHandler::Handle, event_handler_.get(), event, | |
664 hwnd, object_id, child_id)); | |
665 } | |
666 | |
667 // AccEventObserver::EventHandler methods | |
668 AccEventObserver::EventHandler::EventHandler(AccEventObserver* observer) | |
669 : observer_(observer) { | |
670 } | |
671 | |
672 void AccEventObserver::EventHandler::Handle(DWORD event, | |
673 HWND hwnd, | |
674 LONG object_id, | |
675 LONG child_id) { | |
676 if (!observer_) | |
677 return; | |
678 | |
679 switch (event) { | |
680 case EVENT_SYSTEM_MENUPOPUPSTART: | |
681 observer_->OnMenuPopup(hwnd); | |
682 break; | |
683 case IA2_EVENT_DOCUMENT_LOAD_COMPLETE: | |
684 observer_->OnAccDocLoad(hwnd); | |
685 break; | |
686 case IA2_EVENT_TEXT_CARET_MOVED: { | |
687 scoped_refptr<AccObject> object( | |
688 AccObject::CreateFromEvent(hwnd, object_id, child_id)); | |
689 if (object) | |
690 observer_->OnTextCaretMoved(hwnd, object.get()); | |
691 break; | |
692 } | |
693 case EVENT_OBJECT_VALUECHANGE: | |
694 if (observer_->is_watching_) { | |
695 scoped_refptr<AccObject> object( | |
696 AccObject::CreateFromEvent(hwnd, object_id, child_id)); | |
697 if (object) { | |
698 if (observer_->watching_for_matcher_.DoesMatch(object.get())) { | |
699 // Stop watching before calling OnAccValueChange in case the | |
700 // client invokes our watch method during the call. | |
701 observer_->is_watching_ = false; | |
702 std::wstring new_value; | |
703 if (object->GetValue(&new_value)) { | |
704 observer_->OnAccValueChange(hwnd, object.get(), new_value); | |
705 } | |
706 } | |
707 } | |
708 } | |
709 break; | |
710 default: | |
711 break; | |
712 } | |
713 } | |
714 | |
715 // Other methods | |
716 bool FindAccObjectInWindow(HWND hwnd, const AccObjectMatcher& matcher, | |
717 scoped_refptr<AccObject>* object) { | |
718 DCHECK(object); | |
719 EXPECT_TRUE(matcher.FindInWindow(hwnd, object)); | |
720 EXPECT_TRUE(*object) << "Element not found for matcher: " | |
721 << matcher.GetDescription(); | |
722 if (!*object) | |
723 DumpAccessibilityTreeForWindow(hwnd); | |
724 return *object; | |
725 } | |
726 | |
727 void DumpAccessibilityTreeForWindow(HWND hwnd) { | |
728 scoped_refptr<AccObject> object(AccObject::CreateFromWindow(hwnd)); | |
729 if (object) | |
730 std::wcout << object->GetTree(); | |
731 else | |
732 std::cout << "Could not get IAccessible for window" << std::endl; | |
733 } | |
734 | |
735 bool IsDesktopUnlocked() { | |
736 HDESK desk = ::OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP); | |
737 if (desk) | |
738 ::CloseDesktop(desk); | |
739 return desk; | |
740 } | |
741 | |
742 base::FilePath GetIAccessible2ProxyStubPath() { | |
743 base::FilePath path; | |
744 PathService::Get(chrome::DIR_APP, &path); | |
745 return path.AppendASCII("IAccessible2Proxy.dll"); | |
746 } | |
747 | |
748 } // namespace chrome_frame_test | |
OLD | NEW |