blob: b6e5af7a93869882f5d073624147f89ccd6403b5 [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
[email protected]b1d7844b2012-08-22 22:37:477#include <shellapi.h>
[email protected]6927f2e02012-09-18 00:13:498#include <shobjidl.h>
[email protected]b1d7844b2012-08-22 22:37:479
[email protected]fc14cef2009-01-27 22:17:2910#include "base/base_paths.h"
11#include "base/command_line.h"
[email protected]f805fe82010-08-03 22:47:1012#include "base/file_path.h"
[email protected]9e9b6e8e2009-12-02 08:45:0113#include "base/path_service.h"
[email protected]fc14cef2009-01-27 22:17:2914#include "base/process_util.h"
[email protected]f394bc62012-08-22 21:42:2915#include "base/stringprintf.h"
[email protected]0f26d7b2011-01-05 19:10:4416#include "base/utf_string_conversions.h"
[email protected]f394bc62012-08-22 21:42:2917#include "base/win/metro.h"
[email protected]b3d791c92012-10-05 19:01:2418#include "base/win/registry.h"
[email protected]6927f2e02012-09-18 00:13:4919#include "base/win/scoped_com_initializer.h"
20#include "base/win/scoped_comptr.h"
[email protected]b90d7e802011-01-09 16:32:2021#include "base/win/scoped_handle.h"
[email protected]b3d791c92012-10-05 19:01:2422#include "base/win/win_util.h"
[email protected]6927f2e02012-09-18 00:13:4923#include "base/win/windows_version.h"
[email protected]ecb924c2011-03-17 00:34:0924#include "base/win/wrapped_window_proc.h"
[email protected]e9613b52012-11-27 22:35:1325#include "chrome/browser/browser_process.h"
[email protected]245900672012-10-13 02:27:1126#include "chrome/browser/shell_integration.h"
[email protected]b50892c5f2012-05-13 07:34:1427#include "chrome/browser/ui/simple_message_box.h"
[email protected]fc14cef2009-01-27 22:17:2928#include "chrome/common/chrome_constants.h"
[email protected]b3d791c92012-10-05 19:01:2429#include "chrome/common/chrome_paths.h"
30#include "chrome/common/chrome_paths_internal.h"
[email protected]cfa118a2012-10-10 22:27:5331#include "chrome/common/chrome_switches.h"
[email protected]6927f2e02012-09-18 00:13:4932#include "chrome/installer/util/browser_distribution.h"
[email protected]786799692012-09-26 14:16:4833#include "chrome/installer/util/install_util.h"
[email protected]6927f2e02012-09-18 00:13:4934#include "chrome/installer/util/shell_util.h"
[email protected]0a194552011-09-14 17:53:3535#include "chrome/installer/util/wmi.h"
[email protected]b39ef1cb2011-10-25 04:46:5536#include "content/public/common/result_codes.h"
[email protected]34ac8f32009-02-22 23:03:2737#include "grit/chromium_strings.h"
38#include "grit/generated_resources.h"
[email protected]f394bc62012-08-22 21:42:2939#include "net/base/escape.h"
[email protected]c051a1b2011-01-21 23:30:1740#include "ui/base/l10n/l10n_util.h"
[email protected]e7661062011-01-19 22:16:5341#include "ui/base/win/hwnd_util.h"
[email protected]fc14cef2009-01-27 22:17:2942
43namespace {
44
[email protected]2b7ff5b92012-07-17 22:45:1845const char kLockfile[] = "lockfile";
46
[email protected]f394bc62012-08-22 21:42:2947const char kSearchUrl[] =
48 "http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8";
49
[email protected]14649ecd2012-12-05 01:00:2450const int kMetroChromeActivationTimeoutMs = 3000;
51
52// A helper class that acquires the given |mutex| while the AutoLockMutex is in
53// scope.
54class AutoLockMutex {
55 public:
56 explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) {
57 DWORD result = WaitForSingleObject(mutex_, INFINITE);
58 DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
59 }
60
61 ~AutoLockMutex() {
62 BOOL released = ReleaseMutex(mutex_);
63 DPCHECK(released);
64 }
65
66 private:
67 HANDLE mutex_;
68 DISALLOW_COPY_AND_ASSIGN(AutoLockMutex);
69};
70
71// A helper class that releases the given |mutex| while the AutoUnlockMutex is
72// in scope and immediately re-acquires it when going out of scope.
73class AutoUnlockMutex {
74 public:
75 explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) {
76 BOOL released = ReleaseMutex(mutex_);
77 DPCHECK(released);
78 }
79
80 ~AutoUnlockMutex() {
81 DWORD result = WaitForSingleObject(mutex_, INFINITE);
82 DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
83 }
84
85 private:
86 HANDLE mutex_;
87 DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex);
88};
[email protected]b3d791c92012-10-05 19:01:2489
[email protected]f891fb32009-04-08 00:20:3290// Checks the visibility of the enumerated window and signals once a visible
[email protected]fc14cef2009-01-27 22:17:2991// window has been found.
92BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
93 bool* result = reinterpret_cast<bool*>(param);
94 *result = IsWindowVisible(window) != 0;
95 // Stops enumeration if a visible window has been found.
96 return !*result;
97}
98
[email protected]e3ce40a2012-01-31 03:03:0399// This function thunks to the object's version of the windowproc, taking in
100// consideration that there are several messages being dispatched before
101// WM_NCCREATE which we let windows handle.
102LRESULT CALLBACK ThunkWndProc(HWND hwnd, UINT message,
103 WPARAM wparam, LPARAM lparam) {
104 ProcessSingleton* singleton =
105 reinterpret_cast<ProcessSingleton*>(ui::GetWindowUserData(hwnd));
106 if (message == WM_NCCREATE) {
107 CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
108 singleton = reinterpret_cast<ProcessSingleton*>(cs->lpCreateParams);
109 CHECK(singleton);
110 ui::SetWindowUserData(hwnd, singleton);
111 } else if (!singleton) {
112 return ::DefWindowProc(hwnd, message, wparam, lparam);
113 }
114 return singleton->WndProc(hwnd, message, wparam, lparam);
115}
116
[email protected]edf04b512012-02-23 09:47:43117bool ParseCommandLine(const COPYDATASTRUCT* cds,
118 CommandLine* parsed_command_line,
119 FilePath* current_directory) {
120 // We should have enough room for the shortest command (min_message_size)
121 // and also be a multiple of wchar_t bytes. The shortest command
122 // possible is L"START\0\0" (empty current directory and command line).
123 static const int min_message_size = 7;
124 if (cds->cbData < min_message_size * sizeof(wchar_t) ||
125 cds->cbData % sizeof(wchar_t) != 0) {
126 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
127 return false;
128 }
129
130 // We split the string into 4 parts on NULLs.
131 DCHECK(cds->lpData);
132 const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
133 cds->cbData / sizeof(wchar_t));
134 const std::wstring::size_type first_null = msg.find_first_of(L'\0');
135 if (first_null == 0 || first_null == std::wstring::npos) {
136 // no NULL byte, don't know what to do
137 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
138 ", first null = " << first_null;
139 return false;
140 }
141
142 // Decode the command, which is everything until the first NULL.
143 if (msg.substr(0, first_null) == L"START") {
144 // Another instance is starting parse the command line & do what it would
145 // have done.
146 VLOG(1) << "Handling STARTUP request from another process";
147 const std::wstring::size_type second_null =
148 msg.find_first_of(L'\0', first_null + 1);
149 if (second_null == std::wstring::npos ||
150 first_null == msg.length() - 1 || second_null == msg.length()) {
151 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
152 "parts separated by NULLs";
153 return false;
154 }
155
156 // Get current directory.
157 *current_directory = FilePath(msg.substr(first_null + 1,
158 second_null - first_null));
159
160 const std::wstring::size_type third_null =
161 msg.find_first_of(L'\0', second_null + 1);
162 if (third_null == std::wstring::npos ||
163 third_null == msg.length()) {
164 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
165 "parts separated by NULLs";
166 }
167
168 // Get command line.
169 const std::wstring cmd_line =
170 msg.substr(second_null + 1, third_null - second_null);
171 *parsed_command_line = CommandLine::FromString(cmd_line);
172 return true;
173 }
174 return false;
175}
176
[email protected]6927f2e02012-09-18 00:13:49177bool ActivateMetroChrome() {
178 FilePath chrome_exe;
179 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
180 NOTREACHED() << "Failed to get chrome exe path";
181 return false;
182 }
183 string16 app_id = ShellUtil::GetBrowserModelId(
[email protected]786799692012-09-26 14:16:48184 BrowserDistribution::GetDistribution(),
185 InstallUtil::IsPerUserInstall(chrome_exe.value().c_str()));
[email protected]6927f2e02012-09-18 00:13:49186 if (app_id.empty()) {
187 NOTREACHED() << "Failed to get chrome app user model id.";
188 return false;
189 }
190
191 base::win::ScopedComPtr<IApplicationActivationManager> activation_manager;
192 HRESULT hr = activation_manager.CreateInstance(
193 CLSID_ApplicationActivationManager);
194 if (!activation_manager) {
195 NOTREACHED() << "Failed to cocreate activation manager. Error: " << hr;
196 return false;
197 }
198
199 unsigned long pid = 0;
200 hr = activation_manager->ActivateApplication(app_id.c_str(),
201 L"open",
202 AO_NONE,
203 &pid);
204 if (FAILED(hr)) {
205 NOTREACHED() << "Failed to activate metro chrome. Error: " << hr;
206 return false;
207 }
208 return true;
209}
210
[email protected]b3d791c92012-10-05 19:01:24211// Returns true if Chrome needs to be relaunched into Windows 8 immersive mode.
212// Following conditions apply:-
213// 1. Windows 8 or greater.
214// 2. Not in Windows 8 immersive mode.
[email protected]245900672012-10-13 02:27:11215// 3. Chrome is default browser.
216// 4. Process integrity level is not high.
217// 5. The profile data directory is the default directory.
218// 6. Last used mode was immersive/machine is a tablet.
[email protected]b3d791c92012-10-05 19:01:24219// TODO(ananta)
220// Move this function to a common place as the Windows 8 delegate_execute
221// handler can possibly use this.
222bool ShouldLaunchInWindows8ImmersiveMode(const FilePath& user_data_dir) {
223#if defined(USE_AURA)
224 return false;
225#endif
226
227 if (base::win::GetVersion() < base::win::VERSION_WIN8)
228 return false;
229
230 if (base::win::IsProcessImmersive(base::GetCurrentProcessHandle()))
231 return false;
232
[email protected]245900672012-10-13 02:27:11233 if (!ShellIntegration::IsDefaultBrowser())
234 return false;
235
[email protected]b3d791c92012-10-05 19:01:24236 base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN;
237 base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(),
238 &integrity_level);
239 if (integrity_level == base::HIGH_INTEGRITY)
240 return false;
241
242 FilePath default_user_data_dir;
243 if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir))
244 return false;
245
246 if (default_user_data_dir != user_data_dir)
247 return false;
248
[email protected]e212a3062012-11-14 15:59:07249 base::win::RegKey reg_key;
[email protected]7e416002012-10-13 05:19:21250 DWORD reg_value = 0;
[email protected]e212a3062012-11-14 15:59:07251 if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath,
252 KEY_READ) == ERROR_SUCCESS &&
[email protected]7e416002012-10-13 05:19:21253 reg_key.ReadValueDW(chrome::kLaunchModeValue,
254 &reg_value) == ERROR_SUCCESS) {
[email protected]e212a3062012-11-14 15:59:07255 return reg_value == 1;
[email protected]b3d791c92012-10-05 19:01:24256 }
257 return base::win::IsMachineATablet();
258}
259
[email protected]fc14cef2009-01-27 22:17:29260} // namespace
261
[email protected]0a194552011-09-14 17:53:35262// Microsoft's Softricity virtualization breaks the sandbox processes.
263// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to
264// break out of the virtualization environment.
265// http://code.google.com/p/chromium/issues/detail?id=43650
266bool ProcessSingleton::EscapeVirtualization(const FilePath& user_data_dir) {
267 if (::GetModuleHandle(L"sftldr_wow64.dll") ||
268 ::GetModuleHandle(L"sftldr.dll")) {
269 int process_id;
270 if (!installer::WMIProcess::Launch(GetCommandLineW(), &process_id))
271 return false;
272 is_virtualized_ = true;
273 // The new window was spawned from WMI, and won't be in the foreground.
274 // So, first we sleep while the new chrome.exe instance starts (because
275 // WaitForInputIdle doesn't work here). Then we poll for up to two more
276 // seconds and make the window foreground if we find it (or we give up).
277 HWND hwnd = 0;
278 ::Sleep(90);
279 for (int tries = 200; tries; --tries) {
280 hwnd = FindWindowEx(HWND_MESSAGE, NULL, chrome::kMessageWindowClass,
281 user_data_dir.value().c_str());
282 if (hwnd) {
283 ::SetForegroundWindow(hwnd);
284 break;
285 }
286 ::Sleep(10);
287 }
288 return true;
289 }
290 return false;
291}
292
[email protected]7c47ae3e2009-02-18 00:34:21293ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir)
[email protected]0a194552011-09-14 17:53:35294 : window_(NULL), locked_(false), foreground_window_(NULL),
[email protected]14649ecd2012-12-05 01:00:24295 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE),
296 user_data_dir_(user_data_dir) {
[email protected]fc14cef2009-01-27 22:17:29297}
298
[email protected]7c47ae3e2009-02-18 00:34:21299ProcessSingleton::~ProcessSingleton() {
[email protected]a9b36c92012-06-18 08:47:57300 // We need to unregister the window as late as possible so that we can detect
301 // another instance of chrome running. Otherwise we may end up writing out
302 // data while a new chrome is starting up.
303 if (window_) {
304 ::DestroyWindow(window_);
305 ::UnregisterClass(chrome::kMessageWindowClass,
306 base::GetModuleFromAddress(&ThunkWndProc));
307 }
[email protected]2b7ff5b92012-07-17 22:45:18308 if (lock_file_ != INVALID_HANDLE_VALUE)
309 CloseHandle(lock_file_);
[email protected]fc14cef2009-01-27 22:17:29310}
311
[email protected]14649ecd2012-12-05 01:00:24312// Code roughly based on Mozilla.
[email protected]9f20a6d02009-08-21 01:18:37313ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
[email protected]0a194552011-09-14 17:53:35314 if (is_virtualized_)
315 return PROCESS_NOTIFIED; // We already spawned the process in this case.
[email protected]e9613b52012-11-27 22:35:13316 if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) {
[email protected]2b7ff5b92012-07-17 22:45:18317 return LOCK_ERROR;
[email protected]e9613b52012-11-27 22:35:13318 } else if (!remote_window_) {
319 g_browser_process->PlatformSpecificCommandLineProcessing(
320 *CommandLine::ForCurrentProcess());
[email protected]9f20a6d02009-08-21 01:18:37321 return PROCESS_NONE;
[email protected]e9613b52012-11-27 22:35:13322 }
[email protected]fc14cef2009-01-27 22:17:29323
[email protected]f394bc62012-08-22 21:42:29324 DWORD process_id = 0;
325 DWORD thread_id = GetWindowThreadProcessId(remote_window_, &process_id);
326 // It is possible that the process owning this window may have died by now.
327 if (!thread_id || !process_id) {
328 remote_window_ = NULL;
329 return PROCESS_NONE;
330 }
331
332 if (base::win::IsMetroProcess()) {
333 // Interesting corner case. We are launched as a metro process but we
334 // found another chrome running. Since metro enforces single instance then
335 // the other chrome must be desktop chrome and this must be a search charm
336 // activation. This scenario is unique; other cases should be properly
337 // handled by the delegate_execute which will not activate a second chrome.
338 string16 terms;
339 base::win::MetroLaunchType launch = base::win::GetMetroLaunchParams(&terms);
340 if (launch != base::win::METRO_SEARCH) {
341 LOG(WARNING) << "In metro mode, but and launch is " << launch;
342 } else {
343 std::string query = net::EscapeQueryParamValue(UTF16ToUTF8(terms), true);
344 std::string url = base::StringPrintf(kSearchUrl, query.c_str());
345 SHELLEXECUTEINFOA sei = { sizeof(sei) };
346 sei.fMask = SEE_MASK_FLAG_LOG_USAGE;
347 sei.nShow = SW_SHOWNORMAL;
348 sei.lpFile = url.c_str();
349 OutputDebugStringA(sei.lpFile);
350 sei.lpDirectory = "";
351 ::ShellExecuteExA(&sei);
352 }
353 return PROCESS_NOTIFIED;
354 }
355 // Non-metro mode, send our command line to the other chrome message window.
[email protected]fc14cef2009-01-27 22:17:29356 // format is "START\0<<<current directory>>>\0<<<commandline>>>".
357 std::wstring to_send(L"START\0", 6); // want the NULL in the string.
[email protected]b9696482010-11-30 23:56:18358 FilePath cur_dir;
[email protected]fc14cef2009-01-27 22:17:29359 if (!PathService::Get(base::DIR_CURRENT, &cur_dir))
[email protected]9f20a6d02009-08-21 01:18:37360 return PROCESS_NONE;
[email protected]b9696482010-11-30 23:56:18361 to_send.append(cur_dir.value());
[email protected]fc14cef2009-01-27 22:17:29362 to_send.append(L"\0", 1); // Null separator.
363 to_send.append(GetCommandLineW());
364 to_send.append(L"\0", 1); // Null separator.
365
366 // Allow the current running browser window making itself the foreground
367 // window (otherwise it will just flash in the taskbar).
[email protected]fc14cef2009-01-27 22:17:29368 AllowSetForegroundWindow(process_id);
369
[email protected]fc14cef2009-01-27 22:17:29370 COPYDATASTRUCT cds;
371 cds.dwData = 0;
372 cds.cbData = static_cast<DWORD>((to_send.length() + 1) * sizeof(wchar_t));
373 cds.lpData = const_cast<wchar_t*>(to_send.c_str());
374 DWORD_PTR result = 0;
375 if (SendMessageTimeout(remote_window_,
376 WM_COPYDATA,
377 NULL,
378 reinterpret_cast<LPARAM>(&cds),
379 SMTO_ABORTIFHUNG,
[email protected]8b08cbd2009-08-04 05:34:19380 kTimeoutInSeconds * 1000,
[email protected]fc14cef2009-01-27 22:17:29381 &result)) {
[email protected]0815b6d2009-02-11 00:39:37382 // It is possible that the process owning this window may have died by now.
383 if (!result) {
384 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37385 return PROCESS_NONE;
[email protected]0815b6d2009-02-11 00:39:37386 }
[email protected]6927f2e02012-09-18 00:13:49387
388 base::win::ScopedHandle process_handle;
389 if (base::win::GetVersion() >= base::win::VERSION_WIN8 &&
390 base::OpenProcessHandleWithAccess(
391 process_id, PROCESS_QUERY_INFORMATION,
392 process_handle.Receive())) {
393 if (base::win::IsProcessImmersive(process_handle.Get()))
394 ActivateMetroChrome();
395 }
[email protected]9f20a6d02009-08-21 01:18:37396 return PROCESS_NOTIFIED;
[email protected]fc14cef2009-01-27 22:17:29397 }
398
[email protected]0815b6d2009-02-11 00:39:37399 // It is possible that the process owning this window may have died by now.
400 if (!IsWindow(remote_window_)) {
401 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37402 return PROCESS_NONE;
[email protected]0815b6d2009-02-11 00:39:37403 }
404
[email protected]fc14cef2009-01-27 22:17:29405 // The window is hung. Scan for every window to find a visible one.
406 bool visible_window = false;
407 EnumThreadWindows(thread_id,
408 &BrowserWindowEnumeration,
409 reinterpret_cast<LPARAM>(&visible_window));
410
411 // If there is a visible browser window, ask the user before killing it.
[email protected]d33220292012-07-04 01:41:27412 if (visible_window && chrome::ShowMessageBox(NULL,
[email protected]5da155e2012-05-26 16:31:16413 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
414 l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE),
[email protected]d33220292012-07-04 01:41:27415 chrome::MESSAGE_BOX_TYPE_QUESTION) == chrome::MESSAGE_BOX_RESULT_NO) {
[email protected]5da155e2012-05-26 16:31:16416 // The user denied. Quit silently.
417 return PROCESS_NOTIFIED;
[email protected]fc14cef2009-01-27 22:17:29418 }
419
420 // Time to take action. Kill the browser process.
[email protected]1fcfb202011-07-19 19:53:14421 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true);
[email protected]fc14cef2009-01-27 22:17:29422 remote_window_ = NULL;
[email protected]9f20a6d02009-08-21 01:18:37423 return PROCESS_NONE;
[email protected]fc14cef2009-01-27 22:17:29424}
425
[email protected]5d364542012-04-05 07:15:39426ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate(
427 const NotificationCallback& notification_callback) {
[email protected]14649ecd2012-12-05 01:00:24428 ProcessSingleton::NotifyResult result = PROCESS_NONE;
429 if (!Create(notification_callback)) {
430 result = NotifyOtherProcess();
431 if (result == PROCESS_NONE)
432 result = PROFILE_IN_USE;
433 }
434 return result;
[email protected]4a44bc32010-05-28 22:22:44435}
436
[email protected]14649ecd2012-12-05 01:00:24437// Look for a Chrome instance that uses the same profile directory. If there
438// isn't one, create a message window with its title set to the profile
439// directory path.
[email protected]5d364542012-04-05 07:15:39440bool ProcessSingleton::Create(
441 const NotificationCallback& notification_callback) {
[email protected]5d364542012-04-05 07:15:39442 DCHECK(notification_callback_.is_null());
443
[email protected]14649ecd2012-12-05 01:00:24444 static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!";
445 static const wchar_t kMetroActivationEventName[] =
446 L"Local\\ChromeProcessSingletonStartupMetroActivation!";
447
448 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
449 chrome::kMessageWindowClass,
450 user_data_dir_.value().c_str());
451 if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) {
452 // Make sure we will be the one and only process creating the window.
453 // We use a named Mutex since we are protecting against multi-process
454 // access. As documented, it's clearer to NOT request ownership on creation
455 // since it isn't guaranteed we will get it. It is better to create it
456 // without ownership and explicitly get the ownership afterward.
457 base::win::ScopedHandle only_me(CreateMutex(NULL, FALSE, kMutexName));
458 DPCHECK(only_me.IsValid());
459
460 AutoLockMutex auto_lock_only_me(only_me);
461
462 // We now own the mutex so we are the only process that can create the
463 // window at this time, but we must still check if someone created it
464 // between the time where we looked for it above and the time the mutex
465 // was given to us.
466 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
467 chrome::kMessageWindowClass,
468 user_data_dir_.value().c_str());
469
470
471 // In Win8+, a new Chrome process launched in Desktop mode may need to be
472 // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for
473 // heuristics). To accomplish this, the current Chrome activates Metro
474 // Chrome, releases the startup mutex, and waits for metro Chrome to take
475 // the singleton. From that point onward, the command line for this Chrome
476 // process will be sent to Metro Chrome by the usual channels.
477 if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 &&
478 !base::win::IsMetroProcess()) {
479 // |metro_activation_event| is created right before activating a Metro
480 // Chrome (note that there can only be one Metro Chrome process; by OS
481 // design); all following Desktop processes will then wait for this event
482 // to be signaled by Metro Chrome which will do so as soon as it grabs
483 // this singleton (should any of the waiting processes timeout waiting for
484 // the signal they will try to grab the singleton for themselves which
485 // will result in a forced Desktop Chrome launch in the worst case).
486 base::win::ScopedHandle metro_activation_event(
487 OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName));
488 if (!metro_activation_event.IsValid() &&
489 ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) {
490 // No Metro activation is under way, but the desire is to launch in
491 // Metro mode: activate and rendez-vous with the activated process.
492 metro_activation_event.Set(
493 CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName));
494 if (!ActivateMetroChrome()) {
495 // Failed to launch immersive Chrome, default to launching on Desktop.
496 LOG(ERROR) << "Failed to launch immersive chrome";
497 metro_activation_event.Close();
498 }
499 }
500
501 if (metro_activation_event.IsValid()) {
502 // Release |only_me| (to let Metro Chrome grab this singleton) and wait
503 // until the event is signaled (i.e. Metro Chrome was successfully
504 // activated). Ignore timeout waiting for |metro_activation_event|.
505 {
506 AutoUnlockMutex auto_unlock_only_me(only_me);
507
508 DWORD result = WaitForSingleObject(metro_activation_event,
509 kMetroChromeActivationTimeoutMs);
510 DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT)
511 << "Result = " << result;
512 }
513
514 // Check if this singleton was successfully grabbed by another process
515 // (hopefully Metro Chrome). Failing to do so, this process will grab
516 // the singleton and launch in Desktop mode.
517 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
518 chrome::kMessageWindowClass,
519 user_data_dir_.value().c_str());
520 }
521 }
522
523 if (!remote_window_) {
524 // We have to make sure there is no Chrome instance running on another
525 // machine that uses the same profile.
526 FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile);
527 lock_file_ = CreateFile(lock_file_path.value().c_str(),
528 GENERIC_WRITE,
529 FILE_SHARE_READ,
530 NULL,
531 CREATE_ALWAYS,
532 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
533 NULL);
534 DWORD error = GetLastError();
535 LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
536 error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable.";
537 LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
538 << "Lock file can not be created! Error code: " << error;
539
540 if (lock_file_ != INVALID_HANDLE_VALUE) {
541 HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc);
542
543 WNDCLASSEX wc = {0};
544 wc.cbSize = sizeof(wc);
545 wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>;
546 wc.hInstance = hinst;
547 wc.lpszClassName = chrome::kMessageWindowClass;
548 ATOM clazz = ::RegisterClassEx(&wc);
549 DCHECK(clazz);
550
551 // Set the window's title to the path of our user data directory so
552 // other Chrome instances can decide if they should forward to us.
553 window_ = ::CreateWindow(MAKEINTATOM(clazz),
554 user_data_dir_.value().c_str(),
555 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this);
556 CHECK(window_);
557 }
558
559 if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
560 // Make sure no one is still waiting on Metro activation whether it
561 // succeeded (i.e., this is the Metro process) or failed.
562 base::win::ScopedHandle metro_activation_event(
563 OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName));
564 if (metro_activation_event.IsValid())
565 SetEvent(metro_activation_event);
566 }
567 }
568 }
569
[email protected]5d364542012-04-05 07:15:39570 if (window_ != NULL)
571 notification_callback_ = notification_callback;
572
[email protected]9a182832012-02-10 18:45:58573 return window_ != NULL;
[email protected]fc14cef2009-01-27 22:17:29574}
575
[email protected]9f20a6d02009-08-21 01:18:37576void ProcessSingleton::Cleanup() {
577}
578
[email protected]7c47ae3e2009-02-18 00:34:21579LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) {
[email protected]175a7a22009-05-03 15:57:53580 // If locked, it means we are not ready to process this message because
[email protected]afd20c022010-06-10 00:48:20581 // we are probably in a first run critical phase.
[email protected]175a7a22009-05-03 15:57:53582 if (locked_) {
[email protected]95259c62011-10-25 23:23:53583#if defined(USE_AURA)
584 NOTIMPLEMENTED();
585#else
[email protected]175a7a22009-05-03 15:57:53586 // Attempt to place ourselves in the foreground / flash the task bar.
[email protected]edf04b512012-02-23 09:47:43587 if (foreground_window_ != NULL && IsWindow(foreground_window_)) {
[email protected]175a7a22009-05-03 15:57:53588 SetForegroundWindow(foreground_window_);
[email protected]edf04b512012-02-23 09:47:43589 } else {
590 // Read the command line and store it. It will be replayed when the
591 // ProcessSingleton becomes unlocked.
592 CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
593 FilePath current_directory;
594 if (ParseCommandLine(cds, &parsed_command_line, &current_directory))
595 saved_startup_messages_.push_back(
596 std::make_pair(parsed_command_line.argv(), current_directory));
597 }
[email protected]95259c62011-10-25 23:23:53598#endif
[email protected]175a7a22009-05-03 15:57:53599 return TRUE;
600 }
601
[email protected]edf04b512012-02-23 09:47:43602 CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
603 FilePath current_directory;
604 if (!ParseCommandLine(cds, &parsed_command_line, &current_directory))
[email protected]fc14cef2009-01-27 22:17:29605 return TRUE;
[email protected]5d364542012-04-05 07:15:39606 return notification_callback_.Run(parsed_command_line, current_directory) ?
607 TRUE : FALSE;
[email protected]fc14cef2009-01-27 22:17:29608}
609
[email protected]e3ce40a2012-01-31 03:03:03610LRESULT ProcessSingleton::WndProc(HWND hwnd, UINT message,
611 WPARAM wparam, LPARAM lparam) {
[email protected]fc14cef2009-01-27 22:17:29612 switch (message) {
613 case WM_COPYDATA:
614 return OnCopyData(reinterpret_cast<HWND>(wparam),
615 reinterpret_cast<COPYDATASTRUCT*>(lparam));
616 default:
617 break;
618 }
619
620 return ::DefWindowProc(hwnd, message, wparam, lparam);
621}