diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-17 17:04:24 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-17 17:04:24 +0000 |
commit | 39f7f17f35ec7f78a2b5b413d4b0b02fc216da61 (patch) | |
tree | 08ce71d716ee7cf4106e035f78cf2a877933ea6f /chrome_frame | |
parent | 4aad7cc13daa4baa068a8e138ba2b5c3596cbeb4 (diff) | |
download | chromium_src-39f7f17f35ec7f78a2b5b413d4b0b02fc216da61.zip chromium_src-39f7f17f35ec7f78a2b5b413d4b0b02fc216da61.tar.gz chromium_src-39f7f17f35ec7f78a2b5b413d4b0b02fc216da61.tar.bz2 |
[chrome_frame] Add utils for using MSAA for working with menus and for selecting/focusing elements. Change context menu tests to use this approach.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/3115002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56373 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
-rw-r--r-- | chrome_frame/chrome_frame.gyp | 2 | ||||
-rw-r--r-- | chrome_frame/test/chrome_frame_test_utils.cc | 280 | ||||
-rw-r--r-- | chrome_frame/test/chrome_frame_test_utils.h | 57 | ||||
-rw-r--r-- | chrome_frame/test/chrome_frame_ui_test_utils.cc | 425 | ||||
-rw-r--r-- | chrome_frame/test/chrome_frame_ui_test_utils.h | 173 | ||||
-rw-r--r-- | chrome_frame/test/ie_event_sink.cc | 5 | ||||
-rw-r--r-- | chrome_frame/test/ie_event_sink.h | 7 | ||||
-rw-r--r-- | chrome_frame/test/mock_ie_event_sink_actions.h | 96 | ||||
-rw-r--r-- | chrome_frame/test/mock_ie_event_sink_test.h | 13 | ||||
-rw-r--r-- | chrome_frame/test/navigation_test.cc | 17 | ||||
-rw-r--r-- | chrome_frame/test/run_all_unittests.cc | 12 | ||||
-rw-r--r-- | chrome_frame/test/ui_test.cc | 217 | ||||
-rw-r--r-- | chrome_frame/test/win_event_receiver.cc | 32 | ||||
-rw-r--r-- | chrome_frame/test/win_event_receiver.h | 25 |
14 files changed, 873 insertions, 488 deletions
diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index d357cd7..fe06c14 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -240,6 +240,8 @@ 'test/automation_client_mock.h', 'test/chrome_frame_test_utils.cc', 'test/chrome_frame_test_utils.h', + 'test/chrome_frame_ui_test_utils.cc', + 'test/chrome_frame_ui_test_utils.h', 'test/chrome_frame_automation_mock.cc', 'test/chrome_frame_automation_mock.h', 'test/http_server.cc', diff --git a/chrome_frame/test/chrome_frame_test_utils.cc b/chrome_frame/test/chrome_frame_test_utils.cc index 9c43cb2..b9ea02a 100644 --- a/chrome_frame/test/chrome_frame_test_utils.cc +++ b/chrome_frame/test/chrome_frame_test_utils.cc @@ -7,7 +7,6 @@ #include <atlbase.h> #include <atlwin.h> #include <iepmapi.h> -#include <oleacc.h> #include <oleauto.h> #include <sddl.h> @@ -34,7 +33,6 @@ #include "chrome_frame/utils.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/xulrunner-sdk/win/include/accessibility/AccessibleEventId.h" namespace chrome_frame_test { @@ -49,7 +47,6 @@ const char kChromeImageName[] = "chrome.exe"; const wchar_t kIEProfileName[] = L"iexplore"; const wchar_t kChromeLauncher[] = L"chrome_launcher.exe"; const int kChromeFrameLongNavigationTimeoutInSeconds = 10; -const int kChromeDOMAccessibilityTreeTimeoutMs = 10 * 1000; // Callback function for EnumThreadWindows. BOOL CALLBACK CloseWindowsThreadCallback(HWND hwnd, LPARAM param) { @@ -955,283 +952,6 @@ void DelaySendExtendedKeysEnter(TimedMsgLoop* loop, int delay, char c, simulate_input::SendCharA, VK_RETURN, simulate_input::NONE), next_delay); } -UIObjectMatcher::UIObjectMatcher(const std::wstring& name, - const std::wstring& role, - const std::wstring& value) - : name_(name), role_(role), value_(value) { -} - -bool UIObjectMatcher::Find(IAccessible* object, IAccessible** match) const { - DCHECK(object); - DCHECK(match); - ScopedVariant self_id(CHILDID_SELF); - - // Determine if |object| is a match. - bool does_match = true; - if (name_.length()) { - ScopedBstr name; - object->get_accName(self_id, name.Receive()); - std::wstring name_string; - name_string.assign(name, name.Length()); - does_match = MatchPatternWide(name_string, name_); - } - if (does_match && role_.length()) { - ScopedVariant role; - object->get_accRole(self_id, role.Receive()); - does_match = false; - if (role.type() == VT_I4) { - wchar_t role_text[50]; - if (GetRoleText(V_I4(&role), role_text, arraysize(role_text))) { - does_match = MatchPatternWide(role_text, role_); - } - } - } - if (does_match && value_.length()) { - ScopedBstr value; - object->get_accValue(self_id, value.Receive()); - std::wstring value_string; - value_string.assign(value, value.Length()); - does_match = MatchPatternWide(value_string, value_); - } - - if (does_match) { - *match = object; - object->AddRef(); - } else { - // Get the accessible children of |object|. - long child_count, actual_child_count; // NOLINT - if (FAILED(object->get_accChildCount(&child_count))) { - LOG(ERROR) << "Failed to get child count of accessible object"; - return false; - } - scoped_array<VARIANT> children(new VARIANT[child_count]); - if (FAILED(AccessibleChildren(object, 0L, child_count, children.get(), - &actual_child_count))) { - LOG(ERROR) << "Failed to get children of accessible object"; - return false; - } - - // Try each descendant. - for (int i = 0; i < actual_child_count; i++) { - if (children[i].vt == VT_DISPATCH) { - ScopedComPtr<IAccessible> accessible_child; - accessible_child.QueryFrom(V_DISPATCH(&children[i])); - if (accessible_child.get()) { - if (FAILED(Find(accessible_child.get(), match))) - return false; - if (*match) - break; - } - } - } - } - return true; -} - -bool UIObjectMatcher::FindInWindow(HWND hwnd, IAccessible** match) const { - ScopedComPtr<IAccessible> accessible; - HRESULT result = AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, - IID_IAccessible, reinterpret_cast<void**>(accessible.Receive())); - if (FAILED(result) || !accessible.get()) { - LOG(INFO) << "Failed to get accessible object from window"; - return false; - } - return Find(accessible.get(), match); -} - -std::wstring UIObjectMatcher::GetDescription() const { - std::wostringstream ss; - ss << L"("; - if (name_.length()) - ss << L"Name: \"" << name_ << L"\" "; - if (role_.length()) - ss << L"Role: \"" << role_ << L"\" "; - if (value_.length()) - ss << L"Value: \"" << value_ << L"\""; - ss << L")"; - return ss.str(); -} - -void DoDefaultUIAction(HWND hwnd, const UIObjectMatcher& matcher) { - ScopedComPtr<IAccessible> object; - EXPECT_TRUE(matcher.FindInWindow(hwnd, object.Receive())); - EXPECT_TRUE(object.get()); - if (object.get()) { - ScopedVariant self_id(CHILDID_SELF); - EXPECT_HRESULT_SUCCEEDED(object->accDoDefaultAction(self_id)); - } else { - EXPECT_TRUE(object.get()) << "Element not found for matcher: " - << matcher.GetDescription(); - DumpAccessibilityTreeForWindow(hwnd); - } -} - -// Used to ensure a message loop is quit at most one time. -class OneTimeLoopQuitter : public base::RefCounted<OneTimeLoopQuitter> { - public: - OneTimeLoopQuitter() : did_quit_(false), should_quit_(true) {} - - void Quit() { - if (should_quit_ && !did_quit_) - MessageLoop::current()->Quit(); - did_quit_ = true; - } - - void Cancel() { - should_quit_ = false; - } - - bool did_quit() const { return did_quit_; } - - private: - bool did_quit_; - bool should_quit_; - - DISALLOW_COPY_AND_ASSIGN(OneTimeLoopQuitter); -}; - -// Used to wait for an accessibility document load event. Starts listening -// on creation. -class AccessibilityDocumentLoadObserver : public WinEventListener { - public: - explicit AccessibilityDocumentLoadObserver(HWND hwnd) - : did_receive_load_(false), - hwnd_to_watch_(hwnd) { - event_receiver_.SetListenerForEvent(this, IA2_EVENT_DOCUMENT_LOAD_COMPLETE); - } - - // Waits for the document load event to be received or the timeout to occur. - // This assumes there is a MessageLoop for the current thread. - bool WaitForDocumentLoad(int timeoutInMs) { - DCHECK(MessageLoop::current() != NULL); - if (!did_receive_load_) { - quitter = new OneTimeLoopQuitter(); - MessageLoop::current()->PostDelayedTask( - FROM_HERE, - NewRunnableMethod(quitter.get(), &OneTimeLoopQuitter::Quit), - timeoutInMs); - DLOG(INFO) << "Waiting for document load event"; - MessageLoop::current()->Run(); - - DLOG_IF(WARNING, !quitter->did_quit()) - << "Message loop was quit externally."; - quitter->Quit(); - quitter.release(); - } - return did_receive_load_; - } - - private: - virtual void OnEventReceived(DWORD event, HWND hwnd) { - if (hwnd == hwnd_to_watch_) { - DLOG(INFO) << "Received document load event"; - did_receive_load_ = true; - if (quitter.get()) - quitter->Quit(); - } - } - - bool did_receive_load_; - HWND hwnd_to_watch_; - scoped_refptr<OneTimeLoopQuitter> quitter; - WinEventReceiver event_receiver_; - - DISALLOW_COPY_AND_ASSIGN(AccessibilityDocumentLoadObserver); -}; - -bool WaitForChromeDOMAccessibilityTree(HWND hwnd) { - bool tree_is_ready = true; - // Create this here so it can watch for events that are processed when - // fetching IAccessible. - AccessibilityDocumentLoadObserver load_observer(hwnd); - - // Get the first object in the current accessibility tree. The state of this - // object will be busy if Chrome is still processing the tree. - ScopedComPtr<IAccessible> first_object; - UIObjectMatcher first_object_matcher; - if (first_object_matcher.FindInWindow(hwnd, first_object.Receive()) && - first_object.get()) { - ScopedVariant self_id(CHILDID_SELF); - ScopedVariant state; - if (SUCCEEDED(first_object->get_accState(self_id, state.Receive()))) { - if (state.type() == VT_I4 && V_I4(&state) & STATE_SYSTEM_BUSY) { - tree_is_ready = load_observer.WaitForDocumentLoad( - kChromeDOMAccessibilityTreeTimeoutMs); - } - } - } - DLOG_IF(ERROR, !tree_is_ready) - << "Gave up waiting for accessibility document load event"; - return tree_is_ready; -} - -namespace { - -void DumpAccessibilityTreeHelper(IAccessible* object, int depth) { - ScopedVariant self_id(CHILDID_SELF); - ScopedBstr name; - object->get_accName(self_id, name.Receive()); - std::wstring name_string; - name_string.assign(name, name.Length()); - for (int i = 0; i < depth; ++i) { - std::wcout << L"---"; - } - std::wcout << L"\"" << name_string << L"\", "; - - ScopedVariant role; - object->get_accRole(self_id, role.Receive()); - if (role.type() == VT_I4) { - wchar_t role_text[50]; - if (GetRoleText(V_I4(&role), role_text, arraysize(role_text))) - std::wcout << L"\"" << role_text << "\""; - } - std::wcout << ", "; - - ScopedBstr value; - object->get_accValue(self_id, value.Receive()); - std::wstring value_string; - value_string.assign(value, value.Length()); - std::wcout << L"\"" << value_string << L"\"" << std::endl; - - // Get the accessible children of |root|. - HRESULT result = S_OK; - long child_count, actual_child_count; // NOLINT - if (FAILED(object->get_accChildCount(&child_count))) - return; - scoped_array<VARIANT> children(new VARIANT[child_count]); - if (FAILED(AccessibleChildren(object, 0L, child_count, children.get(), - &actual_child_count))) - return; - - for (int i = 0; i < actual_child_count; i++) { - if (children[i].vt == VT_DISPATCH) { - ScopedComPtr<IAccessible> accessible_child; - accessible_child.QueryFrom(V_DISPATCH(&children[i])); - if (accessible_child.get()) { - DumpAccessibilityTreeHelper(accessible_child.get(), depth + 1); - } - } - } -} - -} // namespace - -void DumpAccessibilityTree(IAccessible* object) { - std::cout << "Accessibility object tree:" << std::endl; - std::cout << "NAME, ROLE, VALUE" << std::endl; - DumpAccessibilityTreeHelper(object, 0); -} - -void DumpAccessibilityTreeForWindow(HWND hwnd) { - ScopedComPtr<IAccessible> accessible; - AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, - IID_IAccessible, reinterpret_cast<void**>(accessible.Receive())); - if (accessible.get()) - DumpAccessibilityTree(accessible); - else - std::cout << "Could not get IAccessible for window" << std::endl; -} - CloseIeAtEndOfScope::~CloseIeAtEndOfScope() { int closed = CloseAllIEWindows(); DLOG_IF(ERROR, closed != 0) << "Closed " << closed << " windows forcefully"; diff --git a/chrome_frame/test/chrome_frame_test_utils.h b/chrome_frame/test/chrome_frame_test_utils.h index f05a487..3e63009 100644 --- a/chrome_frame/test/chrome_frame_test_utils.h +++ b/chrome_frame/test/chrome_frame_test_utils.h @@ -368,63 +368,6 @@ class CloseIeAtEndOfScope { ~CloseIeAtEndOfScope(); }; -// Finds a UI/accessibility object with properties that match the specified -// matching patterns. These patterns can include the standard * and ? wildcards. -class UIObjectMatcher { - public: - // Create a matcher from the given string. |matcher| should include matching - // patterns for each property separated by colons. Matching patterns must - // be specified from left to right in the following order: - // 1) Name - // 2) Role: A string representation of a Windows object role, which can be - // found by using the win32 GetRoleText function. E.g., - // ROLE_SYSTEM_ALERT should be represented as 'alert', and - // ROLE_SYSTEM_MENUPOPUP should be represented as 'popup menu'. - // 3) Value - // Matching patterns can be blank, essentially equal to *. - // Literal *, ?, and : characters can be escaped with a backslash. - UIObjectMatcher(const std::wstring& name = L"", - const std::wstring& role = L"", - const std::wstring& value = L""); - - // Finds the first object which satisfies this matcher and sets as |match|. - // This searches the accessibility tree (including |object| itself) of - // |object| in a pre-order fasion. If no object is matched, |match| will be - // NULL. Returns true if no error occured while trying to find a match. It is - // possible to use this method to test for an object's non-existence. - bool Find(IAccessible* object, IAccessible** match) const; - - // Same as above except that it searches within the accessibility tree of the - // given window, which must support the IAccessible interface. - bool FindInWindow(HWND hwnd, IAccessible** match) const; - - // Return a description of the matcher, for debugging/logging purposes. - std::wstring GetDescription() const; - - private: - std::wstring name_; - std::wstring role_; - std::wstring value_; -}; - -// Perform the default UI action for the object which satisfies |matcher|. -// Will cause test failure in case of error or no match is found in the -// accessibility tree of the specified window. -void DoDefaultUIAction(HWND hwnd, const UIObjectMatcher& matcher); - -// Wait for Chrome to report that the DOM accessibility tree is ready. Chrome -// prepares the tree asynchronously and will return a fake tree if it is not -// ready. Returns whether the tree is ready. Returns false if the message loop -// was quit while waiting for the tree. -bool WaitForChromeDOMAccessibilityTree(HWND hwnd); - -// Writes the accessibility tree for |object| to standard out. Used for -// debugging/logging. |object| must be non-null. -void DumpAccessibilityTree(IAccessible* object); - -// Same as above except that it writes the tree for the given window. -void DumpAccessibilityTreeForWindow(HWND hwnd); - // Starts the Chrome crash service which enables us to gather crash dumps // during test runs. base::ProcessHandle StartCrashService(); diff --git a/chrome_frame/test/chrome_frame_ui_test_utils.cc b/chrome_frame/test/chrome_frame_ui_test_utils.cc new file mode 100644 index 0000000..63629f5 --- /dev/null +++ b/chrome_frame/test/chrome_frame_ui_test_utils.cc @@ -0,0 +1,425 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/test/chrome_frame_ui_test_utils.h" + +#include <windows.h> + +#include <sstream> +#include <stack> + +#include "base/message_loop.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "gfx/rect.h" +#include "chrome_frame/test/win_event_receiver.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chrome_frame_test { + +// Timeout for waiting on Chrome to create the accessibility tree for the DOM. +const int kChromeDOMAccessibilityTreeTimeoutMs = 10 * 1000; + +// Timeout for waiting on a menu to popup. +const int kMenuPopupTimeoutMs = 10 * 1000; + +// AccObject methods +AccObject::AccObject(IAccessible* accessible, int child_id) + : accessible_(accessible), child_id_(child_id) { + DCHECK(accessible); + if (child_id != CHILDID_SELF) { + ScopedComPtr<IDispatch> dispatch; + // This class does not support referring to a full MSAA object using the + // parent object and the child id. + HRESULT result = accessible_->get_accChild(child_id_, dispatch.Receive()); + if (result != S_FALSE && result != E_NOINTERFACE) { + LOG(ERROR) << "AccObject created which refers to full MSAA object using " + "parent object and child id. This should NOT be done."; + } + DCHECK(result == S_FALSE || result == E_NOINTERFACE); + } +} + +// static +AccObject* AccObject::CreateFromWindow(HWND hwnd) { + ScopedComPtr<IAccessible> accessible; + AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, + IID_IAccessible, reinterpret_cast<void**>(accessible.Receive())); + if (accessible) + return new AccObject(accessible); + return NULL; +} + +// static +AccObject* AccObject::CreateFromDispatch(IDispatch* dispatch) { + if (dispatch) { + ScopedComPtr<IAccessible> accessible; + accessible.QueryFrom(dispatch); + if (accessible) + return new AccObject(accessible); + } + return NULL; +} + +// static +AccObject* AccObject::CreateFromPoint(int x, int y) { + ScopedComPtr<IAccessible> accessible; + ScopedVariant child_id; + POINT point = {x, y}; + AccessibleObjectFromPoint(point, accessible.Receive(), child_id.Receive()); + if (accessible && child_id.type() == VT_I4) + return new AccObject(accessible, V_I4(&child_id)); + return NULL; +} + +bool AccObject::DoDefaultAction() { + HRESULT result = accessible_->accDoDefaultAction(child_id_); + EXPECT_HRESULT_SUCCEEDED(result) + << "Could not do default action for AccObject: " << GetDescription(); + return SUCCEEDED(result); +} + +bool AccObject::Focus() { + EXPECT_HRESULT_SUCCEEDED( + accessible_->accSelect(SELFLAG_TAKEFOCUS, child_id_)); + + // Double check that the object actually received focus. In some cases + // the parent object must have the focus first. + bool did_focus = false; + ScopedVariant focused; + if (SUCCEEDED(accessible_->get_accFocus(focused.Receive()))) { + if (focused.type() != VT_EMPTY) + did_focus = true; + } + EXPECT_TRUE(did_focus) << "Could not focus AccObject: " << GetDescription(); + return did_focus; +} + +bool AccObject::Select() { + // SELFLAG_TAKESELECTION needs to be combined with the focus in order to + // take effect. + int selection_flag = SELFLAG_TAKEFOCUS | SELFLAG_TAKESELECTION; + EXPECT_HRESULT_SUCCEEDED(accessible_->accSelect(selection_flag, child_id_)); + + // Double check that the object actually received selection. + bool did_select = false; + ScopedVariant selected; + if (SUCCEEDED(accessible_->get_accSelection(selected.Receive()))) { + if (selected.type() != VT_EMPTY) + did_select = true; + } + EXPECT_TRUE(did_select) << "Could not select AccObject: " << GetDescription(); + return did_select; +} + +bool AccObject::GetName(std::wstring* name) { + DCHECK(name); + ScopedBstr name_bstr; + HRESULT result = accessible_->get_accName(child_id_, name_bstr.Receive()); + if (SUCCEEDED(result)) + name->assign(name_bstr, name_bstr.Length()); + return SUCCEEDED(result); +} + +bool AccObject::GetRoleText(std::wstring* role_text) { + DCHECK(role_text); + ScopedVariant role_variant; + if (SUCCEEDED(accessible_->get_accRole(child_id_, role_variant.Receive()))) { + if (role_variant.type() == VT_I4) { + wchar_t role_text_array[50]; + UINT characters = ::GetRoleText(V_I4(&role_variant), role_text_array, + arraysize(role_text_array)); + if (characters) { + *role_text = role_text_array; + return true; + } else { + DLOG(ERROR) << "GetRoleText failed for role: " + << V_I4(&role_variant); + } + } else if (role_variant.type() == VT_BSTR) { + *role_text = V_BSTR(&role_variant); + return true; + } else { + DLOG(ERROR) << "Role was unexpected variant type: " + << role_variant.type(); + } + } + return false; +} + +bool AccObject::GetValue(std::wstring* value) { + DCHECK(value); + ScopedBstr value_bstr; + HRESULT result = accessible_->get_accValue(child_id_, value_bstr.Receive()); + if (SUCCEEDED(result)) + value->assign(value_bstr, value_bstr.Length()); + return SUCCEEDED(result); +} + +bool AccObject::GetState(int* state) { + DCHECK(state); + ScopedVariant state_variant; + if (SUCCEEDED(accessible_->get_accState(child_id_, + state_variant.Receive()))) { + if (state_variant.type() == VT_I4) { + *state = V_I4(&state_variant); + return true; + } + } + return false; +} + +bool AccObject::GetLocation(gfx::Rect* location) { + DCHECK(location); + long left, top, width, height; // NOLINT + HRESULT result = accessible_->accLocation(&left, &top, &width, &height, + child_id_); + if (SUCCEEDED(result)) + *location = gfx::Rect(left, top, width, height); + return SUCCEEDED(result); +} + +AccObject* AccObject::GetParent() { + if (IsSimpleElement()) + return new AccObject(accessible_); + ScopedComPtr<IDispatch> dispatch; + if (FAILED(accessible_->get_accParent(dispatch.Receive()))) + return NULL; + return AccObject::CreateFromDispatch(dispatch.get()); +} + +bool AccObject::GetChildren(RefCountedAccObjectVector* client_objects) { + DCHECK(client_objects); + int child_count; + if (!GetChildCount(&child_count)) { + LOG(ERROR) << "Failed to get child count of AccObject"; + return false; + } + if (child_count == 0) + return true; + scoped_array<VARIANT> unscoped_children(new VARIANT[child_count]); + long actual_child_count; // NOLINT + if (FAILED(AccessibleChildren(accessible_, 0L, child_count, + unscoped_children.get(), + &actual_child_count))) { + LOG(ERROR) << "Failed to get children of accessible object"; + return false; + } + if (actual_child_count == 0) + return true; + // Convert the retrieved children array into an array of scoped children. + scoped_array<ScopedVariant> children(new ScopedVariant[child_count]); + for (int i = 0; i < child_count; ++i) { + children[i].Reset(unscoped_children[i]); + } + + RefCountedAccObjectVector objects; + for (int i = 0; i < actual_child_count; i++) { + ScopedComPtr<IDispatch> dispatch; + if (children[i].type() == VT_I4) { + // According to MSDN, a server is allowed to return a full Accessibility + // object using the parent object and the child id. If get_accChild is + // called with the id, the server must return the actual IAccessible + // interface. Do that here to get an actual IAccessible interface if + // possible, since this class operates under the assumption that if the + // child id is not CHILDID_SELF, the object is a simple element. See the + // DCHECK in the constructor. + HRESULT result = accessible_->get_accChild(children[i], + dispatch.Receive()); + if (result == S_FALSE || result == E_NOINTERFACE) { + // The object in question really is a simple element. Add it. + objects.push_back(new AccObject(accessible_, V_I4(&children[i]))); + continue; + } else if (FAILED(result)) { + LOG(ERROR) << "Failed to determine if child id refers to a full " + << "object. Error: " << result; + return false; + } + // The object in question was actually a full object. It is saved in the + // |dispatch| arg and will be added down below. + } else if (children[i].type() == VT_DISPATCH) { + dispatch.Attach(V_DISPATCH(&children[i].Release())); + } else { + DLOG(WARNING) << "Unrecognizable child type, omitting from children"; + continue; + } + + scoped_refptr<AccObject> child = CreateFromDispatch(dispatch.get()); + if (child) { + objects.push_back(child); + } else { + LOG(ERROR) << "Failed to create AccObject from IDispatch"; + return false; + } + } + + client_objects->insert(client_objects->end(), objects.begin(), objects.end()); + return true; +} + +bool AccObject::GetChildCount(int* child_count) { + DCHECK(child_count); + *child_count = 0; + if (!IsSimpleElement()) { + long long_child_count; // NOLINT + if (FAILED(accessible_->get_accChildCount(&long_child_count))) + return false; + *child_count = static_cast<int>(long_child_count); + } + return true; +} + +bool AccObject::IsSimpleElement() { + return V_I4(&child_id_) != CHILDID_SELF; +} + +bool AccObject::Equals(AccObject* other) { + if (other) { + DCHECK(child_id_.type() == VT_I4 && other->child_id_.type() == VT_I4); + return accessible_.get() == other->accessible_.get() && + V_I4(&child_id_) == V_I4(&other->child_id_); + } + return false; +} + +std::wstring AccObject::GetDescription() { + std::wstring name = L"-", role_text = L"-", value = L"-"; + if (GetName(&name)) + name = L"'" + name + L"'"; + if (GetRoleText(&role_text)) + role_text = L"'" + role_text + L"'"; + if (GetValue(&value)) + value = L"'" + value + L"'"; + int state = 0; + GetState(&state); + return StringPrintf(L"[%ls, %ls, %ls, 0x%x]", name.c_str(), role_text.c_str(), + value.c_str(), state); +} + +std::wstring AccObject::GetTree() { + std::wostringstream string_stream; + string_stream << L"Accessibility object tree:" << std::endl; + string_stream << L"[name, role_text, value, state]" << std::endl; + + std::stack<std::pair<scoped_refptr<AccObject>, int> > pairs; + pairs.push(std::make_pair(this, 0)); + while (!pairs.empty()) { + scoped_refptr<AccObject> object = pairs.top().first; + int depth = pairs.top().second; + pairs.pop(); + + for (int i = 0; i < depth; ++i) + string_stream << L" "; + string_stream << object->GetDescription() << std::endl; + + RefCountedAccObjectVector children; + if (object->GetChildren(&children)) { + for (int i = static_cast<int>(children.size()) - 1; i >= 0; --i) + pairs.push(std::make_pair(children[i], depth + 1)); + } + } + return string_stream.str(); +} + +// AccObjectMatcher methods +AccObjectMatcher::AccObjectMatcher(const std::wstring& name, + const std::wstring& role_text, + const std::wstring& value) + : name_(name), role_text_(role_text), value_(value) { +} + +bool AccObjectMatcher::FindHelper(AccObject* object, + scoped_refptr<AccObject>* match) const { + // Determine if |object| is a match. + bool does_match = true; + std::wstring name, role_text, value; + if (name_.length()) + does_match = object->GetName(&name) && MatchPatternWide(name, name_); + if (does_match && role_text_.length()) { + does_match = object->GetRoleText(&role_text) && + MatchPatternWide(role_text, role_text_); + } + if (does_match && value_.length()) + does_match = object->GetValue(&value) && MatchPatternWide(value, value_); + + if (does_match) { + *match = object; + } else { + // Try to match the children of |object|. + AccObject::RefCountedAccObjectVector children; + if (!object->GetChildren(&children)) { + LOG(ERROR) << "Could not get children of AccObject"; + return false; + } + for (size_t i = 0; i < children.size(); ++i) { + if (!FindHelper(children[i], match)) { + return false; + } + if (*match) + break; + } + } + return true; +} + +bool AccObjectMatcher::Find(AccObject* object, + scoped_refptr<AccObject>* match) const { + DCHECK(object); + DCHECK(match); + *match = NULL; + return FindHelper(object, match); +} + +bool AccObjectMatcher::FindInWindow(HWND hwnd, + scoped_refptr<AccObject>* match) const { + scoped_refptr<AccObject> object(AccObject::CreateFromWindow(hwnd)); + if (!object) { + LOG(INFO) << "Failed to get accessible object from window"; + return false; + } + return Find(object.get(), match); +} + +std::wstring AccObjectMatcher::GetDescription() const { + std::wostringstream ss; + ss << L"["; + if (name_.length()) + ss << L"Name: '" << name_ << L"', "; + if (role_text_.length()) + ss << L"Role: '" << role_text_ << L"', "; + if (value_.length()) + ss << L"Value: '" << value_ << L"'"; + ss << L"]"; + return ss.str(); +} + +// Other methods +bool FindAccObjectInWindow(HWND hwnd, const AccObjectMatcher& matcher, + scoped_refptr<AccObject>* object) { + DCHECK(object); + EXPECT_TRUE(matcher.FindInWindow(hwnd, object)); + EXPECT_TRUE(object) << "Element not found for matcher: " + << matcher.GetDescription(); + if (!object) + DumpAccessibilityTreeForWindow(hwnd); + return *object; +} + +void DumpAccessibilityTreeForWindow(HWND hwnd) { + scoped_refptr<AccObject> object(AccObject::CreateFromWindow(hwnd)); + if (object) + std::wcout << object->GetTree(); + else + std::cout << "Could not get IAccessible for window" << std::endl; +} + +bool IsDesktopUnlocked() { + HDESK desk = ::OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP); + if (desk) + ::CloseDesktop(desk); + return desk; +} + +} // namespace chrome_frame_test diff --git a/chrome_frame/test/chrome_frame_ui_test_utils.h b/chrome_frame/test/chrome_frame_ui_test_utils.h new file mode 100644 index 0000000..4d254d5 --- /dev/null +++ b/chrome_frame/test/chrome_frame_ui_test_utils.h @@ -0,0 +1,173 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_TEST_CHROME_FRAME_UI_TEST_UTILS_H_ +#define CHROME_FRAME_TEST_CHROME_FRAME_UI_TEST_UTILS_H_ + +#include <oleacc.h> + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" + +namespace gfx { +class Rect; +} + +namespace chrome_frame_test { + +// Wrapper for MSAA objects. In MSAA, there are two types of objects. The first, +// called an object or full object, has its own IAccessible interface. The +// second, called a simple element, does not have its own IAccessible interface +// and cannot have children. Simple elements must be referenced by combination +// of the parent object and the element's id in MSAA. This class handles this +// distinction transparently to the client. +class AccObject : public base::RefCounted<AccObject> { + public: + typedef std::vector<scoped_refptr<AccObject> > RefCountedAccObjectVector; + + // Creates an AccObject with an IAccessible and child id. |accessible| must + // not be NULL. |child_id| must always be CHILDID_SELF unless this AccObject + // is a simple element. + AccObject(IAccessible* accessible, int child_id = CHILDID_SELF); + + // Creates an AccObject corresponding to the given window. May return NULL + // if there is not accessible object for the window. The client owns the + // created AccObject. + static AccObject* CreateFromWindow(HWND hwnd); + + // Creates an AccObject from querying the given IDispatch. May return NULL + // if the object does not implement IAccessible. The client owns the created + // AccObject. + // Note: This does not work in Chrome. + static AccObject* CreateFromDispatch(IDispatch* dispatch); + + // Creates an AccObject corresponding to the accessible object at the screen + // coordinates given. Returns NULL on failure. The client owns the created + // AccObject. + static AccObject* CreateFromPoint(int x, int y); + + // Performs the default action on this object. Returns whether the action + // performed successfully. Will cause test failure if unsuccessful. + bool DoDefaultAction(); + + // Focuses this object. Returns whether the object receives focus. Will cause + // test failure if the object is not focused. + bool Focus(); + + // Selects this object. Returns whether the object is now selected. Will cause + // test failure if the object is not selected. + bool Select(); + + // Gets the name of the object and returns true on success. + bool GetName(std::wstring* name); + + // Gets the role text of the object and returns true on success. + bool GetRoleText(std::wstring* role_text); + + // Gets the value of the object and returns true on success. + bool GetValue(std::wstring* value); + + // Gets the state of the object and returns true on success. + bool GetState(int* state); + + // Gets the location of the object in screen coordinates and returns true + // on success. + bool GetLocation(gfx::Rect* location); + + // Gets the parent of the object. May return NULL. + AccObject* GetParent(); + + // Gets the children of this object and returns true on success. |objects| + // will not be modified unless if the operation is successful. + bool GetChildren(RefCountedAccObjectVector* objects); + + // Gets the number of children of this object and returns true on success. + bool GetChildCount(int* child_count); + + // Returns whether this object is a simple element. + bool IsSimpleElement(); + + // Returns whether the two AccObjects point to the same accessibility object. + // |other| can safely be NULL. + bool Equals(AccObject* other); + + // Returns a description of this object. + std::wstring GetDescription(); + + // Returns a description of this object and it's accessibility tree. This + // description will be ended by a newline. + std::wstring GetTree(); + + private: + friend class base::RefCounted<AccObject>; + ~AccObject() {} + + ScopedComPtr<IAccessible> accessible_; + ScopedVariant child_id_; + + DISALLOW_COPY_AND_ASSIGN(AccObject); +}; + +// Finds an accessibility object with properties that match the specified +// matching patterns. These patterns can include the standard * and ? wildcards. +class AccObjectMatcher { + public: + // Create a matcher from the given string. |matcher| should include matching + // patterns for each property separated by colons. Matching patterns must + // be specified from left to right in the following order: + // 1) Name + // 2) Role Text: A string representation of a Windows object role, which can + // be found by using the win32 GetRoleText function. E.g., + // ROLE_SYSTEM_ALERT should be represented as 'alert', and + // ROLE_SYSTEM_MENUPOPUP should be represented as 'popup menu'. + // 3) Value + // Matching patterns can be blank, essentially equal to *. + // Literal *, ?, and : characters can be escaped with a backslash. + AccObjectMatcher(const std::wstring& name = L"", + const std::wstring& role_text = L"", + const std::wstring& value = L""); + + // Finds the first object which satisfies this matcher and sets as |match|. + // This searches the accessibility tree (including |object| itself) of + // |object| in a pre-order fasion. If no object is matched, |match| will be + // NULL. Returns true if no error occured while trying to find a match. It is + // possible to use this method to test for an object's non-existence. + bool Find(AccObject* object, scoped_refptr<AccObject>* match) const; + + // Same as above except that it searches within the accessibility tree of the + // given window, which must support the IAccessible interface. + bool FindInWindow(HWND hwnd, scoped_refptr<AccObject>* match) const; + + // Return a description of the matcher, for debugging/logging purposes. + std::wstring GetDescription() const; + + private: + bool FindHelper(AccObject* object, scoped_refptr<AccObject>* match) const; + + std::wstring name_; + std::wstring role_text_; + std::wstring value_; +}; + +// Finds an AccObject from the given window that satisfied |matcher|. +// Will cause test failure in case of error or if no match is found in the +// accessibility tree of the specified window. Returns whether the object was +// found. +bool FindAccObjectInWindow(HWND hwnd, const AccObjectMatcher& matcher, + scoped_refptr<AccObject>* object); + +// Writes the accessibility tree for the given window to standard out. Used for +// debugging/logging. +void DumpAccessibilityTreeForWindow(HWND hwnd); + +// Returns whether the desktop is unlocked. +bool IsDesktopUnlocked(); + +} // namespace chrome_frame_test + +#endif // CHROME_FRAME_TEST_CHROME_FRAME_UI_TEST_UTILS_H_ diff --git a/chrome_frame/test/ie_event_sink.cc b/chrome_frame/test/ie_event_sink.cc index 20eea5f..853ee59 100644 --- a/chrome_frame/test/ie_event_sink.cc +++ b/chrome_frame/test/ie_event_sink.cc @@ -324,11 +324,6 @@ void IEEventSink::Refresh() { web_browser2_->Refresh2(refresh_level.AsInput()); } -void IEEventSink::WaitForDOMAccessibilityTree() { - if (IsCFRendering()) - WaitForChromeDOMAccessibilityTree(GetRendererWindow()); -} - // private methods void IEEventSink::ConnectToChromeFrame() { DCHECK(web_browser2_); diff --git a/chrome_frame/test/ie_event_sink.h b/chrome_frame/test/ie_event_sink.h index ac1bf93..feddcbf 100644 --- a/chrome_frame/test/ie_event_sink.h +++ b/chrome_frame/test/ie_event_sink.h @@ -111,6 +111,9 @@ class IEEventSink // Get the HWND for the browser's renderer window. HWND GetRendererWindow(); + // Returns whether the browser has a renderer window. + bool HasRendererWindow(); + // Launch IE, use the given listener, and navigate to the given url. HRESULT LaunchIEAndNavigate(const std::wstring& navigate_url, IEEventListener* listener); @@ -127,10 +130,6 @@ class IEEventSink // Expect the address bar to have |url|. void ExpectAddressBarUrl(const std::wstring& url); - // Waits for the Accessibility tree for the DOM to be ready. This is only - // necessary for Chrome, so this method is a no-op for IE. - void WaitForDOMAccessibilityTree(); - // These methods are just simple wrappers of the IWebBrowser2 methods. // They are needed because you cannot post tasks to IWebBrowser2. void GoBack() { diff --git a/chrome_frame/test/mock_ie_event_sink_actions.h b/chrome_frame/test/mock_ie_event_sink_actions.h index 49d6d63..d0e46a5 100644 --- a/chrome_frame/test/mock_ie_event_sink_actions.h +++ b/chrome_frame/test/mock_ie_event_sink_actions.h @@ -5,10 +5,16 @@ #ifndef CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_ACTIONS_H_ #define CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_ACTIONS_H_ +#include <windows.h> + #include "base/scoped_bstr_win.h" #include "chrome/common/chrome_switches.h" #include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/chrome_frame_ui_test_utils.h" +#include "chrome_frame/test/mock_ie_event_sink_test.h" #include "chrome_frame/test/simulate_input.h" +#include "gfx/point.h" +#include "gfx/rect.h" #include "testing/gmock/include/gmock/gmock.h" namespace chrome_frame_test { @@ -103,8 +109,24 @@ ACTION_P5(SendExtendedKeysEnter, loop, delay, c, repeat, mod) { chrome_frame_test::DelaySendExtendedKeysEnter(loop, delay, c, repeat, mod); } +namespace { + +void DoCloseWindowNow(HWND hwnd) { + ::PostMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0); +} + +} // namespace + ACTION(DoCloseWindow) { - ::PostMessage(arg0, WM_SYSCOMMAND, SC_CLOSE, 0); + DoCloseWindowNow(arg0); +} + +ACTION_P(DelayDoCloseWindow, delay) { + DCHECK(MessageLoop::current()); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + NewRunnableFunction(DoCloseWindowNow, arg0), + delay); } ACTION_P3(DelayGoBack, mock, loop, delay) { @@ -125,6 +147,11 @@ ACTION_P(VerifyAddressBarUrl, mock) { mock->event_sink()->ExpectAddressBarUrl(std::wstring(arg1)); } +ACTION_P3(VerifyPageLoad, mock, in_cf, url) { + EXPECT_TRUE(static_cast<bool>(in_cf) == mock->event_sink()->IsCFRendering()); + mock->event_sink()->ExpectAddressBarUrl(url); +} + ACTION_P4(DelaySendScanCode, loop, delay, c, mod) { loop->PostDelayedTask(FROM_HERE, NewRunnableFunction( simulate_input::SendScanCode, c, mod), delay); @@ -199,9 +226,70 @@ ACTION_P3(SelectItem, loop, delay, index) { simulate_input::NONE); } -ACTION_P2(DoDefaultUIActionInDocument, mock, matcher) { - mock->event_sink()->WaitForDOMAccessibilityTree(); - DoDefaultUIAction(mock->event_sink()->GetRendererWindow(), matcher); +ACTION(FocusAccObject) { + scoped_refptr<AccObject> object; + if (FindAccObjectInWindow(arg0, AccObjectMatcher(), &object)) + object->Focus(); +} + +ACTION_P(DoDefaultAction, matcher) { + scoped_refptr<AccObject> object; + if (FindAccObjectInWindow(arg0, matcher, &object)) + object->DoDefaultAction(); +} + +ACTION_P(FocusAccObject, matcher) { + scoped_refptr<AccObject> object; + if (FindAccObjectInWindow(arg0, matcher, &object)) + object->Focus(); +} + +ACTION_P(SelectAccObject, matcher) { + scoped_refptr<AccObject> object; + if (FindAccObjectInWindow(arg0, matcher, &object)) + object->Select(); +} + +ACTION(OpenContextMenuAsync) { + // Special case this implementation because the top-left of the window is + // much more likely to be empty than the center. + HWND hwnd = arg0; + scoped_refptr<AccObject> object; + // TODO(kkania): Switch to using WM_CONTEXTMENU with coordinates -1, -1 + // when Chrome supports this. See render_widget_host_view_win.h. + LPARAM coordinates = (1 << 16) | 1; + // IE needs both messages in order to work. + ::PostMessage(hwnd, WM_RBUTTONDOWN, (WPARAM)0, coordinates); + ::PostMessage(hwnd, WM_RBUTTONUP, (WPARAM)0, coordinates); +} + +ACTION_P(OpenContextMenuAsync, matcher) { + HWND hwnd = arg0; + scoped_refptr<AccObject> object; + if (FindAccObjectInWindow(hwnd, matcher, &object)) { + // TODO(kkania): Switch to using WM_CONTEXTMENU with coordinates -1, -1 + // when Chrome supports this. See render_widget_host_view_win.h. + gfx::Rect object_rect; + bool got_location = object->GetLocation(&object_rect); + EXPECT_TRUE(got_location) << "Could not get bounding rect for object: " + << object->GetDescription(); + if (got_location) { + // WM_RBUTTON* messages expect a relative coordinate, while GetLocation + // returned a screen coordinate. Adjust. + RECT rect; + BOOL got_window_rect = ::GetWindowRect(hwnd, &rect); + EXPECT_TRUE(got_window_rect) << "Could not get window bounds"; + if (got_window_rect) { + gfx::Rect window_rect(rect); + gfx::Point relative_origin = + object_rect.CenterPoint().Subtract(window_rect.origin()); + LPARAM coordinates = (relative_origin.y() << 16) | relative_origin.x(); + // IE needs both messages in order to work. + ::PostMessage(hwnd, WM_RBUTTONDOWN, (WPARAM)0, coordinates); + ::PostMessage(hwnd, WM_RBUTTONUP, (WPARAM)0, coordinates); + } + } + } } } // namespace chrome_frame_test diff --git a/chrome_frame/test/mock_ie_event_sink_test.h b/chrome_frame/test/mock_ie_event_sink_test.h index b6e772a..044d6a0 100644 --- a/chrome_frame/test/mock_ie_event_sink_test.h +++ b/chrome_frame/test/mock_ie_event_sink_test.h @@ -18,6 +18,7 @@ #include "chrome_frame/test/win_event_receiver.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/xulrunner-sdk/win/include/accessibility/AccessibleEventId.h" namespace chrome_frame_test { @@ -122,8 +123,12 @@ class MockIEEventSink : public IEEventListener { // Override IE's OnDocumentComplete to call our OnLoad, iff it is IE actually // rendering the page. virtual void OnDocumentComplete(IDispatch* dispatch, VARIANT* url) { - if (!event_sink_->IsCFRendering()) + if (!event_sink_->IsCFRendering()) { + ::NotifyWinEvent(IA2_EVENT_DOCUMENT_LOAD_COMPLETE, + event_sink_->GetRendererWindow(), + OBJID_CLIENT, 0L); OnLoad(IN_IE, V_BSTR(url)); + } } // Override CF's OnLoad to call our OnLoad. @@ -219,6 +224,12 @@ class MockWindowObserver : public WindowObserver { WindowWatchdog window_watcher_; }; +class MockAccessibilityEventObserver : public AccessibilityEventObserver { + public: + MOCK_METHOD1(OnAccDocLoad, void (HWND)); // NOLINT + MOCK_METHOD1(OnMenuPopup, void (HWND)); // NOLINT +}; + // This test fixture provides common methods needed for testing CF // integration with IE. gMock is used to verify that IE is reporting correct // navigational events and MockWebServer is used to verify that the correct diff --git a/chrome_frame/test/navigation_test.cc b/chrome_frame/test/navigation_test.cc index ebf60b0..76cad4e 100644 --- a/chrome_frame/test/navigation_test.cc +++ b/chrome_frame/test/navigation_test.cc @@ -6,6 +6,7 @@ #include "base/scoped_comptr_win.h" #include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/chrome_frame_ui_test_utils.h" #include "chrome_frame/test/mock_ie_event_sink_actions.h" #include "chrome_frame/test/mock_ie_event_sink_test.h" @@ -537,21 +538,25 @@ TEST_P(NavigationTransitionTest, DISABLED_JavascriptRedirection) { // Test following a link. TEST_P(NavigationTransitionTest, FollowLink) { - if (page1_.invokes_cf() && page2_.invokes_cf() && - GetInstalledIEVersion() > IE_6) { + if (page1_.invokes_cf() && page2_.invokes_cf()) { // For some reason IE 7 and 8 send two BeforeNavigate events for the second - // page for this case. + // page for this case. All versions do not send the OnLoad event for the + // second page if both pages are renderered in CF. LOG(ERROR) << "Test disabled for this configuration."; return; } + MockAccessibilityEventObserver acc_observer; + EXPECT_CALL(acc_observer, OnAccDocLoad(_)).Times(testing::AnyNumber()); + ie_mock_.ExpectNavigation(page1_.invokes_cf(), GetLinkPageUrl()); // Two requests are made when going from CF to IE, at least on Win7 IE8. EXPECT_CALL(server_mock_, Get(_, UrlPathEq(GetLinkPageUrl()), _)) .Times(testing::Between(1, 2)) .WillRepeatedly(SendResponse(&server_mock_, page1_)); - EXPECT_CALL(ie_mock_, OnLoad(page1_.invokes_cf(), StrEq(GetLinkPageUrl()))) - .WillOnce(DoDefaultUIActionInDocument(&ie_mock_, - UIObjectMatcher(L"", L"link"))); + EXPECT_CALL(ie_mock_, OnLoad(page1_.invokes_cf(), StrEq(GetLinkPageUrl()))); + EXPECT_CALL(acc_observer, OnAccDocLoad(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"", L"link"))) + .RetiresOnSaturation(); ie_mock_.ExpectNavigation(page2_.invokes_cf(), GetSimplePageUrl()); server_mock_.ExpectAndServeRequest(page2_, GetSimplePageUrl()); diff --git a/chrome_frame/test/run_all_unittests.cc b/chrome_frame/test/run_all_unittests.cc index fdf559d8..a22d4f5 100644 --- a/chrome_frame/test/run_all_unittests.cc +++ b/chrome_frame/test/run_all_unittests.cc @@ -69,6 +69,18 @@ int main(int argc, char **argv) { SetConfigBool(kChromeFrameHeadlessMode, true); + // Pretend that a screenreader is in use to cause Chrome to generate the + // accessibility tree during page load. Otherwise, Chrome will send back + // an unpopulated tree for the first request while it fetches the tree + // from the renderer. + BOOL is_screenreader_on = FALSE; + SystemParametersInfo(SPI_SETSCREENREADER, TRUE, NULL, 0); + SystemParametersInfo(SPI_GETSCREENREADER, 0, &is_screenreader_on, 0); + if (!is_screenreader_on) { + LOG(ERROR) << "Could not set screenreader property. Tests depending on " + << "MSAA in CF will likely fail..."; + } + base::ProcessHandle crash_service = chrome_frame_test::StartCrashService(); int ret = -1; // If mini_installer is used to register CF, we use the switch diff --git a/chrome_frame/test/ui_test.cc b/chrome_frame/test/ui_test.cc index 8eb4475..4f615b4 100644 --- a/chrome_frame/test/ui_test.cc +++ b/chrome_frame/test/ui_test.cc @@ -8,6 +8,7 @@ #include "base/scoped_variant_win.h" #include "chrome/common/url_constants.h" #include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/chrome_frame_ui_test_utils.h" #include "chrome_frame/test/mock_ie_event_sink_actions.h" #include "chrome_frame/test/mock_ie_event_sink_test.h" @@ -223,7 +224,7 @@ TEST_P(FullTabUITest, FLAKY_ViewSource) { ie_mock_.ExpectNewWindow(&view_source_mock); // For some reason this happens occasionally at least on XP IE7. - EXPECT_CALL(view_source_mock, OnLoad(false, StrEq(url_in_new_window))) + EXPECT_CALL(view_source_mock, OnLoad(IN_IE, StrEq(url_in_new_window))) .Times(testing::AtMost(1)); EXPECT_CALL(view_source_mock, OnLoad(in_cf, StrEq(view_source_url))) .WillOnce(testing::DoAll( @@ -322,26 +323,31 @@ class ContextMenuTest : public MockIEEventSinkTest, public testing::Test { ContextMenuTest() {} virtual void SetUp() { + // These tests must run on an unlocked desktop in order to use MSAA to + // select menu items. + ASSERT_TRUE(IsDesktopUnlocked()) + << "This test must run on an unlocked desktop"; + // These are UI-related tests, so we do not care about the exact // navigations that occur. ie_mock_.ExpectAnyNavigations(); + EXPECT_CALL(ie_mock_, OnLoad(_, _)).Times(testing::AnyNumber()); + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)).Times(testing::AnyNumber()); } + + protected: + testing::NiceMock<MockAccessibilityEventObserver> acc_observer_; }; -// Test Reload from context menu. -// Marking this test FLAKY as it fails at times on the buildbot. -// http://code.google.com/p/chromium/issues/detail?id=26549 -TEST_F(ContextMenuTest, FLAKY_CFReload) { +// Test reloading from the context menu. +TEST_F(ContextMenuTest, CFReload) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::MetaTag()); InSequence expect_in_sequence_for_scope; - // Reload using Rt-Click + DOWN + DOWN + DOWN + ENTER - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(GetSimplePageUrl()))) - .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_DOWN, 3, - simulate_input::NONE))); + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) + .WillOnce(OpenContextMenuAsync()); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"Reload"))); EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(GetSimplePageUrl()))) .WillOnce(CloseBrowserMock(&ie_mock_)); @@ -349,21 +355,18 @@ TEST_F(ContextMenuTest, FLAKY_CFReload) { LaunchIEAndNavigate(GetSimplePageUrl()); } -// Test view source using context menu -// Marking this test FLAKY as it fails at times on the buildbot. -// http://code.google.com/p/chromium/issues/detail?id=26549 -TEST_F(ContextMenuTest, FLAKY_CFViewSource) { +// Test view source from the context menu. +TEST_F(ContextMenuTest, CFViewSource) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::MetaTag()); MockIEEventSink view_source_mock; view_source_mock.ExpectAnyNavigations(); InSequence expect_in_sequence_for_scope; - // View source using Rt-Click + UP + UP + UP + UP + ENTER - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(GetSimplePageUrl()))) - .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_UP, 4, simulate_input::NONE))); + // View the page source. + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) + .WillOnce(OpenContextMenuAsync()); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"View page source"))); // Expect notification for view-source window, handle new window event // and attach a new ie_mock_ to the received web browser @@ -375,6 +378,9 @@ TEST_F(ContextMenuTest, FLAKY_CFViewSource) { url_in_new_window += view_source_url; ie_mock_.ExpectNewWindow(&view_source_mock); + // For some reason this happens occasionally at least on XP IE7 and Win7 IE8. + EXPECT_CALL(view_source_mock, OnLoad(IN_IE, StrEq(url_in_new_window))) + .Times(testing::AtMost(1)); EXPECT_CALL(view_source_mock, OnLoad(IN_CF, StrEq(view_source_url))) .WillOnce(testing::DoAll( VerifyAddressBarUrlWithGcf(&view_source_mock), @@ -386,43 +392,43 @@ TEST_F(ContextMenuTest, FLAKY_CFViewSource) { LaunchIEAndNavigate(GetSimplePageUrl()); } -TEST_F(ContextMenuTest, FLAKY_CFPageInfo) { +TEST_F(ContextMenuTest, CFPageInfo) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::MetaTag()); MockWindowObserver win_observer_mock; InSequence expect_in_sequence_for_scope; - // View page information using Rt-Click + UP + UP + UP + ENTER + // View page information. const wchar_t* kPageInfoWindowClass = L"Chrome_WidgetWin_0"; - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(GetSimplePageUrl()))) + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) .WillOnce(testing::DoAll( WatchWindow(&win_observer_mock, kPageInfoWindowClass), - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_UP, 3, simulate_input::NONE))); + OpenContextMenuAsync())); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"View page info"))); // Expect page info dialog to pop up. Dismiss the dialog with 'Esc' key const char* kPageInfoCaption = "Security Information"; EXPECT_CALL(win_observer_mock, OnWindowDetected(_, StrEq(kPageInfoCaption))) .WillOnce(testing::DoAll( - DelaySendChar(&loop_, 100, VK_ESCAPE, simulate_input::NONE), - DelayCloseBrowserMock(&loop_, 2000, &ie_mock_))); + DoCloseWindow(), + CloseBrowserMock(&ie_mock_))); LaunchIEAndNavigate(GetSimplePageUrl()); } -TEST_F(ContextMenuTest, FLAKY_CFInspector) { +TEST_F(ContextMenuTest, CFInspector) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::MetaTag()); MockWindowObserver win_observer_mock; InSequence expect_in_sequence_for_scope; - // Open developer tools using Rt-Click + UP + UP + ENTER + // Open developer tools. const wchar_t* kPageInfoWindowClass = L"Chrome_WidgetWin_0"; - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(GetSimplePageUrl()))) + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) .WillOnce(testing::DoAll( WatchWindow(&win_observer_mock, kPageInfoWindowClass), - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_UP, 2, simulate_input::NONE))); + OpenContextMenuAsync())); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"Inspect element"))); // Devtools begins life with "Untitled" caption and it changes // later to the 'Developer Tools - <url> form. @@ -430,10 +436,11 @@ TEST_F(ContextMenuTest, FLAKY_CFInspector) { EXPECT_CALL(win_observer_mock, OnWindowDetected(_, testing::StartsWith(kPageInfoCaption))) .WillOnce(testing::DoAll( - SetFocusToRenderer(&ie_mock_), - DelayCloseBrowserMock(&loop_, 2000, &ie_mock_))); + DelayDoCloseWindow(5000), // wait to catch possible crash + DelayCloseBrowserMock(&loop_, 5500, &ie_mock_))); - LaunchIEAndNavigate(GetSimplePageUrl()); + LaunchIENavigateAndLoop(GetSimplePageUrl(), + kChromeFrameLongNavigationTimeoutInSeconds * 2); } TEST_F(ContextMenuTest, FLAKY_CFSaveAs) { @@ -441,15 +448,14 @@ TEST_F(ContextMenuTest, FLAKY_CFSaveAs) { MockWindowObserver win_observer_mock; InSequence expect_in_sequence_for_scope; - // Open'Save As' dialog using Rt-Click + DOWN + DOWN + DOWN + DOWN + ENTER + // Open 'Save As' dialog. const wchar_t* kSaveDlgClass = L"#32770"; - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(GetSimplePageUrl()))) + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) .WillOnce(testing::DoAll( WatchWindow(&win_observer_mock, kSaveDlgClass), - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_DOWN, 4, - simulate_input::NONE))); + OpenContextMenuAsync())); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"Save as..."))); FilePath temp_file_path; EXPECT_TRUE(file_util::CreateTemporaryFile(&temp_file_path)); @@ -465,27 +471,30 @@ TEST_F(ContextMenuTest, FLAKY_CFSaveAs) { DelaySendChar(&loop_, 200, VK_RETURN, simulate_input::NONE), DelayCloseBrowserMock(&loop_, 4000, &ie_mock_))); - LaunchIEAndNavigate(GetSimplePageUrl()); + LaunchIENavigateAndLoop(GetSimplePageUrl(), + kChromeFrameLongNavigationTimeoutInSeconds * 2); ASSERT_NE(INVALID_FILE_ATTRIBUTES, GetFileAttributes(kSaveFileName)); ASSERT_TRUE(DeleteFile(kSaveFileName)); } // This tests that the about:version page can be opened via the CF context menu. -TEST_F(ContextMenuTest, FLAKY_CFAboutVersionLoads) { +TEST_F(ContextMenuTest, CFAboutVersionLoads) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::MetaTag()); const wchar_t* kAboutVersionUrl = L"gcf:about:version"; const wchar_t* kAboutVersionWithoutProtoUrl = L"about:version"; MockIEEventSink new_window_mock; new_window_mock.ExpectAnyNavigations(); + InSequence expect_in_sequence_for_scope; - ie_mock_.ExpectNavigation(IN_CF, GetSimplePageUrl()); - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(GetSimplePageUrl()))) - .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_UP, 1, simulate_input::NONE))); + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) + .WillOnce(OpenContextMenuAsync()); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"About*"))); ie_mock_.ExpectNewWindow(&new_window_mock); + // For some reason this happens occasionally at least on Win7 IE8. + EXPECT_CALL(new_window_mock, OnLoad(IN_IE, StrEq(kAboutVersionUrl))) + .Times(testing::AtMost(1)); EXPECT_CALL(new_window_mock, OnLoad(IN_CF, StrEq(kAboutVersionWithoutProtoUrl))) .WillOnce(testing::DoAll( @@ -499,16 +508,15 @@ TEST_F(ContextMenuTest, FLAKY_CFAboutVersionLoads) { LaunchIEAndNavigate(GetSimplePageUrl()); } -TEST_F(ContextMenuTest, FLAKY_IEOpen) { +TEST_F(ContextMenuTest, IEOpen) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::None()); - // Focus the renderer window by clicking and then tab once to highlight the - // link. - EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(GetLinkPageUrl()))) - .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 1, 1, simulate_input::LEFT), - DelaySendScanCode(&loop_, 1000, VK_TAB, simulate_input::NONE), - OpenContextMenu(&loop_, 2000), - SelectItem(&loop_, 3000, 0))); + InSequence expect_in_sequence_for_scope; + + // Open the link throught the context menu. + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) + .WillOnce(OpenContextMenuAsync(AccObjectMatcher(L"", L"link"))); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"Open"))); EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(GetSimplePageUrl()))) .WillOnce(testing::DoAll( @@ -518,23 +526,17 @@ TEST_F(ContextMenuTest, FLAKY_IEOpen) { LaunchIEAndNavigate(GetLinkPageUrl()); } -TEST_F(ContextMenuTest, FLAKY_IEOpenInNewWindow) { +TEST_F(ContextMenuTest, IEOpenInNewWindow) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::None()); MockIEEventSink new_window_mock; new_window_mock.ExpectAnyNavigations(); + InSequence expect_in_sequence_for_scope; - int open_new_window_index = 2; - if (chrome_frame_test::GetInstalledIEVersion() == IE_6) - open_new_window_index = 1; - - // Focus the renderer window by clicking and then tab once to highlight the - // link. - EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(GetLinkPageUrl()))) - .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 1, 1, simulate_input::LEFT), - DelaySendScanCode(&loop_, 500, VK_TAB, simulate_input::NONE), - OpenContextMenu(&loop_, 1000), - SelectItem(&loop_, 1500, open_new_window_index))); + // Open the link in a new window. + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) + .WillOnce(OpenContextMenuAsync(AccObjectMatcher(L"", L"link"))); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"Open in New Window"))); ie_mock_.ExpectNewWindow(&new_window_mock); EXPECT_CALL(new_window_mock, OnLoad(IN_IE, StrEq(GetSimplePageUrl()))) @@ -552,66 +554,31 @@ TEST_F(ContextMenuTest, FLAKY_IEOpenInNewWindow) { // Test Back/Forward from context menu. // Marking this test FLAKY as it fails at times on the buildbot. // http://code.google.com/p/chromium/issues/detail?id=26549 -TEST_F(ContextMenuTest, FLAKY_IEBackForward) { +TEST_F(ContextMenuTest, IEBackForward) { server_mock_.ExpectAndServeAnyRequests(CFInvocation::None()); std::wstring page1 = GetLinkPageUrl(); std::wstring page2 = GetSimplePageUrl(); InSequence expect_in_sequence_for_scope; - EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(page1))) - .WillOnce(Navigate(&ie_mock_, page2)); - - // Go back using Rt-Click + DOWN + ENTER - EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(page2))) - .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_DOWN, 1, - simulate_input::NONE))); - - // Go forward using Rt-Click + DOWN + DOWN + ENTER - EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(page1))) - .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_DOWN, 2, - simulate_input::NONE))); - - EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(page2))) - .WillOnce(CloseBrowserMock(&ie_mock_)); - - LaunchIEAndNavigate(page1); -} - -// Test Back/Forward from context menu. Loads page 1 in chrome and page 2 -// in IE. Then it tests back and forward using context menu -// Disabling this test as it won't work as per the current chrome external tab -// design. -// http://code.google.com/p/chromium/issues/detail?id=46615 -TEST_F(ContextMenuTest, DISABLED_BackForwardWithSwitch) { - std::wstring page1 = GetLinkPageUrl(); - std::wstring page2 = GetSimplePageUrl(); - InSequence expect_in_sequence_for_scope; - - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(page1))) + // Navigate to second page. + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) .WillOnce(Navigate(&ie_mock_, page2)); - server_mock_.ExpectAndServeRequest(CFInvocation::None(), page2); - // Go back using Rt-Click + DOWN + ENTER - EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(page2))) + // Go back. + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_DOWN, 1, - simulate_input::NONE))); + VerifyPageLoad(&ie_mock_, IN_IE, page2), + OpenContextMenuAsync())); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"Back"))); - // Go forward using Rt-Click + DOWN + DOWN + ENTER - EXPECT_CALL(ie_mock_, OnLoad(IN_CF, StrEq(page1))) + // Go forward. + EXPECT_CALL(acc_observer_, OnAccDocLoad(_)) .WillOnce(testing::DoAll( - DelaySendMouseClick(&ie_mock_, &loop_, 0, 10, 10, - simulate_input::RIGHT), - SendExtendedKeysEnter(&loop_, 500, VK_DOWN, 2, - simulate_input::NONE))); + VerifyPageLoad(&ie_mock_, IN_IE, page1), + OpenContextMenuAsync())); + EXPECT_CALL(acc_observer_, OnMenuPopup(_)) + .WillOnce(DoDefaultAction(AccObjectMatcher(L"Forward"))); EXPECT_CALL(ie_mock_, OnLoad(IN_IE, StrEq(page2))) .WillOnce(CloseBrowserMock(&ie_mock_)); diff --git a/chrome_frame/test/win_event_receiver.cc b/chrome_frame/test/win_event_receiver.cc index 18debbf..b967d5c 100644 --- a/chrome_frame/test/win_event_receiver.cc +++ b/chrome_frame/test/win_event_receiver.cc @@ -9,6 +9,8 @@ #include "chrome_frame/function_stub.h" +#include "third_party/xulrunner-sdk/win/include/accessibility/AccessibleEventId.h" + // WinEventReceiver methods WinEventReceiver::WinEventReceiver() : listener_(NULL), @@ -49,9 +51,12 @@ bool WinEventReceiver::InitializeHook(DWORD event_min, DWORD event_max) { DCHECK(hook_stub_ == NULL); hook_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), WinEventHook); + // Don't use WINEVENT_SKIPOWNPROCESS here because we fake generate an event + // in the mock IE event sink (IA2_EVENT_DOCUMENT_LOAD_COMPLETE) that we want + // to catch. hook_ = SetWinEventHook(event_min, event_max, NULL, reinterpret_cast<WINEVENTPROC>(hook_stub_->code()), 0, - 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + 0, WINEVENT_OUTOFCONTEXT); DLOG_IF(ERROR, hook_ == NULL) << "Unable to SetWinEvent hook"; return hook_ != NULL; } @@ -62,7 +67,7 @@ void WinEventReceiver::WinEventHook(WinEventReceiver* me, HWINEVENTHOOK hook, LONG child_id, DWORD event_thread_id, DWORD event_time) { DCHECK(me->listener_ != NULL); - me->listener_->OnEventReceived(event, hwnd); + me->listener_->OnEventReceived(event, hwnd, object_id, child_id); } // WindowWatchdog methods @@ -84,7 +89,8 @@ void WindowWatchdog::RemoveObserver(WindowObserver* observer) { win_event_receiver_.StopReceivingEvents(); } -void WindowWatchdog::OnEventReceived(DWORD event, HWND hwnd) { +void WindowWatchdog::OnEventReceived(DWORD event, HWND hwnd, LONG object_id, + LONG child_id) { // We need to look for top level windows and a natural check is for // WS_CHILD. Instead, checking for WS_CAPTION allows us to filter // out other stray popups @@ -111,3 +117,23 @@ void WindowWatchdog::OnEventReceived(DWORD event, HWND hwnd) { i->observer->OnWindowDetected(hwnd, caption); } } + +// AccessibilityEventListener methods +AccessibilityEventObserver::AccessibilityEventObserver() { + event_receiver_.SetListenerForEvents(this, EVENT_SYSTEM_MENUPOPUPSTART, + IA2_EVENT_DOCUMENT_LOAD_COMPLETE); +} + +void AccessibilityEventObserver::OnEventReceived(DWORD event, + HWND hwnd, + LONG object_id, + LONG child_id) { + switch (event) { + case EVENT_SYSTEM_MENUPOPUPSTART: + OnMenuPopup(hwnd); + break; + case IA2_EVENT_DOCUMENT_LOAD_COMPLETE: + OnAccDocLoad(hwnd); + break; + } +} diff --git a/chrome_frame/test/win_event_receiver.h b/chrome_frame/test/win_event_receiver.h index 3e67f06..afa5924 100644 --- a/chrome_frame/test/win_event_receiver.h +++ b/chrome_frame/test/win_event_receiver.h @@ -18,7 +18,8 @@ class WinEventListener { virtual ~WinEventListener() {} // Called when an event has been received. |hwnd| is the window that generated // the event, or null if no window is associated with the event. - virtual void OnEventReceived(DWORD event, HWND hwnd) = 0; + virtual void OnEventReceived(DWORD event, HWND hwnd, LONG object_id, + LONG child_id) = 0; }; // Receives WinEvents and forwards them to its listener. The event types the @@ -81,10 +82,28 @@ class WindowWatchdog : public WinEventListener { typedef std::vector<WindowObserverEntry> ObserverMap; // Overriden from WinEventListener. - virtual void OnEventReceived(DWORD event, HWND hwnd); + virtual void OnEventReceived(DWORD event, HWND hwnd, LONG object_id, + LONG child_id); ObserverMap observers_; WinEventReceiver win_event_receiver_; }; -#endif // CHROME_FRAME_TEST_WIN_EVENT_RECEIVER_H_
\ No newline at end of file +class AccessibilityEventObserver : public WinEventListener { + public: + AccessibilityEventObserver(); + + // Called when the DOM accessibility tree for the page is ready. + virtual void OnAccDocLoad(HWND hwnd) = 0; + + // Called when a new menu is shown. + virtual void OnMenuPopup(HWND hwnd) = 0; + + private: + virtual void OnEventReceived(DWORD event, HWND hwnd, LONG object_id, + LONG child_id); + + WinEventReceiver event_receiver_; +}; + +#endif // CHROME_FRAME_TEST_WIN_EVENT_RECEIVER_H_ |