summaryrefslogtreecommitdiffstats
path: root/chrome_frame
diff options
context:
space:
mode:
authorerikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-28 13:47:13 +0000
committererikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-28 13:47:13 +0000
commitf5d1ea32ea7081621c692e03ae153f74bde105b3 (patch)
tree6f508b877d92d3d3dccab58dc49284434c5b9cd3 /chrome_frame
parent2014db003dcdc6e45aed7ba6d5e51819354ef66a (diff)
downloadchromium_src-f5d1ea32ea7081621c692e03ae153f74bde105b3.zip
chromium_src-f5d1ea32ea7081621c692e03ae153f74bde105b3.tar.gz
chromium_src-f5d1ea32ea7081621c692e03ae153f74bde105b3.tar.bz2
This changelist consists of a test for the Delete Browsing History feature.
The feature was never covered by automated tests, and a recent changelist addressed a bug in the feature (http://codereview.chromium.org/3167040). The test validates the following: 1) That cached files are re-retrieved after the user requests their deletion. 2) That saved form data is deleted after the user requests its deletion. 3) That Chrome Frame tabs do not crash when the user deletes browsing history. BUG=56212 TEST= (1) Run "chrome_frame_tests.exe --gclient_filter=DeleteBro*" (should pass) (2) Run the test, and immediately lock your workstation before it starts. It should still pass. (3) Comment out the call to RemoveBrowsingData in DeleteChromeHistory::OnAutomationServerReady (chrome_frame/delete_chrome_history.cc) and run the test. It fails. (4) Revert changes from 3, comment out the check for low integrity level in DeleteChromeHistory::DeleteBrowsingHistory (same file) and run the test. It fails. Review URL: http://codereview.chromium.org/3365010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@60779 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
-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_