diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-02 21:26:34 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-02 21:26:34 +0000 |
commit | 586b3b0b16308c37fbf19f072dd82a448f456155 (patch) | |
tree | 4898b2914f167deabcddddc3f41e7bcacf1cd054 /chrome_frame | |
parent | 4a729ae7ff3f615214d2120a2218a5bef4742c58 (diff) | |
download | chromium_src-586b3b0b16308c37fbf19f072dd82a448f456155.zip chromium_src-586b3b0b16308c37fbf19f072dd82a448f456155.tar.gz chromium_src-586b3b0b16308c37fbf19f072dd82a448f456155.tar.bz2 |
[chrome_frame] Add methods for finding and performing default action on Accessibility objects. Use this approach in one test.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/2895016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54603 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
-rw-r--r-- | chrome_frame/chrome_frame.gyp | 42 | ||||
-rw-r--r-- | chrome_frame/test/chrome_frame_test_utils.cc | 293 | ||||
-rw-r--r-- | chrome_frame/test/chrome_frame_test_utils.h | 65 | ||||
-rw-r--r-- | chrome_frame/test/ie_event_sink.cc | 27 | ||||
-rw-r--r-- | chrome_frame/test/ie_event_sink.h | 4 | ||||
-rw-r--r-- | chrome_frame/test/mock_ie_event_sink_actions.h | 6 | ||||
-rw-r--r-- | chrome_frame/test/mock_ie_event_sink_test.h | 1 | ||||
-rw-r--r-- | chrome_frame/test/navigation_test.cc | 17 | ||||
-rw-r--r-- | chrome_frame/test/net/fake_external_tab.cc | 2 | ||||
-rw-r--r-- | chrome_frame/test/win_event_receiver.cc (renamed from chrome_frame/test/window_watchdog.cc) | 85 | ||||
-rw-r--r-- | chrome_frame/test/win_event_receiver.h | 90 | ||||
-rw-r--r-- | chrome_frame/test/window_watchdog.h | 63 |
12 files changed, 549 insertions, 146 deletions
diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index b517f51..7d36d33 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -152,8 +152,8 @@ 'test/urlmon_moniker_tests.h', 'test/urlmon_moniker_unittest.cc', 'test/util_unittests.cc', - 'test/window_watchdog.h', - 'test/window_watchdog.cc', + 'test/win_event_receiver.h', + 'test/win_event_receiver.cc', 'unittest_precompile.h', 'unittest_precompile.cc', 'urlmon_upload_data_stream.cc', @@ -172,7 +172,7 @@ ['OS=="win"', { 'link_settings': { 'libraries': [ - '-lshdocvw.lib', + '-lshdocvw.lib', '-loleacc.lib', ], }, 'msvs_settings': { @@ -246,8 +246,8 @@ 'test/urlmon_moniker_tests.h', 'test/urlmon_moniker_integration_test.cc', 'test/url_request_test.cc', - 'test/window_watchdog.cc', - 'test/window_watchdog.h', + 'test/win_event_receiver.cc', + 'test/win_event_receiver.h', 'chrome_tab.h', 'chrome_tab.idl', 'test_utils.cc', @@ -264,6 +264,11 @@ ], 'conditions': [ ['OS=="win"', { + 'link_settings': { + 'libraries': [ + '-loleacc.lib', + ], + }, 'msvs_settings': { 'VCLinkerTool': { 'DelayLoadDLLs': ['xpcom.dll', 'nspr4.dll'], @@ -317,8 +322,8 @@ 'test/simulate_input.h', 'test_utils.cc', 'test_utils.h', - 'test/window_watchdog.cc', - 'test/window_watchdog.h', + 'test/win_event_receiver.cc', + 'test/win_event_receiver.h', ], 'include_dirs': [ '<@(xul_include_directories)', @@ -328,6 +333,11 @@ ], 'conditions': [ ['OS=="win"', { + 'link_settings': { + 'libraries': [ + '-loleacc.lib', + ], + }, 'dependencies': [ '../breakpad/breakpad.gyp:breakpad_handler', '../chrome/chrome.gyp:automation', @@ -370,8 +380,8 @@ 'test/simulate_input.h', 'test/test_server.cc', 'test/test_server.h', - 'test/window_watchdog.cc', - 'test/window_watchdog.h', + 'test/win_event_receiver.cc', + 'test/win_event_receiver.h', 'test/net/fake_external_tab.cc', 'test/net/fake_external_tab.h', 'test/net/process_singleton_subclass.cc', @@ -389,6 +399,11 @@ ], 'conditions': [ ['OS=="win"', { + 'link_settings': { + 'libraries': [ + '-loleacc.lib', + ], + }, 'msvs_settings': { 'VCLinkerTool': { 'DelayLoadDLLs': ['prntvpt.dll'], @@ -438,8 +453,8 @@ 'test_utils.h', 'test/simulate_input.cc', 'test/simulate_input.h', - 'test/window_watchdog.cc', - 'test/window_watchdog.h', + 'test/win_event_receiver.cc', + 'test/win_event_receiver.h', 'chrome_tab.h', 'chrome_tab.idl', '../base/test/test_file_util_win.cc', @@ -458,6 +473,11 @@ ], 'conditions': [ ['OS=="win"', { + 'link_settings': { + 'libraries': [ + '-loleacc.lib', + ], + }, 'dependencies': [ # TODO(slightlyoff): Get automation targets working on OS X '../chrome/chrome.gyp:automation', diff --git a/chrome_frame/test/chrome_frame_test_utils.cc b/chrome_frame/test/chrome_frame_test_utils.cc index 8b041a6..78fccc1 100644 --- a/chrome_frame/test/chrome_frame_test_utils.cc +++ b/chrome_frame/test/chrome_frame_test_utils.cc @@ -7,8 +7,12 @@ #include <atlbase.h> #include <atlwin.h> #include <iepmapi.h> +#include <oleacc.h> +#include <oleauto.h> #include <sddl.h> +#include <sstream> + #include "base/command_line.h" #include "base/file_version_info.h" #include "base/file_util.h" @@ -26,9 +30,11 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths_internal.h" +#include "chrome_frame/test/win_event_receiver.h" #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 { @@ -43,6 +49,7 @@ 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) { @@ -848,15 +855,6 @@ void WebBrowserEventSink::Exec(const GUID* cmd_group_guid, DWORD command_id, command_id, cmd_exec_opt, in_args, out_args)); } -void WebBrowserEventSink::WatchChromeWindow(const wchar_t* window_class) { - DCHECK(window_class); - window_watcher_.AddObserver(this, WideToUTF8(window_class)); -} - -void WebBrowserEventSink::StopWatching() { - window_watcher_.RemoveObserver(this); -} - std::wstring GetExeVersion(const std::wstring& exe_path) { scoped_ptr<FileVersionInfo> ie_version_info( FileVersionInfo::CreateFileVersionInfo(FilePath(exe_path))); @@ -960,6 +958,283 @@ 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 4b74f1e..0df5285 100644 --- a/chrome_frame/test/chrome_frame_test_utils.h +++ b/chrome_frame/test/chrome_frame_test_utils.h @@ -23,7 +23,6 @@ #include "chrome_frame/test_utils.h" #include "chrome_frame/test/simulate_input.h" -#include "chrome_frame/test/window_watchdog.h" #include "chrome_frame/utils.h" // Include without path to make GYP build see it. @@ -136,7 +135,6 @@ class WebBrowserEventSink : public CComObjectRootEx<CComMultiThreadModel>, public IDispEventSimpleImpl<0, WebBrowserEventSink, &DIID_DWebBrowserEvents2>, - public WindowObserver, public IUnknown { public: typedef IDispEventSimpleImpl<0, WebBrowserEventSink, @@ -209,10 +207,6 @@ END_SINK_MAP() void Exec(const GUID* cmd_group_guid, DWORD command_id, DWORD cmd_exec_opt, VARIANT* in_args, VARIANT* out_args); - // Watch for new window created. - void WatchChromeWindow(const wchar_t* window_class); - void StopWatching(); - // Overridable methods for the mock. STDMETHOD_(void, OnNavigateError)(IDispatch* dispatch, VARIANT* url, VARIANT* frame_name, VARIANT* status_code, @@ -329,8 +323,6 @@ END_SINK_MAP() static _ATL_FUNC_INFO kVoidMethodInfo; static _ATL_FUNC_INFO kDocumentCompleteInfo; static _ATL_FUNC_INFO kFileDownloadInfo; - - WindowWatchdog window_watcher_; }; // Returns the path of the exe passed in. @@ -377,6 +369,63 @@ 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/ie_event_sink.cc b/chrome_frame/test/ie_event_sink.cc index c04c3c8..fac3dc5 100644 --- a/chrome_frame/test/ie_event_sink.cc +++ b/chrome_frame/test/ie_event_sink.cc @@ -213,17 +213,6 @@ void IEEventSink::ExpectRendererWindowHasFocus() { HWND renderer_window = GetRendererWindow(); EXPECT_TRUE(IsWindow(renderer_window)); - if (IsCFRendering()) { - for (HWND first_child = renderer_window; - IsWindow(first_child); first_child = GetWindow(first_child, GW_CHILD)) { - renderer_window = first_child; - } - - wchar_t class_name[MAX_PATH] = {0}; - GetClassName(renderer_window, class_name, arraysize(class_name)); - EXPECT_EQ(0, _wcsicmp(class_name, L"Chrome_RenderWidgetHostHWND")); - } - DWORD renderer_thread = 0; DWORD renderer_process = 0; renderer_thread = GetWindowThreadProcessId(renderer_window, @@ -270,9 +259,13 @@ HWND IEEventSink::GetRendererWindow() { EXPECT_TRUE(IsWindow(activex_window)); // chrome tab window is the first (and the only) child of activex - HWND chrome_tab_window = GetWindow(activex_window, GW_CHILD); - EXPECT_TRUE(IsWindow(chrome_tab_window)); - renderer_window = GetWindow(chrome_tab_window, GW_CHILD); + for (HWND first_child = activex_window; + IsWindow(first_child); first_child = GetWindow(first_child, GW_CHILD)) { + renderer_window = first_child; + } + wchar_t class_name[MAX_PATH] = {0}; + GetClassName(renderer_window, class_name, arraysize(class_name)); + EXPECT_EQ(0, _wcsicmp(class_name, L"Chrome_RenderWidgetHostHWND")); } } else { DCHECK(web_browser2_); @@ -336,6 +329,12 @@ void IEEventSink::Refresh() { web_browser2_->Refresh2(refresh_level.AsInput()); } +void IEEventSink::WaitForDOMAccessibilityTree() { + if (IsCFRendering()) + WaitForChromeDOMAccessibilityTree(GetRendererWindow()); +} + +// private methods void IEEventSink::ConnectToChromeFrame() { DCHECK(web_browser2_); if (chrome_frame_.get()) diff --git a/chrome_frame/test/ie_event_sink.h b/chrome_frame/test/ie_event_sink.h index 6694575..3268ccd 100644 --- a/chrome_frame/test/ie_event_sink.h +++ b/chrome_frame/test/ie_event_sink.h @@ -119,6 +119,10 @@ 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 ff7d84d..01ef90e 100644 --- a/chrome_frame/test/mock_ie_event_sink_actions.h +++ b/chrome_frame/test/mock_ie_event_sink_actions.h @@ -5,6 +5,7 @@ #ifndef CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_ACTIONS_H_ #define CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_ACTIONS_H_ +#include "chrome_frame/test/chrome_frame_test_utils.h" #include "chrome_frame/test/simulate_input.h" #include "testing/gmock/include/gmock/gmock.h" @@ -167,6 +168,11 @@ ACTION_P3(SelectItem, loop, delay, index) { simulate_input::NONE); } +ACTION_P2(DoDefaultUIActionInDocument, mock, matcher) { + mock->event_sink()->WaitForDOMAccessibilityTree(); + DoDefaultUIAction(mock->event_sink()->GetRendererWindow(), matcher); +} + } // namespace chrome_frame_test #endif // CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_ACTIONS_H_
\ No newline at end of file diff --git a/chrome_frame/test/mock_ie_event_sink_test.h b/chrome_frame/test/mock_ie_event_sink_test.h index c2ad918..e751073 100644 --- a/chrome_frame/test/mock_ie_event_sink_test.h +++ b/chrome_frame/test/mock_ie_event_sink_test.h @@ -14,6 +14,7 @@ #include "chrome_frame/test/ie_event_sink.h" #include "chrome_frame/test/test_server.h" #include "chrome_frame/test/test_with_web_server.h" +#include "chrome_frame/test/win_event_receiver.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/chrome_frame/test/navigation_test.cc b/chrome_frame/test/navigation_test.cc index 2324f86..52f0aac 100644 --- a/chrome_frame/test/navigation_test.cc +++ b/chrome_frame/test/navigation_test.cc @@ -536,8 +536,8 @@ TEST_P(NavigationTransitionTest, DISABLED_JavascriptRedirection) { LaunchIEAndNavigate(redirect_url); } -// Test following a link by TAB + ENTER. -TEST_P(NavigationTransitionTest, FLAKY_FollowLink) { +// Test following a link. +TEST_P(NavigationTransitionTest, FollowLink) { if (page1_.invokes_cf() && page2_.invokes_cf() && GetInstalledIEVersion() > IE_6) { // For some reason IE 7 and 8 send two BeforeNavigate events for the second @@ -546,15 +546,16 @@ TEST_P(NavigationTransitionTest, FLAKY_FollowLink) { return; } ie_mock_.ExpectNavigation(page1_.invokes_cf(), GetLinkPageUrl()); - server_mock_.ExpectAndServeRequestAllowCache(page1_, 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(testing::DoAll( - SetFocusToRenderer(&ie_mock_), - DelaySendChar(&loop_, 500, VK_TAB, simulate_input::NONE), - DelaySendChar(&loop_, 1000, VK_RETURN, simulate_input::NONE))); + .WillOnce(DoDefaultUIActionInDocument(&ie_mock_, + UIObjectMatcher(L"", L"link"))); ie_mock_.ExpectNavigation(page2_.invokes_cf(), GetSimplePageUrl()); - server_mock_.ExpectAndServeRequestAllowCache(page2_, GetSimplePageUrl()); + server_mock_.ExpectAndServeRequest(page2_, GetSimplePageUrl()); EXPECT_CALL(ie_mock_, OnLoad(page2_.invokes_cf(), StrEq(GetSimplePageUrl()))) .WillOnce(testing::DoAll( VerifyAddressBarUrl(&ie_mock_), diff --git a/chrome_frame/test/net/fake_external_tab.cc b/chrome_frame/test/net/fake_external_tab.cc index 5d8e37f..ca0eece 100644 --- a/chrome_frame/test/net/fake_external_tab.cc +++ b/chrome_frame/test/net/fake_external_tab.cc @@ -36,7 +36,7 @@ #include "chrome_frame/utils.h" #include "chrome_frame/test/chrome_frame_test_utils.h" #include "chrome_frame/test/simulate_input.h" -#include "chrome_frame/test/window_watchdog.h" +#include "chrome_frame/test/win_event_receiver.h" #include "chrome_frame/test/net/test_automation_resource_message_filter.h" namespace { diff --git a/chrome_frame/test/window_watchdog.cc b/chrome_frame/test/win_event_receiver.cc index b388c0a..152fb0e 100644 --- a/chrome_frame/test/window_watchdog.cc +++ b/chrome_frame/test/win_event_receiver.cc @@ -2,65 +2,86 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome_frame/test/window_watchdog.h" +#include "chrome_frame/test/win_event_receiver.h" #include "base/logging.h" #include "base/string_util.h" #include "chrome_frame/function_stub.h" +// WinEventReceiver methods +WinEventReceiver::WinEventReceiver() : hook_(NULL), hook_stub_(NULL) { +} -WindowWatchdog::WindowWatchdog() : hook_(NULL), hook_stub_(NULL) { +WinEventReceiver::~WinEventReceiver() { + StopReceivingEvents(); } -WindowWatchdog::~WindowWatchdog() { - UninitializeHook(); +void WinEventReceiver::SetListenerForEvent(WinEventListener* listener, + DWORD event) { + SetListenerForEvents(listener, event, event); } -void WindowWatchdog::AddObserver(WindowObserver* observer, - const std::string& window_class) { - WindowObserverEntry new_entry = { observer, window_class }; - observers_.push_back(new_entry); +void WinEventReceiver::SetListenerForEvents(WinEventListener* listener, + DWORD event_min, DWORD event_max) { + DCHECK(listener != NULL); + StopReceivingEvents(); - if (!hook_) - InitializeHook(); + listener_ = listener; + + InitializeHook(event_min, event_max); } -void WindowWatchdog::RemoveObserver(WindowObserver* observer) { - for (ObserverMap::iterator i = observers_.begin(); i != observers_.end();) { - i = (observer = i->observer) ? observers_.erase(i) : ++i; +void WinEventReceiver::StopReceivingEvents() { + if (hook_) { + ::UnhookWinEvent(hook_); + hook_ = NULL; + FunctionStub::Destroy(hook_stub_); + hook_stub_ = NULL; } - - if (observers_.empty()) - UninitializeHook(); } -bool WindowWatchdog::InitializeHook() { +bool WinEventReceiver::InitializeHook(DWORD event_min, DWORD event_max) { DCHECK(hook_ == NULL); DCHECK(hook_stub_ == NULL); hook_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), WinEventHook); - hook_ = SetWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_SHOW, NULL, + hook_ = SetWinEventHook(event_min, event_max, NULL, reinterpret_cast<WINEVENTPROC>(hook_stub_->code()), 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); - + DLOG_IF(ERROR, hook_ == NULL) << "Unable to SetWinEvent hook"; return hook_ != NULL; } -void WindowWatchdog::UninitializeHook() { - if (hook_) { - ::UnhookWinEvent(hook_); - hook_ = NULL; - FunctionStub::Destroy(hook_stub_); - hook_stub_ = NULL; +// static +void WinEventReceiver::WinEventHook(WinEventReceiver* me, HWINEVENTHOOK hook, + DWORD event, HWND hwnd, LONG object_id, + LONG child_id, DWORD event_thread_id, + DWORD event_time) { + DCHECK(me->listener_ != NULL); + me->listener_->OnEventReceived(event, hwnd); +} + +// WindowWatchdog methods +void WindowWatchdog::AddObserver(WindowObserver* observer, + const std::string& window_class) { + if (observers_.empty()) + win_event_receiver_.SetListenerForEvent(this, EVENT_OBJECT_SHOW); + + WindowObserverEntry new_entry = { observer, window_class }; + observers_.push_back(new_entry); +} + +void WindowWatchdog::RemoveObserver(WindowObserver* observer) { + for (ObserverMap::iterator i = observers_.begin(); i != observers_.end();) { + i = (observer == i->observer) ? observers_.erase(i) : ++i; } + + if (observers_.empty()) + win_event_receiver_.StopReceivingEvents(); } -// static -void WindowWatchdog::WinEventHook(WindowWatchdog* me, HWINEVENTHOOK hook, - DWORD event, HWND hwnd, LONG object_id, - LONG child_id, DWORD event_thread_id, - DWORD event_time) { +void WindowWatchdog::OnEventReceived(DWORD event, HWND hwnd) { // 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 @@ -71,8 +92,8 @@ void WindowWatchdog::WinEventHook(WindowWatchdog* me, HWINEVENTHOOK hook, ::GetClassNameA(hwnd, class_name, arraysize(class_name)); ObserverMap interested_observers; - for (ObserverMap::iterator i = me->observers_.begin(); - i != me->observers_.end(); i++) { + for (ObserverMap::iterator i = observers_.begin(); + i != observers_.end(); i++) { if (0 == lstrcmpA(i->window_class.c_str(), class_name)) { interested_observers.push_back(*i); } diff --git a/chrome_frame/test/win_event_receiver.h b/chrome_frame/test/win_event_receiver.h new file mode 100644 index 0000000..3e67f06 --- /dev/null +++ b/chrome_frame/test/win_event_receiver.h @@ -0,0 +1,90 @@ +// 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_WIN_EVENT_RECEIVER_H_ +#define CHROME_FRAME_TEST_WIN_EVENT_RECEIVER_H_ + +#include <windows.h> + +#include <string> +#include <vector> + +struct FunctionStub; + +// Listens to WinEvents from the WinEventReceiver. +class WinEventListener { + public: + 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; +}; + +// Receives WinEvents and forwards them to its listener. The event types the +// listener wants to receive can be specified. +class WinEventReceiver { + public: + WinEventReceiver(); + ~WinEventReceiver(); + + // Sets the sole listener of this receiver. The listener will receive all + // WinEvents of the given event type. Any previous listener will be + // replaced. |listener| should not be NULL. + void SetListenerForEvent(WinEventListener* listener, DWORD event); + + // Same as above, but sets a range of events to listen for. + void SetListenerForEvents(WinEventListener* listener, DWORD event_min, + DWORD event_max); + + // Stops receiving events and forwarding them to the listener. It is + // permitted to call this even if the receiver has already been stopped. + void StopReceivingEvents(); + + private: + bool InitializeHook(DWORD event_min, DWORD event_max); + + static void CALLBACK WinEventHook(WinEventReceiver* me, HWINEVENTHOOK hook, + DWORD event, HWND hwnd, LONG object_id, LONG child_id, + DWORD event_thread_id, DWORD event_time); + + WinEventListener* listener_; + HWINEVENTHOOK hook_; + FunctionStub* hook_stub_; +}; + +// Observes window show events. Used with WindowWatchdog. +class WindowObserver { + public: + virtual ~WindowObserver() {} + // Called when a window has been shown. + virtual void OnWindowDetected(HWND hwnd, const std::string& caption) = 0; +}; + +// Watch a for window to be shown with the given window class name. +// If found, call the observer interested in it. +class WindowWatchdog : public WinEventListener { + public: + // Register for notifications for |window_class|. An observer can register + // for multiple notifications. + void AddObserver(WindowObserver* observer, const std::string& window_class); + + // Remove all entries for |observer|. + void RemoveObserver(WindowObserver* observer); + + private: + struct WindowObserverEntry { + WindowObserver* observer; + std::string window_class; + }; + + typedef std::vector<WindowObserverEntry> ObserverMap; + + // Overriden from WinEventListener. + virtual void OnEventReceived(DWORD event, HWND hwnd); + + ObserverMap observers_; + WinEventReceiver win_event_receiver_; +}; + +#endif // CHROME_FRAME_TEST_WIN_EVENT_RECEIVER_H_
\ No newline at end of file diff --git a/chrome_frame/test/window_watchdog.h b/chrome_frame/test/window_watchdog.h deleted file mode 100644 index 60accda..0000000 --- a/chrome_frame/test/window_watchdog.h +++ /dev/null @@ -1,63 +0,0 @@ -// 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_WINDOW_WATCHDOG_H_ -#define CHROME_FRAME_TEST_WINDOW_WATCHDOG_H_ - -#include <windows.h> - -#include <string> -#include <vector> - -struct FunctionStub; - -// Interface implemented by WindowWatchdog users. An observer can register -// for notifications on multiple window classes. -class WindowObserver { // NOLINT - public: - virtual void OnWindowDetected(HWND hwnd, const std::string& caption) = 0; - - protected: - virtual ~WindowObserver() {} -}; - -// Watch a for window to be shown with the given window class name. -// If found, call the observer interested in it. -class WindowWatchdog { - public: - WindowWatchdog(); - ~WindowWatchdog(); - - // Register for notifications for |window_class|. An observer can register - // for multiple notifications - void AddObserver(WindowObserver* observer, const std::string& window_class); - - // Remove all entries for |observer| - void RemoveObserver(WindowObserver* observer); - - protected: - bool InitializeHook(); - void UninitializeHook(); - - static void CALLBACK WinEventHook(WindowWatchdog* me, HWINEVENTHOOK hook, - DWORD event, HWND hwnd, LONG object_id, LONG child_id, - DWORD event_thread_id, DWORD event_time); - - void OnDialogFound(HWND hwnd, const std::string& caption); - - protected: - struct WindowObserverEntry { - WindowObserver* observer; - std::string window_class; - }; - - typedef std::vector<WindowObserverEntry> ObserverMap; - - HWINEVENTHOOK hook_; - ObserverMap observers_; - FunctionStub* hook_stub_; -}; - - -#endif // CHROME_FRAME_TEST_WINDOW_WATCHDOG_H_ |