process_singleton_win.cc revision 4a5e2dc747d50c653511c68ccb2cfbfb740bd5a7
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/process_singleton.h" 6 7#include "app/l10n_util.h" 8#include "app/win_util.h" 9#include "base/base_paths.h" 10#include "base/command_line.h" 11#include "base/file_path.h" 12#include "base/path_service.h" 13#include "base/process_util.h" 14#include "base/scoped_handle.h" 15#include "base/win_util.h" 16#include "chrome/browser/browser_process.h" 17#include "chrome/browser/extensions/extensions_startup.h" 18#include "chrome/browser/platform_util.h" 19#include "chrome/browser/profile.h" 20#include "chrome/browser/profile_manager.h" 21#include "chrome/browser/ui/browser_init.h" 22#include "chrome/common/chrome_constants.h" 23#include "chrome/common/chrome_paths.h" 24#include "chrome/common/chrome_switches.h" 25#include "chrome/common/result_codes.h" 26#include "chrome/installer/util/browser_distribution.h" 27#include "grit/chromium_strings.h" 28#include "grit/generated_resources.h" 29 30namespace { 31 32// Checks the visibility of the enumerated window and signals once a visible 33// window has been found. 34BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { 35 bool* result = reinterpret_cast<bool*>(param); 36 *result = IsWindowVisible(window) != 0; 37 // Stops enumeration if a visible window has been found. 38 return !*result; 39} 40 41} // namespace 42 43// Look for a Chrome instance that uses the same profile directory. 44ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir) 45 : window_(NULL), locked_(false), foreground_window_(NULL) { 46 std::wstring user_data_dir_str(user_data_dir.ToWStringHack()); 47 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, 48 chrome::kMessageWindowClass, 49 user_data_dir_str.c_str()); 50 if (!remote_window_) { 51 // Make sure we will be the one and only process creating the window. 52 // We use a named Mutex since we are protecting against multi-process 53 // access. As documented, it's clearer to NOT request ownership on creation 54 // since it isn't guaranteed we will get it. It is better to create it 55 // without ownership and explicitly get the ownership afterward. 56 std::wstring mutex_name(L"Local\\ProcessSingletonStartup!"); 57 mutex_name += BrowserDistribution::GetDistribution()->GetAppGuid(); 58 ScopedHandle only_me(CreateMutex(NULL, FALSE, mutex_name.c_str())); 59 DCHECK(only_me.Get() != NULL) << "GetLastError = " << GetLastError(); 60 61 // This is how we acquire the mutex (as opposed to the initial ownership). 62 DWORD result = WaitForSingleObject(only_me, INFINITE); 63 DCHECK(result == WAIT_OBJECT_0) << "Result = " << result << 64 "GetLastError = " << GetLastError(); 65 66 // We now own the mutex so we are the only process that can create the 67 // window at this time, but we must still check if someone created it 68 // between the time where we looked for it above and the time the mutex 69 // was given to us. 70 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, 71 chrome::kMessageWindowClass, 72 user_data_dir_str.c_str()); 73 if (!remote_window_) 74 Create(); 75 BOOL success = ReleaseMutex(only_me); 76 DCHECK(success) << "GetLastError = " << GetLastError(); 77 } 78} 79 80ProcessSingleton::~ProcessSingleton() { 81 if (window_) { 82 DestroyWindow(window_); 83 UnregisterClass(chrome::kMessageWindowClass, GetModuleHandle(NULL)); 84 } 85} 86 87ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { 88 if (!remote_window_) 89 return PROCESS_NONE; 90 91 // Found another window, send our command line to it 92 // format is "START\0<<<current directory>>>\0<<<commandline>>>". 93 std::wstring to_send(L"START\0", 6); // want the NULL in the string. 94 std::wstring cur_dir; 95 if (!PathService::Get(base::DIR_CURRENT, &cur_dir)) 96 return PROCESS_NONE; 97 to_send.append(cur_dir); 98 to_send.append(L"\0", 1); // Null separator. 99 to_send.append(GetCommandLineW()); 100 to_send.append(L"\0", 1); // Null separator. 101 102 // Allow the current running browser window making itself the foreground 103 // window (otherwise it will just flash in the taskbar). 104 DWORD process_id = 0; 105 DWORD thread_id = GetWindowThreadProcessId(remote_window_, &process_id); 106 // It is possible that the process owning this window may have died by now. 107 if (!thread_id || !process_id) { 108 remote_window_ = NULL; 109 return PROCESS_NONE; 110 } 111 112 AllowSetForegroundWindow(process_id); 113 114 COPYDATASTRUCT cds; 115 cds.dwData = 0; 116 cds.cbData = static_cast<DWORD>((to_send.length() + 1) * sizeof(wchar_t)); 117 cds.lpData = const_cast<wchar_t*>(to_send.c_str()); 118 DWORD_PTR result = 0; 119 if (SendMessageTimeout(remote_window_, 120 WM_COPYDATA, 121 NULL, 122 reinterpret_cast<LPARAM>(&cds), 123 SMTO_ABORTIFHUNG, 124 kTimeoutInSeconds * 1000, 125 &result)) { 126 // It is possible that the process owning this window may have died by now. 127 if (!result) { 128 remote_window_ = NULL; 129 return PROCESS_NONE; 130 } 131 return PROCESS_NOTIFIED; 132 } 133 134 // It is possible that the process owning this window may have died by now. 135 if (!IsWindow(remote_window_)) { 136 remote_window_ = NULL; 137 return PROCESS_NONE; 138 } 139 140 // The window is hung. Scan for every window to find a visible one. 141 bool visible_window = false; 142 EnumThreadWindows(thread_id, 143 &BrowserWindowEnumeration, 144 reinterpret_cast<LPARAM>(&visible_window)); 145 146 // If there is a visible browser window, ask the user before killing it. 147 if (visible_window) { 148 std::wstring text = l10n_util::GetString(IDS_BROWSER_HUNGBROWSER_MESSAGE); 149 std::wstring caption = l10n_util::GetString(IDS_PRODUCT_NAME); 150 if (!platform_util::SimpleYesNoBox(NULL, caption, text)) { 151 // The user denied. Quit silently. 152 return PROCESS_NOTIFIED; 153 } 154 } 155 156 // Time to take action. Kill the browser process. 157 base::KillProcessById(process_id, ResultCodes::HUNG, true); 158 remote_window_ = NULL; 159 return PROCESS_NONE; 160} 161 162ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() { 163 NotifyResult result = NotifyOtherProcess(); 164 if (result != PROCESS_NONE) 165 return result; 166 return Create() ? PROCESS_NONE : PROFILE_IN_USE; 167} 168 169// For windows, there is no need to call Create() since the call is made in 170// the constructor but to avoid having more platform specific code in 171// browser_main.cc we tolerate a second call which will do nothing. 172bool ProcessSingleton::Create() { 173 DCHECK(!remote_window_); 174 if (window_) 175 return true; 176 177 HINSTANCE hinst = GetModuleHandle(NULL); 178 179 WNDCLASSEX wc = {0}; 180 wc.cbSize = sizeof(wc); 181 wc.lpfnWndProc = ProcessSingleton::WndProcStatic; 182 wc.hInstance = hinst; 183 wc.lpszClassName = chrome::kMessageWindowClass; 184 ATOM clazz = RegisterClassEx(&wc); 185 DCHECK(clazz); 186 187 FilePath user_data_dir; 188 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); 189 190 // Set the window's title to the path of our user data directory so other 191 // Chrome instances can decide if they should forward to us or not. 192 window_ = CreateWindow(chrome::kMessageWindowClass, 193 user_data_dir.value().c_str(), 194 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, 0); 195 DCHECK(window_); 196 197 win_util::SetWindowUserData(window_, this); 198 return true; 199} 200 201void ProcessSingleton::Cleanup() { 202} 203 204LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) { 205 // If locked, it means we are not ready to process this message because 206 // we are probably in a first run critical phase. 207 if (locked_) { 208 // Attempt to place ourselves in the foreground / flash the task bar. 209 if (IsWindow(foreground_window_)) 210 SetForegroundWindow(foreground_window_); 211 return TRUE; 212 } 213 214 // Ignore the request if the browser process is already in shutdown path. 215 if (!g_browser_process || g_browser_process->IsShuttingDown()) { 216 LOG(WARNING) << "Not handling WM_COPYDATA as browser is shutting down"; 217 return FALSE; 218 } 219 220 // We should have enough room for the shortest command (min_message_size) 221 // and also be a multiple of wchar_t bytes. The shortest command 222 // possible is L"START\0\0" (empty current directory and command line). 223 static const int min_message_size = 7; 224 if (cds->cbData < min_message_size * sizeof(wchar_t) || 225 cds->cbData % sizeof(wchar_t) != 0) { 226 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData; 227 return TRUE; 228 } 229 230 // We split the string into 4 parts on NULLs. 231 DCHECK(cds->lpData); 232 const std::wstring msg(static_cast<wchar_t*>(cds->lpData), 233 cds->cbData / sizeof(wchar_t)); 234 const std::wstring::size_type first_null = msg.find_first_of(L'\0'); 235 if (first_null == 0 || first_null == std::wstring::npos) { 236 // no NULL byte, don't know what to do 237 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() << 238 ", first null = " << first_null; 239 return TRUE; 240 } 241 242 // Decode the command, which is everything until the first NULL. 243 if (msg.substr(0, first_null) == L"START") { 244 // Another instance is starting parse the command line & do what it would 245 // have done. 246 VLOG(1) << "Handling STARTUP request from another process"; 247 const std::wstring::size_type second_null = 248 msg.find_first_of(L'\0', first_null + 1); 249 if (second_null == std::wstring::npos || 250 first_null == msg.length() - 1 || second_null == msg.length()) { 251 LOG(WARNING) << "Invalid format for start command, we need a string in 4 " 252 "parts separated by NULLs"; 253 return TRUE; 254 } 255 256 // Get current directory. 257 const FilePath cur_dir(msg.substr(first_null + 1, 258 second_null - first_null)); 259 260 const std::wstring::size_type third_null = 261 msg.find_first_of(L'\0', second_null + 1); 262 if (third_null == std::wstring::npos || 263 third_null == msg.length()) { 264 LOG(WARNING) << "Invalid format for start command, we need a string in 4 " 265 "parts separated by NULLs"; 266 } 267 268 // Get command line. 269 const std::wstring cmd_line = 270 msg.substr(second_null + 1, third_null - second_null); 271 272 CommandLine parsed_command_line = CommandLine::FromString(cmd_line); 273 PrefService* prefs = g_browser_process->local_state(); 274 DCHECK(prefs); 275 276 Profile* profile = ProfileManager::GetDefaultProfile(); 277 if (!profile) { 278 // We should only be able to get here if the profile already exists and 279 // has been created. 280 NOTREACHED(); 281 return TRUE; 282 } 283 284 // Handle the --uninstall-extension startup action. This needs to done here 285 // in the process that is running with the target profile, otherwise the 286 // uninstall will fail to unload and remove all components. 287 if (parsed_command_line.HasSwitch(switches::kUninstallExtension)) { 288 extensions_startup::HandleUninstallExtension(parsed_command_line, 289 profile); 290 return TRUE; 291 } 292 293 // Run the browser startup sequence again, with the command line of the 294 // signalling process. 295 BrowserInit::ProcessCommandLine(parsed_command_line, cur_dir, false, 296 profile, NULL); 297 return TRUE; 298 } 299 return TRUE; 300} 301 302LRESULT CALLBACK ProcessSingleton::WndProc(HWND hwnd, UINT message, 303 WPARAM wparam, LPARAM lparam) { 304 switch (message) { 305 case WM_COPYDATA: 306 return OnCopyData(reinterpret_cast<HWND>(wparam), 307 reinterpret_cast<COPYDATASTRUCT*>(lparam)); 308 default: 309 break; 310 } 311 312 return ::DefWindowProc(hwnd, message, wparam, lparam); 313} 314