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.
5 #include "chrome_frame/test/chrome_frame_ui_test_utils.h"
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"
28 namespace chrome_frame_test {
30 // Timeout for waiting on Chrome to create the accessibility tree for the DOM.
31 const int kChromeDOMAccessibilityTreeTimeoutMs = 10 * 1000;
33 // Timeout for waiting on a menu to popup.
34 const int kMenuPopupTimeoutMs = 10 * 1000;
37 AccObject::AccObject(IAccessible* accessible, int child_id)
38 : accessible_(accessible), child_id_(child_id) {
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.";
49 DCHECK(result == S_FALSE || result == E_NOINTERFACE);
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()));
59 return new AccObject(accessible);
64 AccObject* AccObject::CreateFromEvent(HWND hwnd, LONG object_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));
76 AccObject* AccObject::CreateFromDispatch(IDispatch* dispatch) {
78 base::win::ScopedComPtr<IAccessible> accessible;
79 accessible.QueryFrom(dispatch);
81 return new AccObject(accessible);
87 AccObject* AccObject::CreateFromPoint(int x, int y) {
88 base::win::ScopedComPtr<IAccessible> accessible;
89 base::win::ScopedVariant child_id;
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));
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";
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);
111 bool AccObject::LeftClick() {
112 return PostMouseClickAtCenter(WM_LBUTTONDOWN, WM_LBUTTONUP);
115 bool AccObject::RightClick() {
116 return PostMouseClickAtCenter(WM_RBUTTONDOWN, WM_RBUTTONUP);
119 bool AccObject::Focus() {
120 EXPECT_HRESULT_SUCCEEDED(
121 accessible_->accSelect(SELFLAG_TAKEFOCUS, child_id_));
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)
131 EXPECT_TRUE(did_focus) << "Could not focus AccObject: " << GetDescription();
135 bool AccObject::Select() {
136 // SELFLAG_TAKESELECTION needs to be combined with the focus in order to
138 int selection_flag = SELFLAG_TAKEFOCUS | SELFLAG_TAKESELECTION;
139 EXPECT_HRESULT_SUCCEEDED(accessible_->accSelect(selection_flag, child_id_));
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)
148 EXPECT_TRUE(did_select) << "Could not select AccObject: " << GetDescription();
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));
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;
163 EXPECT_TRUE(did_set_value) << "Could not set value for AccObject: "
165 return did_set_value;
168 bool AccObject::GetName(std::wstring* 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);
177 bool AccObject::GetRoleText(std::wstring* 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));
186 *role_text = role_text_array;
189 LOG(ERROR) << "GetRoleText failed for role: " << V_I4(&role_variant);
191 } else if (role_variant.type() == VT_BSTR) {
192 *role_text = V_BSTR(&role_variant);
195 LOG(ERROR) << "Role was unexpected variant type: "
196 << role_variant.type();
202 bool AccObject::GetValue(std::wstring* 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);
211 bool AccObject::GetState(int* 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);
224 bool AccObject::GetLocation(gfx::Rect* location) {
226 long left, top, width, height; // NOLINT
227 HRESULT result = accessible_->accLocation(&left, &top, &width, &height,
229 if (SUCCEEDED(result))
230 *location = gfx::Rect(left, top, width, height);
231 return SUCCEEDED(result);
234 bool AccObject::GetLocationInClient(gfx::Rect* client_location) {
235 DCHECK(client_location);
237 if (!GetLocation(&location))
239 HWND container_window = NULL;
240 if (!GetWindow(&container_window))
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: "
249 location.Offset(offset.x, offset.y);
250 *client_location = location;
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())))
260 return AccObject::CreateFromDispatch(dispatch.get());
263 bool AccObject::GetChildren(RefCountedAccObjectVector* client_objects) {
264 DCHECK(client_objects);
266 if (!GetChildCount(&child_count)) {
267 LOG(ERROR) << "Failed to get child count of AccObject";
270 if (child_count == 0)
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,
279 &found_child_count))) {
280 LOG(ERROR) << "Failed to get children of accessible object";
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]);
287 objects.push_back(obj);
288 ::VariantClear(&children[i]);
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);
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;
310 if (!already_found) {
311 objects.push_back(child_object);
312 remaining_child_count--;
314 scoped_refptr<AccObject> next_child_object;
315 child_object->GetFromNavigation(NAVDIR_NEXT, &next_child_object);
316 child_object = next_child_object;
319 client_objects->insert(client_objects->end(), objects.begin(), objects.end());
323 bool AccObject::GetChildCount(int* child_count) {
326 if (!IsSimpleElement()) {
327 long long_child_count; // NOLINT
328 if (FAILED(accessible_->get_accChildCount(&long_child_count)))
330 *child_count = static_cast<int>(long_child_count);
335 bool AccObject::GetFromNavigation(long navigation_type,
336 scoped_refptr<AccObject>* 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,
344 object_variant.Receive());
345 if (FAILED(result)) {
346 LOG(WARNING) << "Navigation from accessibility object failed";
349 if (result == S_FALSE || object_variant.type() == VT_EMPTY) {
350 // This indicates that there was no accessibility object found by the
354 AccObject* navigated_to_object;
355 if (!is_child_navigation && !IsSimpleElement()) {
356 scoped_refptr<AccObject> parent = GetParent();
358 LOG(WARNING) << "Could not get parent for accessibiliy navigation";
361 navigated_to_object = CreateFromVariant(parent, object_variant);
363 navigated_to_object = CreateFromVariant(this, object_variant);
365 if (!navigated_to_object)
367 *object = navigated_to_object;
371 bool AccObject::GetWindow(HWND* window) {
373 return SUCCEEDED(::WindowFromAccessibleObject(accessible_, window)) && window;
376 bool AccObject::GetWindowClassName(std::wstring* 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;
389 bool AccObject::GetSelectionRange(int* start_offset, int* end_offset) {
390 DCHECK(start_offset);
392 base::win::ScopedComPtr<IAccessibleText> accessible_text;
393 HRESULT hr = DoQueryService(IID_IAccessibleText,
395 accessible_text.Receive());
397 LOG(ERROR) << "Could not get IAccessibleText interface. Error: " << hr
398 << "\nIs IAccessible2Proxy.dll registered?";
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";
411 *start_offset = start;
416 bool AccObject::GetSelectedText(std::wstring* text) {
418 int start = 0, end = 0;
419 if (!GetSelectionRange(&start, &end))
421 base::win::ScopedComPtr<IAccessibleText> accessible_text;
422 HRESULT hr = DoQueryService(IID_IAccessibleText,
424 accessible_text.Receive());
426 LOG(ERROR) << "Could not get IAccessibleText interface. Error: " << hr
427 << "\nIs IAccessible2Proxy.dll registered?";
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";
435 text->assign(text_bstr, text_bstr.Length());
439 bool AccObject::IsSimpleElement() {
440 return V_I4(&child_id_) != CHILDID_SELF;
443 bool AccObject::Equals(AccObject* 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_);
452 std::wstring AccObject::GetDescription() {
453 std::wstring name = L"-", role_text = L"-", value = L"-";
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"'";
462 return base::StringPrintf(L"[%ls, %ls, %ls, 0x%x]", name.c_str(),
463 role_text.c_str(), value.c_str(), state);
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;
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;
478 for (int i = 0; i < depth; ++i)
479 string_stream << L" ";
480 string_stream << object->GetDescription() << std::endl;
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));
488 return string_stream.str();
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,
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());
513 VLOG(1) << "Failed to determine if child id refers to a full "
514 << "object. Error: " << result << std::endl
515 << "Parent object: " << WideToUTF8(object->GetDescription())
516 << std::endl << "Child ID: " << V_I4(&variant);
518 } else if (V_VT(&variant) == VT_DISPATCH) {
519 return CreateFromDispatch(V_DISPATCH(&variant));
521 LOG(WARNING) << "Unrecognizable child type";
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();
533 if (class_name == L"#32768") {
534 // For some reason, it seems that menus expect screen coordinates.
535 if (!GetLocation(&location))
538 if (!GetLocationInClient(&location))
542 gfx::Point center = location.CenterPoint();
543 return PostMouseButtonMessages(button_down, button_up,
544 center.x(), center.y());
547 bool AccObject::PostMouseButtonMessages(
548 int button_down, int button_up, int x, int y) {
549 HWND container_window;
550 if (!GetWindow(&container_window))
553 LPARAM coordinates = MAKELPARAM(x, y);
554 ::PostMessage(container_window, button_down, 0, coordinates);
555 ::PostMessage(container_window, button_up, 0, coordinates);
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) {
566 bool AccObjectMatcher::FindHelper(AccObject* object,
567 scoped_refptr<AccObject>* match) const {
568 if (DoesMatch(object)) {
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";
577 for (size_t i = 0; i < children.size(); ++i) {
578 if (!FindHelper(children[i], match)) {
588 bool AccObjectMatcher::Find(AccObject* object,
589 scoped_refptr<AccObject>* match) const {
593 return FindHelper(object, match);
596 bool AccObjectMatcher::FindInWindow(HWND hwnd,
597 scoped_refptr<AccObject>* match) const {
598 scoped_refptr<AccObject> object(AccObject::CreateFromWindow(hwnd));
600 VLOG(1) << "Failed to get accessible object from window";
603 return Find(object.get(), match);
606 bool AccObjectMatcher::DoesMatch(AccObject* object) const {
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_));
615 if (does_match && role_text_.length()) {
616 object->GetRoleText(&role_text);
617 does_match = MatchPattern(role_text, role_text_);
619 if (does_match && value_.length()) {
620 object->GetValue(&value);
621 does_match = MatchPattern(value, value_);
626 std::wstring AccObjectMatcher::GetDescription() const {
627 std::wostringstream ss;
630 ss << L"Name: '" << name_ << L"', ";
631 if (role_text_.length())
632 ss << L"Role: '" << role_text_ << L"', ";
634 ss << L"Value: '" << value_ << L"'";
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);
647 AccEventObserver::~AccEventObserver() {
648 event_handler_->observer_ = NULL;
651 void AccEventObserver::WatchForOneValueChange(const AccObjectMatcher& matcher) {
653 watching_for_matcher_ = matcher;
656 void AccEventObserver::OnEventReceived(DWORD event,
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));
667 // AccEventObserver::EventHandler methods
668 AccEventObserver::EventHandler::EventHandler(AccEventObserver* observer)
669 : observer_(observer) {
672 void AccEventObserver::EventHandler::Handle(DWORD event,
680 case EVENT_SYSTEM_MENUPOPUPSTART:
681 observer_->OnMenuPopup(hwnd);
683 case IA2_EVENT_DOCUMENT_LOAD_COMPLETE:
684 observer_->OnAccDocLoad(hwnd);
686 case IA2_EVENT_TEXT_CARET_MOVED: {
687 scoped_refptr<AccObject> object(
688 AccObject::CreateFromEvent(hwnd, object_id, child_id));
690 observer_->OnTextCaretMoved(hwnd, object.get());
693 case EVENT_OBJECT_VALUECHANGE:
694 if (observer_->is_watching_) {
695 scoped_refptr<AccObject> object(
696 AccObject::CreateFromEvent(hwnd, object_id, child_id));
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);
716 bool FindAccObjectInWindow(HWND hwnd, const AccObjectMatcher& matcher,
717 scoped_refptr<AccObject>* object) {
719 EXPECT_TRUE(matcher.FindInWindow(hwnd, object));
720 EXPECT_TRUE(*object) << "Element not found for matcher: "
721 << matcher.GetDescription();
723 DumpAccessibilityTreeForWindow(hwnd);
727 void DumpAccessibilityTreeForWindow(HWND hwnd) {
728 scoped_refptr<AccObject> object(AccObject::CreateFromWindow(hwnd));
730 std::wcout << object->GetTree();
732 std::cout << "Could not get IAccessible for window" << std::endl;
735 bool IsDesktopUnlocked() {
736 HDESK desk = ::OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP);
738 ::CloseDesktop(desk);
742 base::FilePath GetIAccessible2ProxyStubPath() {
744 PathService::Get(chrome::DIR_APP, &path);
745 return path.AppendASCII("IAccessible2Proxy.dll");
748 } // namespace chrome_frame_test