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