summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/browser_main.cc4
-rw-r--r--chrome/installer/util/browser_distribution.cc8
-rw-r--r--chrome/installer/util/util_constants.cc3
-rw-r--r--chrome/installer/util/util_constants.h3
-rw-r--r--chrome_frame/bho_loader.cc8
-rw-r--r--chrome_frame/chrome_frame.gyp2
-rw-r--r--chrome_frame/chrome_frame_helper_main.cc113
-rw-r--r--chrome_frame/chrome_frame_helper_util.cc96
-rw-r--r--chrome_frame/chrome_frame_helper_util.h18
-rw-r--r--chrome_frame/chrome_frame_launcher.gyp36
-rw-r--r--chrome_frame/chrome_tab.cc9
-rw-r--r--chrome_frame/registry_watcher.cc101
-rw-r--r--chrome_frame/registry_watcher.h36
-rw-r--r--chrome_frame/test/registry_watcher_unittest.cc72
14 files changed, 483 insertions, 26 deletions
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index 277f514..596fe0e 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -1619,7 +1619,9 @@ int BrowserMain(const MainFunctionParams& parameters) {
// Note this check should only happen here, after all the checks above
// (uninstall, resource bundle initialization, other chrome browser
// processes etc).
- if (CheckMachineLevelInstall())
+ // Do not allow this to occur for Chrome Frame user-to-system handoffs.
+ if (!parsed_command_line.HasSwitch(switches::kChromeFrame) &&
+ CheckMachineLevelInstall())
return ResultCodes::MACHINE_LEVEL_INSTALL_EXISTS;
// Create the TranslateManager singleton.
diff --git a/chrome/installer/util/browser_distribution.cc b/chrome/installer/util/browser_distribution.cc
index 8a1ed71..18dba7e 100644
--- a/chrome/installer/util/browser_distribution.cc
+++ b/chrome/installer/util/browser_distribution.cc
@@ -43,14 +43,6 @@ bool IsChromeFrameModule() {
installer::kChromeFrameDll);
}
-// Returns true if currently running in ceee_broker.exe
-bool IsCeeeBrokerProcess() {
- FilePath exe_path;
- PathService::Get(base::FILE_EXE, &exe_path);
- return FilePath::CompareEqualIgnoreCase(exe_path.BaseName().value(),
- installer::kCeeeBrokerExe);
-}
-
BrowserDistribution::Type GetCurrentDistributionType() {
static BrowserDistribution::Type type =
(MasterPreferences::ForCurrentProcess().install_chrome_frame() ||
diff --git a/chrome/installer/util/util_constants.cc b/chrome/installer/util/util_constants.cc
index bfc1007..63baee9 100644
--- a/chrome/installer/util/util_constants.cc
+++ b/chrome/installer/util/util_constants.cc
@@ -157,9 +157,6 @@ const char kToastResultsKey[] = "toast-results-key";
} // namespace switches
-const wchar_t kCeeeBrokerExe[] = L"ceee_broker.exe";
-const wchar_t kCeeeIeDll[] = L"ceee_ie.dll";
-const wchar_t kCeeeInstallHelperDll[] = L"ceee_installer_helper.dll";
const wchar_t kChromeDll[] = L"chrome.dll";
const wchar_t kChromeExe[] = L"chrome.exe";
const wchar_t kChromeFrameDll[] = L"npchrome_frame.dll";
diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h
index 16cb791..868c4ee 100644
--- a/chrome/installer/util/util_constants.h
+++ b/chrome/installer/util/util_constants.h
@@ -152,9 +152,6 @@ extern const char kExperimentGroup[];
extern const char kToastResultsKey[];
} // namespace switches
-extern const wchar_t kCeeeBrokerExe[];
-extern const wchar_t kCeeeIeDll[];
-extern const wchar_t kCeeeInstallHelperDll[];
extern const wchar_t kChromeDll[];
extern const wchar_t kChromeExe[];
extern const wchar_t kChromeFrameDll[];
diff --git a/chrome_frame/bho_loader.cc b/chrome_frame/bho_loader.cc
index 2cf547d..255bb6c 100644
--- a/chrome_frame/bho_loader.cc
+++ b/chrome_frame/bho_loader.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
@@ -71,6 +71,12 @@ void BHOLoader::OnHookEvent(DWORD event, HWND window) {
UtilGetWebBrowserObjectFromWindow(browser_hwnd, __uuidof(browser),
reinterpret_cast<void**>(&browser));
if (browser) {
+ if (IsSystemLevelChromeFrameInstalled()) {
+ // We're in the right place, but a system-level installation has
+ // appeared. We should leave now.
+ return;
+ }
+
// Figure out if we're already in the property map.
wchar_t bho_clsid_as_string[MAX_PATH] = {0};
StringFromGUID2(CLSID_ChromeFrameBHO, bho_clsid_as_string,
diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp
index 228e102..71d626c 100644
--- a/chrome_frame/chrome_frame.gyp
+++ b/chrome_frame/chrome_frame.gyp
@@ -80,6 +80,7 @@
'../chrome/app/policy/cloud_policy_codegen.gyp:policy',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
+ 'chrome_frame_launcher.gyp:chrome_frame_helper_lib',
'chrome_frame_ie',
'chrome_frame_strings',
'chrome_tab_idl',
@@ -105,6 +106,7 @@
'test/infobar_unittests.cc',
'test/policy_settings_unittest.cc',
'test/ready_mode_unittest.cc',
+ 'test/registry_watcher_unittest.cc',
'test/simulate_input.h',
'test/simulate_input.cc',
'test/urlmon_moniker_tests.h',
diff --git a/chrome_frame/chrome_frame_helper_main.cc b/chrome_frame/chrome_frame_helper_main.cc
index 2f36916..6408ecd 100644
--- a/chrome_frame/chrome_frame_helper_main.cc
+++ b/chrome_frame/chrome_frame_helper_main.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
//
@@ -13,9 +13,15 @@
//
#include <crtdbg.h>
+#include <objbase.h>
+#include <stdlib.h>
#include <windows.h>
+#include "chrome_frame/chrome_frame_helper_util.h"
#include "chrome_frame/crash_server_init.h"
+#include "chrome_frame/registry_watcher.h"
+
+namespace {
// Window class and window names.
const wchar_t kChromeFrameHelperWindowClassName[] =
@@ -23,6 +29,24 @@ const wchar_t kChromeFrameHelperWindowClassName[] =
const wchar_t kChromeFrameHelperWindowName[] =
L"ChromeFrameHelperWindowName";
+const wchar_t kChromeFrameClientStateKey[] =
+ L"Software\\Google\\Update\\ClientState\\"
+ L"{8BA986DA-5100-405E-AA35-86F34A02ACBF}";
+const wchar_t kChromeFrameUninstallCmdExeValue[] = L"UninstallString";
+const wchar_t kChromeFrameUninstallCmdArgsValue[] = L"UninstallArguments";
+
+const wchar_t kBHORegistrationPath[] =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer"
+ L"\\Browser Helper Objects";
+
+const wchar_t kStartupArg[] = L"--startup";
+const wchar_t kForceUninstall[] = L"--force-uninstall";
+
+HWND g_hwnd = NULL;
+const UINT kRegistryWatcherChangeMessage = WM_USER + 42;
+
+} // namespace
+
// Small helper class that assists in loading the DLL that contains code
// to register our event hook callback. Automatically removes the hook and
// unloads the DLL on destruction.
@@ -108,6 +132,8 @@ LRESULT CALLBACK ChromeFrameHelperWndProc(HWND hwnd,
case WM_CREATE:
CloseAllHelperWindowsApartFrom(hwnd);
break;
+ case kRegistryWatcherChangeMessage:
+ // A system level Chrome appeared. Fall through:
case WM_DESTROY:
PostQuitMessage(0);
break;
@@ -133,21 +159,102 @@ HWND RegisterAndCreateWindow(HINSTANCE hinstance) {
return hwnd;
}
+
+// This method runs the user-level Chrome Frame uninstall command. This is
+// intended to allow it to delegate to an existing system-level install.
+bool UninstallUserLevelChromeFrame() {
+ bool success = false;
+ HKEY reg_handle = NULL;
+ wchar_t reg_path_buffer[MAX_PATH] = {0};
+ LONG result = RegOpenKeyEx(HKEY_CURRENT_USER,
+ kChromeFrameClientStateKey,
+ 0,
+ KEY_QUERY_VALUE,
+ &reg_handle);
+ if (result == ERROR_SUCCESS) {
+ wchar_t exe_buffer[MAX_PATH] = {0};
+ wchar_t args_buffer[MAX_PATH] = {0};
+ LONG exe_result = ReadValue(reg_handle,
+ kChromeFrameUninstallCmdExeValue,
+ MAX_PATH,
+ exe_buffer);
+ LONG args_result = ReadValue(reg_handle,
+ kChromeFrameUninstallCmdArgsValue,
+ MAX_PATH,
+ args_buffer);
+ RegCloseKey(reg_handle);
+ reg_handle = NULL;
+
+ if (exe_result == ERROR_SUCCESS && args_result == ERROR_SUCCESS) {
+ STARTUPINFO startup_info = {0};
+ startup_info.cb = sizeof(startup_info);
+ startup_info.dwFlags = STARTF_USESHOWWINDOW;
+ startup_info.wShowWindow = SW_SHOW;
+ PROCESS_INFORMATION process_info = {0};
+
+ // Quote the command string in the args.
+ wchar_t argument_string[MAX_PATH * 3] = {0};
+ int arg_len = _snwprintf(argument_string,
+ _countof(argument_string) - 1,
+ L"\"%s\" %s %s",
+ exe_buffer,
+ args_buffer,
+ kForceUninstall);
+
+ if (arg_len > 0 && CreateProcess(exe_buffer, argument_string,
+ NULL, NULL, FALSE, 0, NULL, NULL,
+ &startup_info, &process_info)) {
+ // Close handles.
+ CloseHandle(process_info.hThread);
+ CloseHandle(process_info.hProcess);
+ success = true;
+ }
+ }
+ }
+
+ return success;
+}
+
+void WaitCallback() {
+ // Check if the Chrome Frame BHO is now in the list of registered BHOs:
+ if (IsBHOLoadingPolicyRegistered()) {
+ PostMessage(g_hwnd, kRegistryWatcherChangeMessage, 0, 0);
+ }
+}
+
int APIENTRY wWinMain(HINSTANCE hinstance, HINSTANCE, wchar_t*, int show_cmd) {
const wchar_t* cmd_line = ::GetCommandLine();
google_breakpad::scoped_ptr<google_breakpad::ExceptionHandler> breakpad(
InitializeCrashReporting(cmd_line));
+ if (IsSystemLevelChromeFrameInstalled()) {
+ // If we're running at startup, check for system-level Chrome Frame
+ // installations. If we have one, time
+ // to bail, also schedule user-level CF to be uninstalled at next logon.
+ const wchar_t* cmd_line = ::GetCommandLine();
+ if (cmd_line && wcsstr(cmd_line, kStartupArg) != NULL) {
+ bool uninstalled = UninstallUserLevelChromeFrame();
+ _ASSERTE(uninstalled);
+ }
+ return 0;
+ }
+
// Create a window with a known class and title just to listen for WM_CLOSE
// messages that will shut us down.
- HWND hwnd = RegisterAndCreateWindow(hinstance);
- _ASSERTE(hwnd);
+ g_hwnd = RegisterAndCreateWindow(hinstance);
+ _ASSERTE(IsWindow(g_hwnd));
// Load the hook dll, and set the event hooks.
HookDllLoader loader;
bool loaded = loader.Load();
_ASSERTE(loaded);
+ // Start up the registry watcher
+ RegistryWatcher registry_watcher(HKEY_LOCAL_MACHINE, kBHORegistrationPath,
+ WaitCallback);
+ bool watching = registry_watcher.StartWatching();
+ _ASSERTE(watching);
+
if (loaded) {
MSG msg;
BOOL ret;
diff --git a/chrome_frame/chrome_frame_helper_util.cc b/chrome_frame/chrome_frame_helper_util.cc
index 87f0efc..333fc96 100644
--- a/chrome_frame/chrome_frame_helper_util.cc
+++ b/chrome_frame/chrome_frame_helper_util.cc
@@ -1,13 +1,27 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome_frame/chrome_frame_helper_util.h"
+#include "chrome_tab.h" // NOLINT
#include <shlwapi.h>
+#include <stdio.h>
+
+namespace {
const wchar_t kGetBrowserMessage[] = L"GetAutomationObject";
+const wchar_t kBHORegistrationPathFmt[] =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer"
+ L"\\Browser Helper Objects\\%s";
+const wchar_t kChromeFrameClientKey[] =
+ L"Software\\Google\\Update\\Clients\\"
+ L"{8BA986DA-5100-405E-AA35-86F34A02ACBF}";
+const wchar_t kGoogleUpdateVersionValue[] = L"pv";
+
+} // namespace
+
bool UtilIsWebBrowserWindow(HWND window_to_check) {
bool is_browser_window = false;
@@ -185,3 +199,83 @@ HWND RecurseFindWindow(HWND parent,
EnumChildWindows(parent, WndEnumProc, reinterpret_cast<LPARAM>(&params));
return params.window_found_;
}
+
+// TODO(robertshield): This is stolen shamelessly from mini_installer.cc.
+// Refactor this before (more) bad things happen.
+LONG ReadValue(HKEY key,
+ const wchar_t* value_name,
+ size_t value_size,
+ wchar_t* value) {
+ DWORD type;
+ DWORD byte_length = static_cast<DWORD>(value_size * sizeof(wchar_t));
+ LONG result = ::RegQueryValueEx(key, value_name, NULL, &type,
+ reinterpret_cast<BYTE*>(value),
+ &byte_length);
+ if (result == ERROR_SUCCESS) {
+ if (type != REG_SZ) {
+ result = ERROR_NOT_SUPPORTED;
+ } else if (byte_length == 0) {
+ *value = L'\0';
+ } else if (value[byte_length/sizeof(wchar_t) - 1] != L'\0') {
+ if ((byte_length / sizeof(wchar_t)) < value_size)
+ value[byte_length / sizeof(wchar_t)] = L'\0';
+ else
+ result = ERROR_MORE_DATA;
+ }
+ }
+ return result;
+}
+
+bool IsBHOLoadingPolicyRegistered() {
+ wchar_t bho_clsid_as_string[MAX_PATH] = {0};
+ int count = StringFromGUID2(CLSID_ChromeFrameBHO, bho_clsid_as_string,
+ ARRAYSIZE(bho_clsid_as_string));
+
+ bool bho_registered = false;
+ if (count > 0) {
+ wchar_t reg_path_buffer[MAX_PATH] = {0};
+ int path_count = _snwprintf(reg_path_buffer,
+ MAX_PATH - 1,
+ kBHORegistrationPathFmt,
+ bho_clsid_as_string);
+
+ if (path_count > 0) {
+ HKEY reg_handle = NULL;
+ LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ reg_path_buffer,
+ 0,
+ KEY_QUERY_VALUE,
+ &reg_handle);
+ if (result == ERROR_SUCCESS) {
+ RegCloseKey(reg_handle);
+ bho_registered = true;
+ }
+ }
+ }
+
+ return bho_registered;
+}
+
+bool IsSystemLevelChromeFrameInstalled() {
+ bool system_level_installed = false;
+ HKEY reg_handle = NULL;
+ LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ kChromeFrameClientKey,
+ 0,
+ KEY_QUERY_VALUE,
+ &reg_handle);
+ if (result == ERROR_SUCCESS) {
+ wchar_t version_buffer[MAX_PATH] = {0};
+ result = ReadValue(reg_handle,
+ kGoogleUpdateVersionValue,
+ MAX_PATH,
+ version_buffer);
+ if (result == ERROR_SUCCESS && version_buffer[0] != L'\0') {
+ system_level_installed = true;
+ }
+ RegCloseKey(reg_handle);
+ }
+
+ return system_level_installed;
+}
+
diff --git a/chrome_frame/chrome_frame_helper_util.h b/chrome_frame/chrome_frame_helper_util.h
index d80b07d..28a1f88 100644
--- a/chrome_frame/chrome_frame_helper_util.h
+++ b/chrome_frame/chrome_frame_helper_util.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
@@ -68,4 +68,20 @@ HWND RecurseFindWindow(HWND parent,
DWORD thread_id_to_match,
DWORD process_id_to_match);
+// Reads |value_size| characters from a value named |value_name| in |key| and
+// places them in the buffer pointed to by |value|. Returns ERROR_SUCCESS on
+// success.
+LONG ReadValue(HKEY key,
+ const wchar_t* value_name,
+ size_t value_size,
+ wchar_t* value);
+
+
+// Returns true if our BHO is registered in the HKLM subkey that IE checks for
+// the list of BHOs to load.
+bool IsBHOLoadingPolicyRegistered();
+
+// Returns true if system-level Chrome Frame is installed.
+bool IsSystemLevelChromeFrameInstalled();
+
#endif // CHROME_FRAME_CHROME_FRAME_HELPER_UTIL_H_
diff --git a/chrome_frame/chrome_frame_launcher.gyp b/chrome_frame/chrome_frame_launcher.gyp
index 01c9f2e..c03f2a4 100644
--- a/chrome_frame/chrome_frame_launcher.gyp
+++ b/chrome_frame/chrome_frame_launcher.gyp
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 The Chromium Authors. All rights reserved.
+# Copyright (c) 2011 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.
@@ -102,6 +102,7 @@
'../chrome/chrome.gyp:chrome_version_header',
'chrome_frame.gyp:chrome_frame_utils',
'chrome_frame_helper_dll',
+ 'chrome_frame_helper_lib',
],
'resource_include_dirs': [
'<(INTERMEDIATE_DIR)',
@@ -131,6 +132,7 @@
'msvs_guid': '5E80032F-7033-4661-9016-D98268244783',
'dependencies': [
'../chrome/chrome.gyp:chrome_version_header',
+ 'chrome_frame_helper_lib',
],
'resource_include_dirs': [
'<(INTERMEDIATE_DIR)',
@@ -146,8 +148,6 @@
'bho_loader.h',
'chrome_frame_helper_dll.cc',
'chrome_frame_helper_dll.def',
- 'chrome_frame_helper_util.cc',
- 'chrome_frame_helper_util.h',
'chrome_frame_helper_version.rc',
'<(SHARED_INTERMEDIATE_DIR)/chrome_tab.h',
'chrome_tab.idl',
@@ -161,6 +161,36 @@
'ProgramDatabaseFile': '$(OutDir)\\chrome_frame_helper_dll.pdb',
# Set /SUBSYSTEM:WINDOWS since this is not a command-line program.
'SubSystem': '2',
+ },
+ },
+ },
+ {
+ 'target_name': 'chrome_frame_helper_lib',
+ 'type': 'static_library',
+ 'msvs_guid': '9984D820-1D28-48A7-957C-2AFA41B416C9',
+ 'dependencies': [
+ '../chrome/chrome.gyp:chrome_version_header',
+ ],
+ 'resource_include_dirs': [
+ '<(INTERMEDIATE_DIR)',
+ '<(SHARED_INTERMEDIATE_DIR)',
+ ],
+ 'include_dirs': [
+ # To allow including "chrome_tab.h"
+ '<(INTERMEDIATE_DIR)',
+ '<(INTERMEDIATE_DIR)/../chrome_frame',
+ ],
+ 'sources': [
+ 'chrome_frame_helper_util.cc',
+ 'chrome_frame_helper_util.h',
+ 'registry_watcher.cc',
+ 'registry_watcher.h',
+ '<(SHARED_INTERMEDIATE_DIR)/chrome_tab.h',
+ 'chrome_tab.idl',
+ 'iids.cc',
+ ],
+ 'msvs_settings': {
+ 'VCLinkerTool': {
'AdditionalDependencies': [
'shlwapi.lib',
],
diff --git a/chrome_frame/chrome_tab.cc b/chrome_frame/chrome_tab.cc
index b7d2898..61f9a18 100644
--- a/chrome_frame/chrome_tab.cc
+++ b/chrome_frame/chrome_tab.cc
@@ -70,6 +70,7 @@ const wchar_t kRunOnce[] =
const wchar_t kRunKeyName[] = L"ChromeFrameHelper";
const wchar_t kChromeFrameHelperExe[] = L"chrome_frame_helper.exe";
+const wchar_t kChromeFrameHelperStartupArg[] = L"--startup";
// Window class and window names.
// TODO(robertshield): These and other constants need to be refactored into
@@ -314,7 +315,6 @@ HRESULT SetupRunOnce() {
// Use this only for the dev channel and CEEE channels.
if (channel_name.find(L"dev") != std::wstring::npos ||
channel_name.find(L"ceee") != std::wstring::npos) {
-
HKEY hive = HKEY_CURRENT_USER;
if (IsSystemProcess()) {
// For system installs, our updates will be running as SYSTEM which
@@ -375,9 +375,14 @@ void SetupUserLevelHelper() {
kChromeFrameHelperWindowName);
if (file_util::PathExists(helper_path)) {
+ std::wstring helper_path_cmd(L"\"");
+ helper_path_cmd += helper_path.value();
+ helper_path_cmd += L"\" ";
+ helper_path_cmd += kChromeFrameHelperStartupArg;
+
// Add new run-at-startup entry.
base::win::AddCommandToAutoRun(HKEY_CURRENT_USER, kRunKeyName,
- helper_path.value());
+ helper_path_cmd);
// Start new instance.
bool launched = base::LaunchApp(helper_path.value(), false, true, NULL);
diff --git a/chrome_frame/registry_watcher.cc b/chrome_frame/registry_watcher.cc
new file mode 100644
index 0000000..dcbf8b9
--- /dev/null
+++ b/chrome_frame/registry_watcher.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 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.
+//
+// A utility class that makes it easy to register for registry change
+// notifications.
+//
+
+#include "chrome_frame/registry_watcher.h"
+
+#include "chrome_frame/chrome_frame_helper_util.h"
+
+namespace {
+const wchar_t kRegistryWatcherEventName[] = L"chrome_registry_watcher_event";
+} // namespace
+
+RegistryWatcher::RegistryWatcher(HKEY hive,
+ const wchar_t* path,
+ NotifyFunc callback)
+ : callback_(callback),
+ wait_event_(NULL),
+ wait_handle_(NULL),
+ stopping_(false) {
+ // Enforce that we can open the given registry path with the KEY_NOTIFY
+ // permission.
+ LONG result = RegOpenKeyEx(hive, path, 0, KEY_NOTIFY, &registry_key_);
+ if (result != ERROR_SUCCESS) {
+ registry_key_ = NULL;
+ }
+}
+
+RegistryWatcher::~RegistryWatcher() {
+ StopWatching();
+ if (registry_key_) {
+ RegCloseKey(registry_key_);
+ registry_key_ = NULL;
+ }
+}
+
+bool RegistryWatcher::StartWatching() {
+ if (!registry_key_ || wait_event_ || !callback_) {
+ return false;
+ }
+
+ bool result = false;
+ wait_event_ = CreateEvent(NULL,
+ FALSE, // Auto-resets
+ FALSE, // Initially non-signalled
+ kRegistryWatcherEventName);
+ if (wait_event_ != NULL) {
+ LONG notify_result = RegNotifyChangeKeyValue(
+ registry_key_,
+ TRUE, // Watch subtree
+ REG_NOTIFY_CHANGE_NAME, // Notifies if a subkey is added.
+ wait_event_,
+ TRUE); // Asynchronous, signal the event when a change occurs.
+
+ if (notify_result == ERROR_SUCCESS) {
+ if (RegisterWaitForSingleObject(&wait_handle_,
+ wait_event_,
+ &RegistryWatcher::WaitCallback,
+ reinterpret_cast<void*>(this),
+ INFINITE,
+ WT_EXECUTEDEFAULT)) {
+ stopping_ = false;
+ result = true;
+ }
+ }
+ }
+
+ // If we're not good to go we don't need to hold onto the event.
+ if (!result && wait_event_) {
+ CloseHandle(wait_event_);
+ wait_event_ = NULL;
+ }
+
+ return result;
+}
+
+void RegistryWatcher::StopWatching() {
+ stopping_ = true;
+ if (wait_handle_) {
+ // Unregister the wait and block until any current handlers have returned.
+ UnregisterWaitEx(wait_handle_, INVALID_HANDLE_VALUE);
+ wait_handle_ = NULL;
+ }
+ if (wait_event_) {
+ CloseHandle(wait_event_);
+ wait_event_ = NULL;
+ }
+}
+
+void CALLBACK RegistryWatcher::WaitCallback(void* param, BOOLEAN wait_fired) {
+ RegistryWatcher* watcher = reinterpret_cast<RegistryWatcher*>(param);
+ if (watcher->stopping_)
+ return;
+
+ if (watcher->callback_) {
+ watcher->callback_();
+ }
+}
diff --git a/chrome_frame/registry_watcher.h b/chrome_frame/registry_watcher.h
new file mode 100644
index 0000000..bb72e82
--- /dev/null
+++ b/chrome_frame/registry_watcher.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011 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.
+//
+// A utility class that makes it easy to register for registry change
+// notifications.
+//
+
+#ifndef CHROME_FRAME_REGISTRY_WATCHER_H_
+#define CHROME_FRAME_REGISTRY_WATCHER_H_
+
+#include <windows.h>
+
+class RegistryWatcher {
+ public:
+ typedef void (*NotifyFunc)();
+ RegistryWatcher(HKEY hive, const wchar_t* path, NotifyFunc callback);
+ ~RegistryWatcher();
+
+ bool StartWatching();
+ void StopWatching();
+
+ private:
+ static void CALLBACK WaitCallback(void* param, BOOLEAN wait_fired);
+
+ HKEY registry_key_;
+
+ HANDLE wait_event_;
+ HANDLE wait_handle_;
+ bool stopping_;
+
+ NotifyFunc callback_;
+};
+
+
+#endif // CHROME_FRAME_REGISTRY_WATCHER_H_
diff --git a/chrome_frame/test/registry_watcher_unittest.cc b/chrome_frame/test/registry_watcher_unittest.cc
new file mode 100644
index 0000000..3476510
--- /dev/null
+++ b/chrome_frame/test/registry_watcher_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome_frame/registry_watcher.h"
+
+#include "base/synchronization/waitable_event.h"
+#include "base/time.h"
+#include "base/win/registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::win::RegKey;
+
+const wchar_t kTestRoot[] = L"CFRegistryWatcherTest";
+const wchar_t kTestWindowClass[] = L"TestWndClass";
+const wchar_t kTestWindowName[] = L"TestWndName";
+
+// Give notifications 30 seconds to stick. Hopefully long enough to avoid
+// flakiness.
+const int64 kWaitSeconds = 30;
+
+class RegistryWatcherUnittest : public testing::Test {
+ public:
+ void SetUp() {
+ // Create a temporary key for testing
+ temp_key_.Open(HKEY_CURRENT_USER, L"", KEY_QUERY_VALUE);
+ temp_key_.DeleteKey(kTestRoot);
+ ASSERT_NE(ERROR_SUCCESS, temp_key_.Open(HKEY_CURRENT_USER,
+ kTestRoot,
+ KEY_READ));
+ ASSERT_EQ(ERROR_SUCCESS,
+ temp_key_.Create(HKEY_CURRENT_USER, kTestRoot, KEY_READ));
+
+ reg_change_count_ = 0;
+ }
+
+ void TearDown() {
+ // Clean up the temporary key
+ temp_key_.Open(HKEY_CURRENT_USER, L"", KEY_QUERY_VALUE);
+ ASSERT_EQ(ERROR_SUCCESS, temp_key_.DeleteKey(kTestRoot));
+ temp_key_.Close();
+
+ reg_change_count_ = 0;
+ }
+
+ static void WaitCallback() {
+ reg_change_count_++;
+ event_.Signal();
+ }
+
+ protected:
+ RegKey temp_key_;
+ static unsigned int reg_change_count_;
+ static base::WaitableEvent event_;
+};
+
+unsigned int RegistryWatcherUnittest::reg_change_count_ = 0;
+base::WaitableEvent RegistryWatcherUnittest::event_(
+ false, // auto reset
+ false); // initially unsignalled
+
+TEST_F(RegistryWatcherUnittest, Basic) {
+ RegistryWatcher watcher(HKEY_CURRENT_USER,
+ kTestRoot,
+ &RegistryWatcherUnittest::WaitCallback);
+ ASSERT_TRUE(watcher.StartWatching());
+ EXPECT_EQ(0, reg_change_count_);
+
+ EXPECT_EQ(ERROR_SUCCESS, temp_key_.CreateKey(L"foo", KEY_ALL_ACCESS));
+ EXPECT_TRUE(event_.TimedWait(base::TimeDelta::FromSeconds(kWaitSeconds)));
+ EXPECT_EQ(1, reg_change_count_);
+}