| 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 |