blob: 783c9da1a6260270fecd633ad675d9e16a3125e4 [file] [log] [blame]
[email protected]e3ce40a2012-01-31 03:03:031// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]fc14cef2009-01-27 22:17:292// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]7c47ae3e2009-02-18 00:34:215#include "chrome/browser/process_singleton.h"
[email protected]fc14cef2009-01-27 22:17:296
7#include "base/base_paths.h"
8#include "base/command_line.h"
[email protected]f805fe82010-08-03 22:47:109#include "base/file_path.h"
[email protected]9e9b6e8e2009-12-02 08:45:0110#include "base/path_service.h"
[email protected]fc14cef2009-01-27 22:17:2911#include "base/process_util.h"
[email protected]0f26d7b2011-01-05 19:10:4412#include "base/utf_string_conversions.h"
[email protected]b90d7e802011-01-09 16:32:2013#include "base/win/scoped_handle.h"
[email protected]ecb924c2011-03-17 00:34:0914#include "base/win/wrapped_window_proc.h"
[email protected]b50892c5f2012-05-13 07:34:1415#include "chrome/browser/ui/simple_message_box.h"
[email protected]fc14cef2009-01-27 22:17:2916#include "chrome/common/chrome_constants.h"
[email protected]0a194552011-09-14 17:53:3517#include "chrome/installer/util/wmi.h"
[email protected]b39ef1cb2011-10-25 04:46:5518#include "content/public/common/result_codes.h"
[email protected]34ac8f32009-02-22 23:03:2719#include "grit/chromium_strings.h"
20#include "grit/generated_resources.h"
[email protected]c051a1b2011-01-21 23:30:1721#include "ui/base/l10n/l10n_util.h"
[email protected]e7661062011-01-19 22:16:5322#include "ui/base/win/hwnd_util.h"
[email protected]fc14cef2009-01-27 22:17:2923
24namespace {
25
[email protected]f891fb32009-04-08 00:20:3226// Checks the visibility of the enumerated window and signals once a visible
[email protected]fc14cef2009-01-27 22:17:2927// window has been found.
28BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
29 bool* result = reinterpret_cast<bool*>(param);
30 *result = IsWindowVisible(window) != 0;
31 // Stops enumeration if a visible window has been found.
32 return !*result;
33}
34
[email protected]e3ce40a2012-01-31 03:03:0335// This function thunks to the object's version of the windowproc, taking in
36// consideration that there are several messages being dispatched before
37// WM_NCCREATE which we let windows handle.
38LRESULT CALLBACK ThunkWndProc(HWND hwnd, UINT message,
39 WPARAM wparam, LPARAM lparam) {
40 ProcessSingleton* singleton =
41 reinterpret_cast<ProcessSingleton*>(ui::GetWindowUserData(hwnd));
42 if (message == WM_NCCREATE) {
43 CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
44 singleton = reinterpret_cast<ProcessSingleton*>(cs->lpCreateParams);
45 CHECK(singleton);
46 ui::SetWindowUserData(hwnd, singleton);
47 } else if (!singleton) {
48 return ::DefWindowProc(hwnd, message, wparam, lparam);
49 }
50 return singleton->WndProc(hwnd, message, wparam, lparam);
51}
52
[email protected]edf04b512012-02-23 09:47:4353bool ParseCommandLine(const COPYDATASTRUCT* cds,
54 CommandLine* parsed_command_line,
55 FilePath* current_directory) {
56 // We should have enough room for the shortest command (min_message_size)
57 // and also be a multiple of wchar_t bytes. The shortest command
58 // possible is L"START\0\0" (empty current directory and command line).
59 static const int min_message_size = 7;
60 if (cds->cbData < min_message_size * sizeof(wchar_t) ||
61 cds->cbData % sizeof(wchar_t) != 0) {
62 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
63 return false;
64 }
65
66 // We split the string into 4 parts on NULLs.
67 DCHECK(cds->lpData);
68 const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
69 cds->cbData / sizeof(wchar_t));
70 const std::wstring::size_type first_null = msg.find_first_of(L'\0');
71 if (first_null == 0 || first_null == std::wstring::npos) {
72 // no NULL byte, don't know what to do
73 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
74 ", first null = " << first_null;
75 return false;
76 }
77
78 // Decode the command, which is everything until the first NULL.
79 if (msg.substr(0, first_null) == L"START") {
80 // Another instance is starting parse the command line & do what it would
81 // have done.
82 VLOG(1) << "Handling STARTUP request from another process";
83 const std::wstring::size_type second_null =
84 msg.find_first_of(L'\0', first_null + 1);
85 if (second_null == std::wstring::npos ||
86 first_null == msg.length() - 1 || second_null == msg.length()) {
87 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
88 "parts separated by NULLs";
89 return false;
90 }
91
92 // Get current directory.
93 *current_directory = FilePath(msg.substr(first_null + 1,
94 second_null - first_null));
95
96 const std::wstring::size_type third_null =
97 msg.find_first_of(L'\0', second_null + 1);
98 if (third_null == std::wstring::npos ||
99 third_null == msg.length()) {
100 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
101 "parts separated by NULLs";
102 }
103
104 // Get command line.
105 const std::wstring cmd_line =
106 msg.substr(second_null + 1, third_null - second_null);
107 *parsed_command_line = CommandLine::FromString(cmd_line);
108 return true;
109 }
110 return false;
111}
112
[email protected]fc14cef2009-01-27 22:17:29113} // namespace
114
[email protected]0a194552011-09-14 17:53:35115// Microsoft's Softricity virtualization breaks the sandbox processes.
116// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to
117// break out of the virtualization environment.
118// http://code.google.com/p/chromium/issues/detail?id=43650
119bool ProcessSingleton::EscapeVirtualization(const FilePath& user_data_dir) {
120 if (::GetModuleHandle(L"sftldr_wow64.dll") ||
121 ::GetModuleHandle(L"sftldr.dll")) {
122 int process_id;
123 if (!installer::WMIProcess::Launch(GetCommandLineW(), &process_id))
124 return false;
125 is_virtualized_ = true;
126 // The new window was spawned from WMI, and won't be in the foreground.
127 // So, first we sleep while the new chrome.exe instance starts (because
128 // WaitForInputIdle doesn't work here). Then we poll for up to two more
129 // seconds and make the window foreground if we find it (or we give up).
130 HWND hwnd = 0;
131 ::Sleep(90);
132 for (int tries = 200; tries; --tries) {
133 hwnd = FindWindowEx(HWND_MESSAGE, NULL, chrome::kMessageWindowClass,
134 user_data_dir.value().c_str());
135 if (hwnd) {
136 ::SetForegroundWindow(hwnd);
137 break;
138 }
139 ::Sleep(10);
140 }
141 return true;
142 }
143 return false;
144}
145
[email protected]f891fb32009-04-08 00:20:32146// Look for a Chrome instance that uses the same profile directory.
[email protected]9a182832012-02-10 18:45:58147// If there isn't one, create a message window with its title set to
148// the profile directory path.
[email protected]7c47ae3e2009-02-18 00:34:21149ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir)
[email protected]0a194552011-09-14 17:53:35150 : window_(NULL), locked_(false), foreground_window_(NULL),
151 is_virtualized_(false) {
[email protected]bbef41f02010-03-04 16:16:19152 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
153 chrome::kMessageWindowClass,
[email protected]8a205c02011-02-04 20:41:33154 user_data_dir.value().c_str());
[email protected]0a194552011-09-14 17:53:35155 if (!remote_window_ && !EscapeVirtualization(user_data_dir)) {
[email protected]bbef41f02010-03-04 16:16:19156 // Make sure we will be the one and only process creating the window.
157 // We use a named Mutex since we are protecting against multi-process
158 // access. As documented, it's clearer to NOT request ownership on creation
159 // since it isn't guaranteed we will get it. It is better to create it
160 // without ownership and explicitly get the ownership afterward.
[email protected]e132804b2010-12-22 12:48:25161 std::wstring mutex_name(L"Local\\ChromeProcessSingletonStartup!");
[email protected]b90d7e802011-01-09 16:32:20162 base::win::ScopedHandle only_me(
163 CreateMutex(NULL, FALSE, mutex_name.c_str()));
[email protected]bbef41f02010-03-04 16:16:19164 DCHECK(only_me.Get() != NULL) << "GetLastError = " << GetLastError();
165
166 // This is how we acquire the mutex (as opposed to the initial ownership).
167 DWORD result = WaitForSingleObject(only_me, INFINITE);
168 DCHECK(result == WAIT_OBJECT_0) << "Result = " << result <<
169 "GetLastError = " << GetLastError();
170
171 // We now own the mutex so we are the only process that can create the
172 // window at this time, but we must still check if someone created it
173 // between the time where we looked for it above and the time the mutex
174 // was given to us.
175 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
176 chrome::kMessageWindowClass,
[email protected]8a205c02011-02-04 20:41:33177 user_data_dir.value().c_str());
[email protected]9a182832012-02-10 18:45:58178 if (!remote_window_) {
179 HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc);
180
181 WNDCLASSEX wc = {0};
182 wc.cbSize = sizeof(wc);
183 wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>;
184 wc.hInstance = hinst;
185 wc.lpszClassName = chrome::kMessageWindowClass;
186 ATOM clazz = ::RegisterClassEx(&wc);
187 DCHECK(clazz);
188
[email protected]9a182832012-02-10 18:45:58189 // Set the window's title to the path of our user data directory so other
190 // Chrome instances can decide if they should forward to us or not.
191 window_ = ::CreateWindow(MAKEINTATOM(clazz),
192 user_data_dir.value().c_str(),
193 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this);
194 CHECK(window_);
195 }
[email protected]bbef41f02010-03-04 16:16:19196 BOOL success = ReleaseMutex(only_me);
197 DCHECK(success) << "GetLastError = " << GetLastError();
198 }
[email protected]fc14cef2009-01-27 22:17:29199}
200
[email protected]7c47ae3e2009-02-18 00:34:21201ProcessSingleton::~ProcessSingleton() {
[email protected]a9b36c92012-06-18 08:47:57202 // We need to unregister the window as late as possible so that we can detect
203 // another instance of chrome running. Otherwise we may end up writing out
204 // data while a new chrome is starting up.
205 if (window_) {
206 ::DestroyWindow(window_);
207 ::UnregisterClass(chrome::kMessageWindowClass,
208 base::GetModuleFromAddress(&ThunkWndProc));
209 }
[email protected]fc14cef2009-01-27 22:17:29210}
211
[email protected]9f20a6d02009-08-21 01:18:37212ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
[email protected]0a194552011-09-14 17:53:35213 if (is_virtualized_)
214 return PROCESS_NOTIFIED; // We already spawned the process in this case.
215 else if (!remote_window_)
[email protected]9f20a6d02009-08-21 01:18:37216 return PROCESS_NONE;
[email protected]fc14cef2009-01-27 22:17:29217
218 // Found another window, send our command line to it
219 // format is "START\0<<<current directory>>>\0<<<commandline>>>".
220 std::wstring to_send(L"START\0", 6); // want the NULL in the string.
[email protected]b9696482010-11-30 23:56:18221 FilePath cur_dir;
[email protected]fc14cef2009-01-27 22:17:29222 if (!PathService::Get(base::DIR_CURRENT, &cur_dir))
[email protected]9f20a6d02009-08-21 01:18:37223 return PROCESS_NONE;
[email protected]b9696482010-11-30 23:56:18224 to_send.append(cur_dir.value());
[email protected]fc14cef2009-01-27 22:17:29225 to_send.append(L"\0", 1); // Null separator.
226 to_send.append(GetCommandLineW());
227 to_send.append(L"\0", 1); // Null separator.
228
229 // Allow the current running browser window making itself the foreground
230 // window (otherwise it will just flash in the taskbar).
231 DWORD process_id = 0;
232 DWORD thread_id = GetWindowThreadProcessId(remote_window_, &process_id);
[email protected]0815b6d2009-02-11 00:39:37233 // It is possible that the process owning this window may have died by now.
234 if (!thread_id || !process_id) {
235 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37236 return PROCESS_NONE;
[email protected]0815b6d2009-02-11 00:39:37237 }
238
[email protected]fc14cef2009-01-27 22:17:29239 AllowSetForegroundWindow(process_id);
240
[email protected]fc14cef2009-01-27 22:17:29241 COPYDATASTRUCT cds;
242 cds.dwData = 0;
243 cds.cbData = static_cast<DWORD>((to_send.length() + 1) * sizeof(wchar_t));
244 cds.lpData = const_cast<wchar_t*>(to_send.c_str());
245 DWORD_PTR result = 0;
246 if (SendMessageTimeout(remote_window_,
247 WM_COPYDATA,
248 NULL,
249 reinterpret_cast<LPARAM>(&cds),
250 SMTO_ABORTIFHUNG,
[email protected]8b08cbd2009-08-04 05:34:19251 kTimeoutInSeconds * 1000,
[email protected]fc14cef2009-01-27 22:17:29252 &result)) {
[email protected]0815b6d2009-02-11 00:39:37253 // It is possible that the process owning this window may have died by now.
254 if (!result) {
255 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37256 return PROCESS_NONE;
[email protected]0815b6d2009-02-11 00:39:37257 }
[email protected]9f20a6d02009-08-21 01:18:37258 return PROCESS_NOTIFIED;
[email protected]fc14cef2009-01-27 22:17:29259 }
260
[email protected]0815b6d2009-02-11 00:39:37261 // It is possible that the process owning this window may have died by now.
262 if (!IsWindow(remote_window_)) {
263 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37264 return PROCESS_NONE;
[email protected]0815b6d2009-02-11 00:39:37265 }
266
[email protected]fc14cef2009-01-27 22:17:29267 // The window is hung. Scan for every window to find a visible one.
268 bool visible_window = false;
269 EnumThreadWindows(thread_id,
270 &BrowserWindowEnumeration,
271 reinterpret_cast<LPARAM>(&visible_window));
272
273 // If there is a visible browser window, ask the user before killing it.
[email protected]d33220292012-07-04 01:41:27274 if (visible_window && chrome::ShowMessageBox(NULL,
[email protected]5da155e2012-05-26 16:31:16275 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
276 l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE),
[email protected]d33220292012-07-04 01:41:27277 chrome::MESSAGE_BOX_TYPE_QUESTION) == chrome::MESSAGE_BOX_RESULT_NO) {
[email protected]5da155e2012-05-26 16:31:16278 // The user denied. Quit silently.
279 return PROCESS_NOTIFIED;
[email protected]fc14cef2009-01-27 22:17:29280 }
281
282 // Time to take action. Kill the browser process.
[email protected]1fcfb202011-07-19 19:53:14283 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true);
[email protected]fc14cef2009-01-27 22:17:29284 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37285 return PROCESS_NONE;
[email protected]fc14cef2009-01-27 22:17:29286}
287
[email protected]5d364542012-04-05 07:15:39288ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate(
289 const NotificationCallback& notification_callback) {
[email protected]4a44bc32010-05-28 22:22:44290 NotifyResult result = NotifyOtherProcess();
291 if (result != PROCESS_NONE)
292 return result;
[email protected]5d364542012-04-05 07:15:39293 return Create(notification_callback) ? PROCESS_NONE : PROFILE_IN_USE;
[email protected]4a44bc32010-05-28 22:22:44294}
295
[email protected]9a182832012-02-10 18:45:58296// On Windows, there is no need to call Create() since the message
297// window is created in the constructor but to avoid having more
298// platform specific code in browser_main.cc we tolerate calls to
[email protected]5d364542012-04-05 07:15:39299// Create().
300bool ProcessSingleton::Create(
301 const NotificationCallback& notification_callback) {
[email protected]fc14cef2009-01-27 22:17:29302 DCHECK(!remote_window_);
[email protected]5d364542012-04-05 07:15:39303 DCHECK(notification_callback_.is_null());
304
305 if (window_ != NULL)
306 notification_callback_ = notification_callback;
307
[email protected]9a182832012-02-10 18:45:58308 return window_ != NULL;
[email protected]fc14cef2009-01-27 22:17:29309}
310
[email protected]9f20a6d02009-08-21 01:18:37311void ProcessSingleton::Cleanup() {
[email protected]9f20a6d02009-08-21 01:18:37312}
313
[email protected]7c47ae3e2009-02-18 00:34:21314LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) {
[email protected]175a7a22009-05-03 15:57:53315 // If locked, it means we are not ready to process this message because
[email protected]afd20c022010-06-10 00:48:20316 // we are probably in a first run critical phase.
[email protected]175a7a22009-05-03 15:57:53317 if (locked_) {
[email protected]95259c62011-10-25 23:23:53318#if defined(USE_AURA)
319 NOTIMPLEMENTED();
320#else
[email protected]175a7a22009-05-03 15:57:53321 // Attempt to place ourselves in the foreground / flash the task bar.
[email protected]edf04b512012-02-23 09:47:43322 if (foreground_window_ != NULL && IsWindow(foreground_window_)) {
[email protected]175a7a22009-05-03 15:57:53323 SetForegroundWindow(foreground_window_);
[email protected]edf04b512012-02-23 09:47:43324 } else {
325 // Read the command line and store it. It will be replayed when the
326 // ProcessSingleton becomes unlocked.
327 CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
328 FilePath current_directory;
329 if (ParseCommandLine(cds, &parsed_command_line, &current_directory))
330 saved_startup_messages_.push_back(
331 std::make_pair(parsed_command_line.argv(), current_directory));
332 }
[email protected]95259c62011-10-25 23:23:53333#endif
[email protected]175a7a22009-05-03 15:57:53334 return TRUE;
335 }
336
[email protected]edf04b512012-02-23 09:47:43337 CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
338 FilePath current_directory;
339 if (!ParseCommandLine(cds, &parsed_command_line, &current_directory))
[email protected]fc14cef2009-01-27 22:17:29340 return TRUE;
[email protected]5d364542012-04-05 07:15:39341 return notification_callback_.Run(parsed_command_line, current_directory) ?
342 TRUE : FALSE;
[email protected]fc14cef2009-01-27 22:17:29343}
344
[email protected]e3ce40a2012-01-31 03:03:03345LRESULT ProcessSingleton::WndProc(HWND hwnd, UINT message,
346 WPARAM wparam, LPARAM lparam) {
[email protected]fc14cef2009-01-27 22:17:29347 switch (message) {
348 case WM_COPYDATA:
349 return OnCopyData(reinterpret_cast<HWND>(wparam),
350 reinterpret_cast<COPYDATASTRUCT*>(lparam));
351 default:
352 break;
353 }
354
355 return ::DefWindowProc(hwnd, message, wparam, lparam);
356}