diff options
-rw-r--r-- | chrome/browser/browser_main.cc | 4 | ||||
-rw-r--r-- | chrome/installer/util/browser_distribution.cc | 8 | ||||
-rw-r--r-- | chrome/installer/util/util_constants.cc | 3 | ||||
-rw-r--r-- | chrome/installer/util/util_constants.h | 3 | ||||
-rw-r--r-- | chrome_frame/bho_loader.cc | 8 | ||||
-rw-r--r-- | chrome_frame/chrome_frame.gyp | 2 | ||||
-rw-r--r-- | chrome_frame/chrome_frame_helper_main.cc | 113 | ||||
-rw-r--r-- | chrome_frame/chrome_frame_helper_util.cc | 96 | ||||
-rw-r--r-- | chrome_frame/chrome_frame_helper_util.h | 18 | ||||
-rw-r--r-- | chrome_frame/chrome_frame_launcher.gyp | 36 | ||||
-rw-r--r-- | chrome_frame/chrome_tab.cc | 9 | ||||
-rw-r--r-- | chrome_frame/registry_watcher.cc | 101 | ||||
-rw-r--r-- | chrome_frame/registry_watcher.h | 36 | ||||
-rw-r--r-- | chrome_frame/test/registry_watcher_unittest.cc | 72 |
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, + ®_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>(¶ms)); 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, + ®_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, + ®_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, ®istry_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_); +} |