summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome_frame/chrome_frame.gyp23
-rw-r--r--chrome_frame/test/delete_chrome_history_test.cc276
-rw-r--r--chrome_frame/test/mock_ie_event_sink_actions.h67
-rw-r--r--chrome_frame/test/mock_ie_event_sink_test.h54
-rw-r--r--chrome_frame/test/navigation_test.cc32
-rw-r--r--chrome_frame/test/net/fake_external_tab.cc13
-rw-r--r--chrome_frame/test/ui_test.cc67
-rw-r--r--chrome_frame/test/win_event_receiver.cc191
-rw-r--r--chrome_frame/test/win_event_receiver.h78
9 files changed, 674 insertions, 127 deletions
diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp
index 83bdb34..5d3893e 100644
--- a/chrome_frame/chrome_frame.gyp
+++ b/chrome_frame/chrome_frame.gyp
@@ -226,6 +226,9 @@
'../build/temp_gyp/googleurl.gyp:googleurl',
'../chrome/chrome.gyp:common',
'../chrome/chrome.gyp:utility',
+ '../chrome/chrome.gyp:browser',
+ '../chrome/chrome.gyp:debugger',
+ '../chrome/chrome.gyp:renderer',
'../net/net.gyp:net_test_support',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
@@ -247,6 +250,7 @@
'test/chrome_frame_ui_test_utils.h',
'test/chrome_frame_automation_mock.cc',
'test/chrome_frame_automation_mock.h',
+ 'test/delete_chrome_history_test.cc',
'test/http_server.cc',
'test/http_server.h',
'test/ie_event_sink.cc',
@@ -274,7 +278,6 @@
'test/win_event_receiver.h',
'chrome_tab.h',
'chrome_tab.idl',
- 'renderer_glue.cc',
'test_utils.cc',
'test_utils.h',
],
@@ -304,7 +307,23 @@
'../chrome/chrome.gyp:automation',
'../chrome/chrome.gyp:installer_util',
'../google_update/google_update.gyp:google_update',
- ]
+ ],
+ 'configurations': {
+ 'Debug_Base': {
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ 'LinkIncremental': '<(msvs_large_module_debug_link_mode)',
+ },
+ },
+ },
+ },
+ 'conditions': [
+ ['win_use_allocator_shim==1', {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator',
+ ],
+ }],
+ ],
}],
],
},
diff --git a/chrome_frame/test/delete_chrome_history_test.cc b/chrome_frame/test/delete_chrome_history_test.cc
new file mode 100644
index 0000000..a856a2b0
--- /dev/null
+++ b/chrome_frame/test/delete_chrome_history_test.cc
@@ -0,0 +1,276 @@
+// 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 <string>
+
+#include "base/rand_util.h"
+#include "chrome/browser/webdata/web_database.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome_frame/test/mock_ie_event_sink_actions.h"
+#include "chrome_frame/test/mock_ie_event_sink_test.h"
+
+using testing::_;
+
+namespace chrome_frame_test {
+
+class DeleteBrowsingHistoryTest
+ : public MockIEEventSinkTest,
+ public testing::Test {
+ public:
+ DeleteBrowsingHistoryTest() {}
+
+ virtual void SetUp() {
+ // We will use the OnAccLoad event to monitor page loads, so we ignore
+ // these.
+ ie_mock_.ExpectAnyNavigations();
+ ie_mock2_.ExpectAnyNavigations();
+ ie_mock3_.ExpectAnyNavigations();
+ EXPECT_CALL(acc_observer_, OnAccDocLoad(_)).Times(testing::AnyNumber());
+
+ // Use a random image_path to ensure that a prior run does not
+ // interfere with our expectations about caching.
+ image_path_ = L"/" + RandomChars(32);
+ topHtml =
+ "<html><head>"
+ "<meta http-equiv=\"x-ua-compatible\" content=\"chrome=1\" />"
+ "</head>"
+ "<body>"
+ "<form method=\"POST\" action=\"/form\">"
+ "<input title=\"username\" type=\"text\" name=\"username\" />"
+ "<input type=\"submit\" title=\"Submit\" name=\"Submit\" />"
+ "</form>"
+ "<img alt=\"Blank image.\" src=\"" + WideToASCII(image_path_) + "\" />"
+ "This is some text.</body></html>";
+ }
+
+ protected:
+ std::wstring image_path_;
+ std::string topHtml;
+
+ testing::NiceMock<MockAccEventObserver> acc_observer_;
+ MockWindowObserver delete_browsing_history_window_observer_mock_;
+ MockObjectWatcherDelegate ie_process_exit_watcher_mock_;
+
+ testing::StrictMock<MockIEEventSink> ie_mock2_;
+ testing::StrictMock<MockIEEventSink> ie_mock3_;
+
+ // Returns a string of |count| lowercase random characters.
+ static std::wstring RandomChars(int count) {
+ srand(static_cast<unsigned int>(time(NULL)));
+ std::wstring str;
+ for (int i = 0; i < count; ++i)
+ str += L'a' + base::RandInt(0, 25);
+ return str;
+ }
+};
+
+namespace {
+
+const wchar_t* kFormFieldName = L"username";
+const wchar_t* kFormFieldValue = L"test_username";
+
+const char* kHtmlHttpHeaders =
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n";
+const char* kFormResultHtml =
+ "<html><head><meta http-equiv=\"x-ua-compatible\" content=\"chrome=1\" />"
+ "</head><body>Nice work.</body></html>";
+const char* kBlankPngResponse[] = {
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Content-Type: image/png\r\n"
+ "Cache-Control: max-age=3600, must-revalidate\r\n",
+ "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
+ "\x00\x00\x00\x01\x00\x00\x00\x01\x01\x03\x00\x00\x00\x25\xdb\x56"
+ "\xca\x00\x00\x00\x03\x50\x4c\x54\x45\x00\x00\x00\xa7\x7a\x3d\xda"
+ "\x00\x00\x00\x01\x74\x52\x4e\x53\x00\x40\xe6\xd8\x66\x00\x00\x00"
+ "\x0a\x49\x44\x41\x54\x08\xd7\x63\x60\x00\x00\x00\x02\x00\x01\xe2"
+ "\x21\xbc\x33\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"};
+
+const size_t kBlankPngFileLength = 95;
+} // anonymous namespace
+
+// Looks up |element_name| in the Chrome form data DB and ensures that the
+// results match |matcher|.
+ACTION_P2(ExpectFormValuesForElementNameMatch, element_name, matcher) {
+ FilePath profile_path(
+ chrome_frame_test::GetProfilePath(kIexploreProfileName)
+ .Append(L"Default").Append(chrome::kWebDataFilename));
+
+ WebDatabase web_database;
+ sql::InitStatus init_status = web_database.Init(profile_path);
+ EXPECT_EQ(sql::INIT_OK, init_status);
+
+ if (init_status == sql::INIT_OK) {
+ std::vector<string16> values;
+ web_database.GetFormValuesForElementName(element_name, L"", &values, 9999);
+ EXPECT_THAT(values, matcher);
+ }
+}
+
+// Launch |ie_mock| and navigate it to |url|.
+ACTION_P2(LaunchThisIEAndNavigate, ie_mock, url) {
+ EXPECT_HRESULT_SUCCEEDED(ie_mock->event_sink()->LaunchIEAndNavigate(url,
+ ie_mock));
+}
+
+TEST_F(DeleteBrowsingHistoryTest, CFDeleteBrowsingHistory) {
+ if (GetInstalledIEVersion() < IE_8) {
+ LOG(ERROR) << "Test does not apply to IE versions < 8.";
+ return;
+ }
+ delete_browsing_history_window_observer_mock_.WatchWindow(
+ "Delete Browsing History");
+
+ // For some reason, this page is occasionally being cached, so we randomize
+ // its name to ensure that, at least the first time we request it, it is
+ // retrieved.
+ std::wstring top_name = RandomChars(32);
+ std::wstring top_url = server_mock_.Resolve(top_name);
+ std::wstring top_path = L"/" + top_name;
+
+ // Even still, it might not be hit the second or third time, so let's just
+ // not worry about how often or whether it's called
+ EXPECT_CALL(server_mock_, Get(_, testing::StrEq(top_path), _))
+ .Times(testing::AnyNumber())
+ .WillRepeatedly(SendFast(kHtmlHttpHeaders, topHtml));
+
+ testing::InSequence expect_in_sequence_for_scope;
+
+ // First launch will hit the server, requesting top.html and then image_path_
+ EXPECT_CALL(server_mock_, Get(_, testing::StrEq(image_path_), _))
+ .WillOnce(SendFast(kBlankPngResponse[0],
+ std::string(kBlankPngResponse[1],
+ kBlankPngFileLength)));
+
+ // top.html contains a form. Fill in the username field and submit, causing
+ // the value to be stored in Chrome's form data DB.
+ EXPECT_CALL(ie_mock_, OnLoad(_, _))
+ .WillOnce(testing::DoAll(
+ AccLeftClickInRenderer(&ie_mock_, AccObjectMatcher(L"username")),
+ PostCharMessagesToRenderer(&ie_mock_, WideToASCII(kFormFieldValue)),
+ AccLeftClickInRenderer(&ie_mock_, AccObjectMatcher(L"Submit"))));
+
+ EXPECT_CALL(server_mock_, Post(_, testing::StrEq(L"/form"), _))
+ .WillOnce(SendFast(kHtmlHttpHeaders, kFormResultHtml));
+
+ // OnLoad of the result page from form submission. Now close the browser.
+ EXPECT_CALL(ie_mock_, OnLoad(_, _))
+ .WillOnce(testing::DoAll(
+ WatchRendererProcess(&ie_process_exit_watcher_mock_, &ie_mock_),
+ CloseBrowserMock(&ie_mock_)));
+
+ EXPECT_CALL(ie_mock_, OnQuit());
+
+ // Wait until the process is gone, so that the Chrome databases are unlocked.
+ // Verify that the submitted username is in the database, then launch a new
+ // IE instance.
+ EXPECT_CALL(ie_process_exit_watcher_mock_, OnObjectSignaled(_))
+ .WillOnce(testing::DoAll(
+ ExpectFormValuesForElementNameMatch(
+ kFormFieldName, testing::Contains(kFormFieldValue)),
+ LaunchThisIEAndNavigate(&ie_mock2_, top_url)));
+
+ // Second launch won't load the image due to the cache.
+
+ // We do the delete private data twice, each time toggling the state of the
+ // 'Delete form data' and 'Delete temporary files' options.
+ // That's because we have no way to know their initial states. Using this,
+ // trick we are guaranteed to run it exactly once with each option turned on.
+ // Running it once with the option turned off is harmless.
+
+ // Proceed to open up the "Safety" menu for the first time through the loop.
+ EXPECT_CALL(ie_mock2_, OnLoad(_, _))
+ .WillOnce(AccDoDefaultActionInBrowser(&ie_mock2_,
+ AccObjectMatcher(L"Safety")));
+
+ // Store the dialog and progress_bar HWNDs for each iteration
+ // in order to distinguish between the OnClose of each.
+ HWND dialog[] = {NULL, NULL};
+ HWND progress_bar[] = {NULL, NULL};
+
+ for (int i = 0; i < 2; ++i) {
+ // Watch for the popup menu, click 'Delete Browsing History...'
+ EXPECT_CALL(acc_observer_, OnMenuPopup(_))
+ .WillOnce(
+ AccLeftClick(AccObjectMatcher(L"Delete Browsing History...*")));
+
+ // When it shows up, toggle the options and click "Delete".
+ EXPECT_CALL(delete_browsing_history_window_observer_mock_, OnWindowOpen(_))
+ .WillOnce(testing::DoAll(
+ testing::SaveArg<0>(&dialog[i]),
+ AccLeftClick(AccObjectMatcher(L"Temporary Internet files")),
+ AccLeftClick(AccObjectMatcher(L"Form data")),
+ AccLeftClick(AccObjectMatcher(L"Delete"))));
+
+ // The configuration dialog closes.
+ // This is not reliably ordered with respect to the following OnWindowOpen.
+ // Specifying 'AnyNumber' of times allows us to disregard it, although we
+ // can't avoid receiving the call.
+ EXPECT_CALL(delete_browsing_history_window_observer_mock_,
+ OnWindowClose(testing::Eq(testing::ByRef(dialog[i]))))
+ .Times(testing::AnyNumber());
+
+ // The progress dialog that pops up has the same caption.
+ EXPECT_CALL(delete_browsing_history_window_observer_mock_,
+ OnWindowOpen(_)).WillOnce(testing::SaveArg<0>(&progress_bar[i]));
+
+ // Watch for it to go away, then either do the "Delete History" again or
+ // close the browser.
+ // In either case, validate the contents of the renderer to ensure that
+ // we didn't cause Chrome to crash.
+ if (i == 0) {
+ EXPECT_CALL(delete_browsing_history_window_observer_mock_,
+ OnWindowClose(testing::Eq(testing::ByRef(progress_bar[i]))))
+ .WillOnce(testing::DoAll(
+ AccExpectInRenderer(&ie_mock2_,
+ AccObjectMatcher(L"Blank image.")),
+ AccDoDefaultActionInBrowser(&ie_mock2_,
+ AccObjectMatcher(L"Safety"))));
+ } else {
+ EXPECT_CALL(delete_browsing_history_window_observer_mock_,
+ OnWindowClose(testing::Eq(testing::ByRef(progress_bar[i]))))
+ .WillOnce(testing::DoAll(
+ AccExpectInRenderer(&ie_mock2_,
+ AccObjectMatcher(L"Blank image.")),
+ WatchRendererProcess(&ie_process_exit_watcher_mock_,
+ &ie_mock2_),
+ CloseBrowserMock(&ie_mock2_)));
+ }
+ }
+
+ EXPECT_CALL(ie_mock2_, OnQuit());
+
+ // When the process is actually exited, and the DB has been released, verify
+ // that the remembered form data is not in the form data DB.
+ EXPECT_CALL(ie_process_exit_watcher_mock_, OnObjectSignaled(_))
+ .WillOnce(testing::DoAll(
+ ExpectFormValuesForElementNameMatch(
+ kFormFieldName, testing::Not(testing::Contains(kFormFieldValue))),
+ LaunchThisIEAndNavigate(&ie_mock3_, top_url)));
+
+ // Now that the cache is cleared, final session should load the image from the
+ // server.
+ EXPECT_CALL(server_mock_, Get(_, testing::StrEq(image_path_), _))
+ .WillOnce(
+ SendFast(kBlankPngResponse[0], std::string(kBlankPngResponse[1],
+ kBlankPngFileLength)));
+
+ EXPECT_CALL(ie_mock3_, OnLoad(_, _))
+ .WillOnce(CloseBrowserMock(&ie_mock3_));
+
+ EXPECT_CALL(ie_mock3_, OnQuit())
+ .WillOnce(QUIT_LOOP(loop_));
+
+ // Start it up. Everything else is triggered as mock actions.
+ ASSERT_HRESULT_SUCCEEDED(
+ ie_mock_.event_sink()->LaunchIEAndNavigate(top_url, &ie_mock_));
+
+ // 3 navigations + 2 invocations of delete browser history == 5
+ loop_.RunFor(kChromeFrameLongNavigationTimeoutInSeconds * 5);
+}
+
+} // namespace chrome_frame_test
diff --git a/chrome_frame/test/mock_ie_event_sink_actions.h b/chrome_frame/test/mock_ie_event_sink_actions.h
index d6d8160..7c9b38b 100644
--- a/chrome_frame/test/mock_ie_event_sink_actions.h
+++ b/chrome_frame/test/mock_ie_event_sink_actions.h
@@ -6,6 +6,7 @@
#define CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_ACTIONS_H_
#include <windows.h>
+#include <string>
#include "base/basictypes.h"
#include "base/platform_thread.h"
@@ -126,28 +127,35 @@ ACTION_P2(AccSetValue, matcher, value) {
}
}
+namespace { // NOLINT
+template<typename R> R AccInWindow(testing::Action<R(HWND)> action, HWND hwnd) {
+ return action.Perform(typename testing::Action<R(HWND)>::ArgumentTuple(hwnd));
+}
+}
+
+ACTION_P2(AccDoDefaultActionInBrowser, mock, matcher) {
+ AccInWindow<void>(AccDoDefaultAction(matcher),
+ mock->event_sink()->GetBrowserWindow());
+}
+
ACTION_P2(AccDoDefaultActionInRenderer, mock, matcher) {
- HWND window = mock->event_sink()->GetRendererWindow();
- scoped_refptr<AccObject> object;
- if (FindAccObjectInWindow(window, matcher, &object)) {
- EXPECT_TRUE(object->DoDefaultAction());
- }
+ AccInWindow<void>(AccDoDefaultAction(matcher),
+ mock->event_sink()->GetRendererWindow());
}
ACTION_P2(AccLeftClickInBrowser, mock, matcher) {
- HWND window = mock->event_sink()->GetBrowserWindow();
- scoped_refptr<AccObject> object;
- if (FindAccObjectInWindow(window, matcher, &object)) {
- EXPECT_TRUE(object->LeftClick());
- }
+ AccInWindow<void>(AccLeftClick(matcher),
+ mock->event_sink()->GetBrowserWindow());
+}
+
+ACTION_P2(AccLeftClickInRenderer, mock, matcher) {
+ AccInWindow<void>(AccLeftClick(matcher),
+ mock->event_sink()->GetRendererWindow());
}
ACTION_P3(AccSetValueInBrowser, mock, matcher, value) {
- HWND window = mock->event_sink()->GetBrowserWindow();
- scoped_refptr<AccObject> object;
- if (FindAccObjectInWindow(window, matcher, &object)) {
- EXPECT_TRUE(object->SetValue(value));
- }
+ AccInWindow<void>(AccSetValue(matcher, value),
+ mock->event_sink()->GetBrowserWindow());
}
ACTION_P2(AccWatchForOneValueChange, observer, matcher) {
@@ -172,8 +180,8 @@ ACTION_P(PostCharMessage, character_code) {
}
ACTION_P2(PostCharMessageToRenderer, mock, character_code) {
- HWND window = mock->event_sink()->GetRendererWindow();
- ::PostMessage(window, WM_CHAR, character_code, 0);
+ AccInWindow<void>(PostCharMessage(character_code),
+ mock->event_sink()->GetRendererWindow());
}
ACTION_P2(PostCharMessagesToRenderer, mock, character_codes) {
@@ -191,7 +199,21 @@ ACTION_P(StopWindowWatching, mock) {
mock->StopWatching();
}
-namespace {
+ACTION_P(WatchWindowProcess, mock_observer) {
+ mock_observer->WatchProcessForHwnd(arg0);
+}
+
+ACTION_P2(WatchBrowserProcess, mock_observer, mock) {
+ AccInWindow<void>(WatchWindowProcess(mock_observer),
+ mock->event_sink()->GetBrowserWindow());
+}
+
+ACTION_P2(WatchRendererProcess, mock_observer, mock) {
+ AccInWindow<void>(WatchWindowProcess(mock_observer),
+ mock->event_sink()->GetRendererWindow());
+}
+
+namespace { // NOLINT
void DoCloseWindowNow(HWND hwnd) {
::PostMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
@@ -218,6 +240,15 @@ ACTION(KillChromeFrameProcesses) {
}
// Verifying actions
+ACTION_P(AccExpect, matcher) {
+ scoped_refptr<AccObject> object;
+ EXPECT_TRUE(FindAccObjectInWindow(arg0, matcher, &object));
+}
+
+ACTION_P2(AccExpectInRenderer, mock, matcher) {
+ AccInWindow<void>(AccExpect(matcher),
+ mock->event_sink()->GetRendererWindow());
+}
ACTION_P(ExpectRendererHasFocus, mock) {
mock->event_sink()->ExpectRendererWindowHasFocus();
diff --git a/chrome_frame/test/mock_ie_event_sink_test.h b/chrome_frame/test/mock_ie_event_sink_test.h
index 4a1e31d..74be974 100644
--- a/chrome_frame/test/mock_ie_event_sink_test.h
+++ b/chrome_frame/test/mock_ie_event_sink_test.h
@@ -8,6 +8,7 @@
#include <atlbase.h>
#include <atlcom.h>
#include <string>
+#include <vector>
#include "base/file_path.h"
#include "base/string_number_conversions.h"
@@ -203,18 +204,55 @@ class MockPropertyNotifySinkListener : public PropertyNotifySinkListener {
ScopedComPtr<IUnknown> event_source_;
};
+// Allows tests to observe when processes exit.
+class MockObjectWatcherDelegate : public base::ObjectWatcher::Delegate {
+ public:
+ // base::ObjectWatcher::Delegate implementation
+ MOCK_METHOD1(OnObjectSignaled, void (HANDLE process_handle)); // NOLINT
+
+ virtual ~MockObjectWatcherDelegate() {
+ // Would be nice to free them when OnObjectSignaled is called, too, but
+ // it doesn't seem worth it.
+ for (std::vector<HANDLE>::iterator it = process_handles_.begin();
+ it != process_handles_.end(); ++it) {
+ ::CloseHandle(*it);
+ }
+ }
+
+ // Registers this instance to watch |process_handle| for termination.
+ void WatchProcess(HANDLE process_handle) {
+ process_handles_.push_back(process_handle);
+ object_watcher_.StartWatching(process_handle, this);
+ }
+
+ // Registers this instance to watch |hwnd|'s owning process for termination.
+ void WatchProcessForHwnd(HWND hwnd) {
+ DWORD pid = 0;
+ ::GetWindowThreadProcessId(hwnd, &pid);
+ EXPECT_TRUE(pid);
+ if (pid != 0) {
+ HANDLE process_handle = ::OpenProcess(SYNCHRONIZE, FALSE, pid);
+ EXPECT_TRUE(process_handle);
+ if (process_handle != NULL) {
+ WatchProcess(process_handle);
+ }
+ }
+ }
+
+ private:
+ std::vector<HANDLE> process_handles_;
+ base::ObjectWatcher object_watcher_;
+};
// Mocks a window observer so that tests can detect new windows.
class MockWindowObserver : public WindowObserver {
public:
- // Override WindowObserver methods.
- MOCK_METHOD2(OnWindowDetected, void (HWND hwnd, // NOLINT
- const std::string& caption));
-
- // Watch for all windows of the given class type.
- void WatchWindow(const wchar_t* window_class) {
- DCHECK(window_class);
- window_watcher_.AddObserver(this, WideToUTF8(window_class));
+ // WindowObserver implementation
+ MOCK_METHOD1(OnWindowOpen, void (HWND hwnd)); // NOLINT
+ MOCK_METHOD1(OnWindowClose, void (HWND hwnd)); // NOLINT
+
+ void WatchWindow(std::string caption_pattern) {
+ window_watcher_.AddObserver(this, caption_pattern);
}
private:
diff --git a/chrome_frame/test/navigation_test.cc b/chrome_frame/test/navigation_test.cc
index 0701a30..62cb876 100644
--- a/chrome_frame/test/navigation_test.cc
+++ b/chrome_frame/test/navigation_test.cc
@@ -287,6 +287,7 @@ TEST_P(FullTabNavigationTest, FLAKY_RestrictedSite) {
return;
}
MockWindowObserver win_observer_mock;
+
ScopedComPtr<IInternetSecurityManager> security_manager;
HRESULT hr = security_manager.CreateInstance(CLSID_InternetSecurityManager);
ASSERT_HRESULT_SUCCEEDED(hr);
@@ -294,25 +295,25 @@ TEST_P(FullTabNavigationTest, FLAKY_RestrictedSite) {
hr = security_manager->SetZoneMapping(URLZONE_UNTRUSTED,
GetTestUrl(L"").c_str(), SZM_CREATE);
- EXPECT_CALL(ie_mock_, OnFileDownload(_, _))
- .Times(testing::AnyNumber());
+ EXPECT_CALL(ie_mock_, OnFileDownload(_, _)).Times(testing::AnyNumber());
server_mock_.ExpectAndServeAnyRequests(GetParam());
ProtocolPatchMethod patch_method = GetPatchMethod();
- const wchar_t* kDialogClass = L"#32770";
const char* kAlertDlgCaption = "Security Alert";
- EXPECT_CALL(ie_mock_, OnBeforeNavigate2(_,
- testing::Field(&VARIANT::bstrVal,
- testing::StrCaseEq(GetSimplePageUrl())), _, _, _, _, _))
- .Times(1)
- .WillOnce(WatchWindow(&win_observer_mock, kDialogClass));
+ EXPECT_CALL(ie_mock_, OnBeforeNavigate2(
+ _,
+ testing::Field(&VARIANT::bstrVal, testing::StrCaseEq(GetSimplePageUrl())),
+ _, _, _, _, _))
+ .Times(1)
+ .WillOnce(WatchWindow(&win_observer_mock, kAlertDlgCaption));
if (patch_method == PATCH_METHOD_INET_PROTOCOL) {
- EXPECT_CALL(ie_mock_, OnBeforeNavigate2(_,
- testing::Field(&VARIANT::bstrVal,
- testing::HasSubstr(L"res://")), _, _, _, _, _))
+ EXPECT_CALL(ie_mock_, OnBeforeNavigate2(
+ _,
+ testing::Field(&VARIANT::bstrVal, testing::HasSubstr(L"res://")),
+ _, _, _, _, _))
.Times(testing::AtMost(1));
}
@@ -320,11 +321,12 @@ TEST_P(FullTabNavigationTest, FLAKY_RestrictedSite) {
testing::Field(&VARIANT::bstrVal, StrEq(GetSimplePageUrl()))))
.Times(testing::AtMost(1));
- EXPECT_CALL(win_observer_mock, OnWindowDetected(_, StrEq(kAlertDlgCaption)))
+ EXPECT_CALL(win_observer_mock, OnWindowOpen(_))
.Times(1)
- .WillOnce(testing::DoAll(
- DoCloseWindow(),
- CloseBrowserMock(&ie_mock_)));
+ .WillOnce(DoCloseWindow());
+ EXPECT_CALL(win_observer_mock, OnWindowClose(_))
+ .Times(1)
+ .WillOnce(CloseBrowserMock(&ie_mock_));
LaunchIEAndNavigate(GetSimplePageUrl());
diff --git a/chrome_frame/test/net/fake_external_tab.cc b/chrome_frame/test/net/fake_external_tab.cc
index b42b13b..59d4f84 100644
--- a/chrome_frame/test/net/fake_external_tab.cc
+++ b/chrome_frame/test/net/fake_external_tab.cc
@@ -105,7 +105,8 @@ class SupplyProxyCredentials : public WindowObserver {
HWND password_;
};
- virtual void OnWindowDetected(HWND hwnd, const std::string& caption);
+ virtual void OnWindowOpen(HWND hwnd);
+ virtual void OnWindowClose(HWND hwnd);
static BOOL CALLBACK EnumChildren(HWND hwnd, LPARAM param);
protected:
@@ -119,12 +120,9 @@ SupplyProxyCredentials::SupplyProxyCredentials(const char* username,
: username_(username), password_(password) {
}
-void SupplyProxyCredentials::OnWindowDetected(HWND hwnd,
- const std::string& caption) {
- // IE's dialog caption (en-US).
- if (caption.compare("Windows Security") != 0)
- return;
+void SupplyProxyCredentials::OnWindowClose(HWND hwnd) { }
+void SupplyProxyCredentials::OnWindowOpen(HWND hwnd) {
DialogProps props = {0};
::EnumChildWindows(hwnd, EnumChildren, reinterpret_cast<LPARAM>(&props));
DCHECK(::IsWindow(props.username_));
@@ -474,8 +472,7 @@ int main(int argc, char** argv) {
WindowWatchdog watchdog;
// See url_request_unittest.cc for these credentials.
SupplyProxyCredentials credentials("user", "secret");
- // Check for a dialog class ("#32770")
- watchdog.AddObserver(&credentials, "#32770");
+ watchdog.AddObserver(&credentials, "Windows Security");
testing::InitGoogleTest(&argc, argv);
FilterDisabledTests();
PluginService::EnableChromePlugins(false);
diff --git a/chrome_frame/test/ui_test.cc b/chrome_frame/test/ui_test.cc
index 54fd0d2..6224d80 100644
--- a/chrome_frame/test/ui_test.cc
+++ b/chrome_frame/test/ui_test.cc
@@ -112,23 +112,23 @@ TEST_P(FullTabUITest, FLAKY_CtrlN) {
// events for New Window, but for Crl+N we don't get any
// OnNewWindowX notifications. :(
MockWindowObserver win_observer_mock;
- const wchar_t* kIEFrameClass = L"IEFrame";
+
+ const char* kNewWindowTitlePattern = "*Internet Explorer*";
EXPECT_CALL(ie_mock_, OnLoad(is_cf, StrEq(GetSimplePageUrl())))
.WillOnce(testing::DoAll(
- WatchWindow(&win_observer_mock, kIEFrameClass),
+ WatchWindow(&win_observer_mock, kNewWindowTitlePattern),
SetFocusToRenderer(&ie_mock_),
DelaySendChar(&loop_, 1000, 'n', simulate_input::CONTROL)));
- // Watch for new window
- const char* kNewWindowTitle = "Internet Explorer";
- EXPECT_CALL(win_observer_mock,
- OnWindowDetected(_, testing::HasSubstr(kNewWindowTitle)))
- .WillOnce(testing::DoAll(
- DoCloseWindow(),
- CloseBrowserMock(&ie_mock_)));
+ // Watch for new window. It appears that the window close message cannot be
+ // reliably delivered immediately upon receipt of the window open event.
+ EXPECT_CALL(win_observer_mock, OnWindowOpen(_))
+ .WillOnce(DelayDoCloseWindow(500));
+
+ EXPECT_CALL(win_observer_mock, OnWindowClose(_))
+ .WillOnce(CloseBrowserMock(&ie_mock_));
LaunchIEAndNavigate(GetSimplePageUrl());
- // TODO(kkania): The new window does not close properly sometimes.
}
// Test that ctrl+r does cause a refresh.
@@ -389,20 +389,20 @@ TEST_F(ContextMenuTest, CFPageInfo) {
InSequence expect_in_sequence_for_scope;
// View page information.
- const wchar_t* kPageInfoWindowClass = L"Chrome_WidgetWin_0";
+ const char* kPageInfoCaption = "Security Information";
EXPECT_CALL(acc_observer_, OnAccDocLoad(_))
.WillOnce(testing::DoAll(
- WatchWindow(&win_observer_mock, kPageInfoWindowClass),
+ WatchWindow(&win_observer_mock, kPageInfoCaption),
OpenContextMenuAsync()));
EXPECT_CALL(acc_observer_, OnMenuPopup(_))
.WillOnce(AccLeftClick(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(
- DoCloseWindow(),
- CloseBrowserMock(&ie_mock_)));
+ EXPECT_CALL(win_observer_mock, OnWindowOpen(_))
+ .WillOnce(DoCloseWindow());
+
+ EXPECT_CALL(win_observer_mock, OnWindowClose(_))
+ .WillOnce(CloseBrowserMock(&ie_mock_));
LaunchIEAndNavigate(GetSimplePageUrl());
}
@@ -413,22 +413,20 @@ TEST_F(ContextMenuTest, CFInspector) {
InSequence expect_in_sequence_for_scope;
// Open developer tools.
- const wchar_t* kPageInfoWindowClass = L"Chrome_WidgetWin_0";
+ // Devtools begins life with "Untitled" caption and it changes
+ // later to the 'Developer Tools - <url> form.
+ const char* kPageInfoCaptionPattern = "Untitled*";
EXPECT_CALL(acc_observer_, OnAccDocLoad(_))
.WillOnce(testing::DoAll(
- WatchWindow(&win_observer_mock, kPageInfoWindowClass),
+ WatchWindow(&win_observer_mock, kPageInfoCaptionPattern),
OpenContextMenuAsync()));
EXPECT_CALL(acc_observer_, OnMenuPopup(_))
.WillOnce(AccLeftClick(AccObjectMatcher(L"Inspect element")));
- // Devtools begins life with "Untitled" caption and it changes
- // later to the 'Developer Tools - <url> form.
- const char* kPageInfoCaption = "Untitled";
- EXPECT_CALL(win_observer_mock,
- OnWindowDetected(_, testing::StartsWith(kPageInfoCaption)))
- .WillOnce(testing::DoAll(
- DelayDoCloseWindow(5000), // wait to catch possible crash
- DelayCloseBrowserMock(&loop_, 5500, &ie_mock_)));
+ EXPECT_CALL(win_observer_mock, OnWindowOpen(_))
+ .WillOnce(DelayDoCloseWindow(5000)); // wait to catch possible crash
+ EXPECT_CALL(win_observer_mock, OnWindowClose(_))
+ .WillOnce(CloseBrowserMock(&ie_mock_));
LaunchIENavigateAndLoop(GetSimplePageUrl(),
kChromeFrameLongNavigationTimeoutInSeconds * 2);
@@ -437,13 +435,14 @@ TEST_F(ContextMenuTest, CFInspector) {
TEST_F(ContextMenuTest, CFSaveAs) {
server_mock_.ExpectAndServeAnyRequests(CFInvocation::MetaTag());
MockWindowObserver win_observer_mock;
+
InSequence expect_in_sequence_for_scope;
// Open 'Save As' dialog.
- const wchar_t* kSaveDlgClass = L"#32770";
+ const char* kSaveDlgCaption = "Save As";
EXPECT_CALL(acc_observer_, OnAccDocLoad(_))
.WillOnce(testing::DoAll(
- WatchWindow(&win_observer_mock, kSaveDlgClass),
+ WatchWindow(&win_observer_mock, kSaveDlgCaption),
OpenContextMenuAsync()));
EXPECT_CALL(acc_observer_, OnMenuPopup(_))
.WillOnce(AccLeftClick(AccObjectMatcher(L"Save as...")));
@@ -454,12 +453,14 @@ TEST_F(ContextMenuTest, CFSaveAs) {
temp_file_path = temp_file_path.ReplaceExtension(L".htm");
ASSERT_TRUE(file_util::DieFileDie(temp_file_path, false));
- EXPECT_CALL(win_observer_mock, OnWindowDetected(_, StrEq("Save As")))
+ EXPECT_CALL(win_observer_mock, OnWindowOpen(_))
.WillOnce(testing::DoAll(
AccSetValue(AccObjectMatcher(L"File name:", L"", L"simple*"),
- temp_file_path.value()),
- AccDoDefaultAction(AccObjectMatcher(L"Save", L"push button")),
- CloseWhenFileSaved(&ie_mock_, temp_file_path, 5000)));
+ temp_file_path.value()),
+ AccDoDefaultAction(AccObjectMatcher(L"Save", L"push button"))));
+
+ EXPECT_CALL(win_observer_mock, OnWindowClose(_))
+ .WillOnce(CloseWhenFileSaved(&ie_mock_, temp_file_path, 5000));
LaunchIENavigateAndLoop(GetSimplePageUrl(),
kChromeFrameLongNavigationTimeoutInSeconds * 2);
diff --git a/chrome_frame/test/win_event_receiver.cc b/chrome_frame/test/win_event_receiver.cc
index e6295cd..c3ae0b3 100644
--- a/chrome_frame/test/win_event_receiver.cc
+++ b/chrome_frame/test/win_event_receiver.cc
@@ -5,6 +5,8 @@
#include "chrome_frame/test/win_event_receiver.h"
#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/object_watcher.h"
#include "base/string_util.h"
#include "chrome_frame/function_stub.h"
@@ -68,18 +70,93 @@ void WinEventReceiver::WinEventHook(WinEventReceiver* me, HWINEVENTHOOK hook,
me->listener_->OnEventReceived(event, hwnd, object_id, child_id);
}
-// WindowWatchdog methods
+// Notifies WindowWatchdog when the process owning a given window exits.
+//
+// If the process terminates before its handle may be obtained, this class will
+// still properly notifyy the WindowWatchdog.
+//
+// Notification is always delivered via a message loop task in the message loop
+// that is active when the instance is constructed.
+class WindowWatchdog::ProcessExitObserver
+ : public base::ObjectWatcher::Delegate {
+ public:
+ // Initiates the process watch. Will always return without notifying the
+ // watchdog.
+ ProcessExitObserver(WindowWatchdog* window_watchdog, HWND hwnd);
+ virtual ~ProcessExitObserver();
+
+ // base::ObjectWatcher::Delegate implementation
+ virtual void OnObjectSignaled(HANDLE process_handle);
+
+ private:
+ WindowWatchdog* window_watchdog_;
+ HANDLE process_handle_;
+ HWND hwnd_;
+
+ ScopedRunnableMethodFactory<ProcessExitObserver> method_task_factory_;
+ base::ObjectWatcher object_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessExitObserver);
+};
+
+WindowWatchdog::ProcessExitObserver::ProcessExitObserver(
+ WindowWatchdog* window_watchdog, HWND hwnd)
+ : window_watchdog_(window_watchdog),
+ process_handle_(NULL),
+ hwnd_(hwnd),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_task_factory_(this)) {
+ DWORD pid = 0;
+ ::GetWindowThreadProcessId(hwnd, &pid);
+ if (pid != 0) {
+ process_handle_ = ::OpenProcess(SYNCHRONIZE, FALSE, pid);
+ }
+
+ if (process_handle_ != NULL) {
+ object_watcher_.StartWatching(process_handle_, this);
+ } else {
+ // Process is gone, so the window must be gone too. Notify our observer!
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ method_task_factory_.NewRunnableMethod(
+ &ProcessExitObserver::OnObjectSignaled, HANDLE(NULL)));
+ }
+}
+
+WindowWatchdog::ProcessExitObserver::~ProcessExitObserver() {
+ if (process_handle_ != NULL) {
+ ::CloseHandle(process_handle_);
+ }
+}
+
+void WindowWatchdog::ProcessExitObserver::OnObjectSignaled(
+ HANDLE process_handle) {
+ window_watchdog_->OnHwndProcessExited(hwnd_);
+}
+
+WindowWatchdog::WindowWatchdog() {}
+
void WindowWatchdog::AddObserver(WindowObserver* observer,
- const std::string& window_class) {
- if (observers_.empty())
- win_event_receiver_.SetListenerForEvent(this, EVENT_OBJECT_SHOW);
+ const std::string& caption_pattern) {
+ if (observers_.empty()) {
+ // SetListenerForEvents takes an event_min and event_max.
+ // EVENT_OBJECT_DESTROY, EVENT_OBJECT_SHOW, and EVENT_OBJECT_HIDE are
+ // consecutive, in that order; hence we supply only DESTROY and HIDE to
+ // denote exactly the required set.
+ win_event_receiver_.SetListenerForEvents(
+ this, EVENT_OBJECT_DESTROY, EVENT_OBJECT_HIDE);
+ }
+
+ ObserverEntry new_entry = {
+ observer,
+ caption_pattern,
+ OpenWindowList() };
- 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();) {
+ for (ObserverEntryList::iterator i = observers_.begin();
+ i != observers_.end(); ) {
i = (observer == i->observer) ? observers_.erase(i) : ++i;
}
@@ -87,31 +164,93 @@ void WindowWatchdog::RemoveObserver(WindowObserver* observer) {
win_event_receiver_.StopReceivingEvents();
}
-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
- if (!(WS_CAPTION & GetWindowLong(hwnd, GWL_STYLE)))
- return;
+std::string WindowWatchdog::GetWindowCaption(HWND hwnd) {
+ std::string caption;
+ int len = ::GetWindowTextLength(hwnd) + 1;
+ ::GetWindowTextA(hwnd, WriteInto(&caption, len), len);
- char class_name[MAX_PATH] = {0};
- ::GetClassNameA(hwnd, class_name, arraysize(class_name));
+ return caption;
+}
+
+void WindowWatchdog::HandleOnOpen(HWND hwnd) {
+ std::string caption = GetWindowCaption(hwnd);
+
+ // Instantiated only if there is at least one interested observer. Each
+ // interested observer will maintain a reference to this object, such that it
+ // is deleted when the last observer disappears.
+ linked_ptr<ProcessExitObserver> process_exit_observer;
+
+ // Identify the interested observers and mark them as watching this HWND for
+ // close.
+ ObserverEntryList interested_observers;
+ for (ObserverEntryList::iterator entry_iter = observers_.begin();
+ entry_iter != observers_.end(); ++entry_iter) {
+ if (MatchPattern(caption, entry_iter->caption_pattern)) {
+ if (process_exit_observer == NULL) {
+ process_exit_observer.reset(new ProcessExitObserver(this, hwnd));
+ }
- ObserverMap interested_observers;
- 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);
+ entry_iter->open_windows.push_back(
+ OpenWindowEntry(hwnd, process_exit_observer));
+
+ interested_observers.push_back(*entry_iter);
}
}
- std::string caption;
- int len = ::GetWindowTextLength(hwnd) + 1;
- ::GetWindowTextA(hwnd, WriteInto(&caption, len), len);
+ // Notify the interested observers in a separate pass in case AddObserver or
+ // RemoveObserver is called as a side-effect of the notification.
+ for (ObserverEntryList::iterator entry_iter = interested_observers.begin();
+ entry_iter != interested_observers.end(); ++entry_iter) {
+ entry_iter->observer->OnWindowOpen(hwnd);
+ }
+}
+
+void WindowWatchdog::HandleOnClose(HWND hwnd) {
+ // Identify the interested observers, reaping OpenWindow entries as
+ // appropriate
+ ObserverEntryList interested_observers;
+ for (ObserverEntryList::iterator entry_iter = observers_.begin();
+ entry_iter != observers_.end(); ++entry_iter) {
+ size_t num_open_windows = entry_iter->open_windows.size();
+
+ OpenWindowList::iterator window_iter = entry_iter->open_windows.begin();
+ while (window_iter != entry_iter->open_windows.end()) {
+ if (hwnd == window_iter->first) {
+ window_iter = entry_iter->open_windows.erase(window_iter);
+ } else {
+ ++window_iter;
+ }
+ }
- for (ObserverMap::iterator i = interested_observers.begin();
- i != interested_observers.end(); i++) {
- i->observer->OnWindowDetected(hwnd, caption);
+ if (num_open_windows != entry_iter->open_windows.size()) {
+ interested_observers.push_back(*entry_iter);
+ }
+ }
+
+ // Notify the interested observers in a separate pass in case AddObserver or
+ // RemoveObserver is called as a side-effect of the notification.
+ for (ObserverEntryList::iterator entry_iter = interested_observers.begin();
+ entry_iter != interested_observers.end(); ++entry_iter) {
+ entry_iter->observer->OnWindowClose(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
+ if (!WS_CAPTION & GetWindowLong(hwnd, GWL_STYLE)) {
+ return;
+ }
+ if (event == EVENT_OBJECT_SHOW) {
+ HandleOnOpen(hwnd);
+ } else {
+ DCHECK(event == EVENT_OBJECT_DESTROY || event == EVENT_OBJECT_HIDE);
+ HandleOnClose(hwnd);
+ }
+}
+
+void WindowWatchdog::OnHwndProcessExited(HWND hwnd) {
+ HandleOnClose(hwnd);
+}
diff --git a/chrome_frame/test/win_event_receiver.h b/chrome_frame/test/win_event_receiver.h
index c330520..d91f268 100644
--- a/chrome_frame/test/win_event_receiver.h
+++ b/chrome_frame/test/win_event_receiver.h
@@ -9,6 +9,10 @@
#include <string>
#include <vector>
+#include <utility>
+
+#include "base/linked_ptr.h"
+#include "base/object_watcher.h"
struct FunctionStub;
@@ -54,39 +58,79 @@ class WinEventReceiver {
FunctionStub* hook_stub_;
};
-// Observes window show events. Used with WindowWatchdog.
+// Receives notifications when a window is opened or closed.
class WindowObserver {
public:
virtual ~WindowObserver() {}
- // Called when a window has been shown.
- virtual void OnWindowDetected(HWND hwnd, const std::string& caption) = 0;
+ virtual void OnWindowOpen(HWND hwnd) = 0;
+ virtual void OnWindowClose(HWND hwnd) = 0;
};
-// Watch a for window to be shown with the given window class name.
-// If found, call the observer interested in it.
+// Notifies observers when windows whose captions match specified patterns
+// open or close. When a window opens, its caption is compared to the patterns
+// associated with each observer. Observers registered with matching patterns
+// are notified of the window's opening and will be notified when the same
+// window is closed (including if the owning process terminates without closing
+// the window).
+//
+// Changes to a window's caption while it is open do not affect the set of
+// observers to be notified when it closes.
+//
+// Observers are not notified of the closing of windows that were already open
+// when they were registered.
+//
+// Observers may call AddObserver and/or RemoveObserver during notifications.
+//
+// Each instance of this class must only be accessed from a single thread, and
+// that thread must be running a message loop.
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|.
+ WindowWatchdog();
+ // Register |observer| to be notified when windows matching |caption_pattern|
+ // are opened or closed. A single observer may be registered multiple times.
+ // If a single window caption matches multiple registrations of a single
+ // observer, the observer will be notified once per matching registration.
+ void AddObserver(WindowObserver* observer,
+ const std::string& caption_pattern);
+
+ // Remove all registrations of |observer|. The |observer| will not be notified
+ // during or after this call.
void RemoveObserver(WindowObserver* observer);
private:
- struct WindowObserverEntry {
+ class ProcessExitObserver;
+
+ // The Delegate object is actually a ProcessExitObserver, but declaring
+ // it as such would require fully declaring the ProcessExitObserver class
+ // here in order for linked_ptr to access its destructor.
+ typedef std::pair<HWND, linked_ptr<base::ObjectWatcher::Delegate> >
+ OpenWindowEntry;
+ typedef std::vector<OpenWindowEntry> OpenWindowList;
+
+ struct ObserverEntry {
WindowObserver* observer;
- std::string window_class;
+ std::string caption_pattern;
+ OpenWindowList open_windows;
};
- typedef std::vector<WindowObserverEntry> ObserverMap;
+ typedef std::vector<ObserverEntry> ObserverEntryList;
- // Overriden from WinEventListener.
- virtual void OnEventReceived(DWORD event, HWND hwnd, LONG object_id,
- LONG child_id);
+ // WinEventListener implementation.
+ virtual void OnEventReceived(
+ DWORD event, HWND hwnd, LONG object_id, LONG child_id);
- ObserverMap observers_;
+ static std::string GetWindowCaption(HWND hwnd);
+
+ void HandleOnOpen(HWND hwnd);
+ void HandleOnClose(HWND hwnd);
+ void OnHwndProcessExited(HWND hwnd);
+
+ ObserverEntryList observers_;
WinEventReceiver win_event_receiver_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowWatchdog);
};
+
+
#endif // CHROME_FRAME_TEST_WIN_EVENT_RECEIVER_H_