1 // Copyright (c) 2012 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.
5 #include "chrome/browser/process_singleton.h"
9 #include "base/base_paths.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/path_service.h"
14 #include "base/process/kill.h"
15 #include "base/process/process_info.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/time/time.h"
20 #include "base/win/metro.h"
21 #include "base/win/registry.h"
22 #include "base/win/scoped_handle.h"
23 #include "base/win/win_util.h"
24 #include "base/win/windows_version.h"
25 #include "chrome/browser/browser_process.h"
26 #include "chrome/browser/browser_process_platform_part.h"
27 #include "chrome/browser/chrome_process_finder_win.h"
28 #include "chrome/browser/metro_utils/metro_chrome_win.h"
29 #include "chrome/browser/shell_integration.h"
30 #include "chrome/browser/ui/simple_message_box.h"
31 #include "chrome/common/chrome_constants.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/chrome_paths_internal.h"
34 #include "chrome/common/chrome_switches.h"
35 #include "chrome/installer/util/wmi.h"
36 #include "content/public/common/result_codes.h"
37 #include "grit/chromium_strings.h"
38 #include "grit/generated_resources.h"
39 #include "net/base/escape.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/gfx/win/hwnd_util.h"
45 const char kLockfile
[] = "lockfile";
47 const int kMetroChromeActivationTimeoutMs
= 3000;
49 // A helper class that acquires the given |mutex| while the AutoLockMutex is in
53 explicit AutoLockMutex(HANDLE mutex
) : mutex_(mutex
) {
54 DWORD result
= ::WaitForSingleObject(mutex_
, INFINITE
);
55 DPCHECK(result
== WAIT_OBJECT_0
) << "Result = " << result
;
59 BOOL released
= ::ReleaseMutex(mutex_
);
65 DISALLOW_COPY_AND_ASSIGN(AutoLockMutex
);
68 // A helper class that releases the given |mutex| while the AutoUnlockMutex is
69 // in scope and immediately re-acquires it when going out of scope.
70 class AutoUnlockMutex
{
72 explicit AutoUnlockMutex(HANDLE mutex
) : mutex_(mutex
) {
73 BOOL released
= ::ReleaseMutex(mutex_
);
78 DWORD result
= ::WaitForSingleObject(mutex_
, INFINITE
);
79 DPCHECK(result
== WAIT_OBJECT_0
) << "Result = " << result
;
84 DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex
);
87 // Checks the visibility of the enumerated window and signals once a visible
88 // window has been found.
89 BOOL CALLBACK
BrowserWindowEnumeration(HWND window
, LPARAM param
) {
90 bool* result
= reinterpret_cast<bool*>(param
);
91 *result
= ::IsWindowVisible(window
) != 0;
92 // Stops enumeration if a visible window has been found.
96 bool ParseCommandLine(const COPYDATASTRUCT
* cds
,
97 CommandLine
* parsed_command_line
,
98 base::FilePath
* current_directory
) {
99 // We should have enough room for the shortest command (min_message_size)
100 // and also be a multiple of wchar_t bytes. The shortest command
101 // possible is L"START\0\0" (empty current directory and command line).
102 static const int min_message_size
= 7;
103 if (cds
->cbData
< min_message_size
* sizeof(wchar_t) ||
104 cds
->cbData
% sizeof(wchar_t) != 0) {
105 LOG(WARNING
) << "Invalid WM_COPYDATA, length = " << cds
->cbData
;
109 // We split the string into 4 parts on NULLs.
111 const std::wstring
msg(static_cast<wchar_t*>(cds
->lpData
),
112 cds
->cbData
/ sizeof(wchar_t));
113 const std::wstring::size_type first_null
= msg
.find_first_of(L
'\0');
114 if (first_null
== 0 || first_null
== std::wstring::npos
) {
115 // no NULL byte, don't know what to do
116 LOG(WARNING
) << "Invalid WM_COPYDATA, length = " << msg
.length() <<
117 ", first null = " << first_null
;
121 // Decode the command, which is everything until the first NULL.
122 if (msg
.substr(0, first_null
) == L
"START") {
123 // Another instance is starting parse the command line & do what it would
125 VLOG(1) << "Handling STARTUP request from another process";
126 const std::wstring::size_type second_null
=
127 msg
.find_first_of(L
'\0', first_null
+ 1);
128 if (second_null
== std::wstring::npos
||
129 first_null
== msg
.length() - 1 || second_null
== msg
.length()) {
130 LOG(WARNING
) << "Invalid format for start command, we need a string in 4 "
131 "parts separated by NULLs";
135 // Get current directory.
136 *current_directory
= base::FilePath(msg
.substr(first_null
+ 1,
137 second_null
- first_null
));
139 const std::wstring::size_type third_null
=
140 msg
.find_first_of(L
'\0', second_null
+ 1);
141 if (third_null
== std::wstring::npos
||
142 third_null
== msg
.length()) {
143 LOG(WARNING
) << "Invalid format for start command, we need a string in 4 "
144 "parts separated by NULLs";
148 const std::wstring cmd_line
=
149 msg
.substr(second_null
+ 1, third_null
- second_null
);
150 *parsed_command_line
= CommandLine::FromString(cmd_line
);
156 bool ProcessLaunchNotification(
157 const ProcessSingleton::NotificationCallback
& notification_callback
,
162 if (message
!= WM_COPYDATA
)
165 // Handle the WM_COPYDATA message from another process.
166 HWND hwnd
= reinterpret_cast<HWND
>(wparam
);
167 const COPYDATASTRUCT
* cds
= reinterpret_cast<COPYDATASTRUCT
*>(lparam
);
169 CommandLine
parsed_command_line(CommandLine::NO_PROGRAM
);
170 base::FilePath current_directory
;
171 if (!ParseCommandLine(cds
, &parsed_command_line
, ¤t_directory
)) {
176 *result
= notification_callback
.Run(parsed_command_line
, current_directory
) ?
181 // Returns true if Chrome needs to be relaunched into Windows 8 immersive mode.
182 // Following conditions apply:-
183 // 1. Windows 8 or greater.
184 // 2. Not in Windows 8 immersive mode.
185 // 3. Chrome is default browser.
186 // 4. Process integrity level is not high.
187 // 5. The profile data directory is the default directory.
188 // 6. Last used mode was immersive/machine is a tablet.
190 // Move this function to a common place as the Windows 8 delegate_execute
191 // handler can possibly use this.
192 bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath
& user_data_dir
) {
193 #if defined(USE_AURA)
194 // Returning false from this function doesn't mean we don't launch immersive
195 // mode in Aura. This function is specifically called in case when we need
196 // to relaunch desktop launched chrome into immersive mode through 'relaunch'
197 // menu. In case of Aura, we will use delegate_execute to do the relaunch.
201 if (base::win::GetVersion() < base::win::VERSION_WIN8
)
204 if (base::win::IsProcessImmersive(base::GetCurrentProcessHandle()))
207 if (ShellIntegration::GetDefaultBrowser() != ShellIntegration::IS_DEFAULT
)
210 base::IntegrityLevel integrity_level
= base::INTEGRITY_UNKNOWN
;
211 base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(),
213 if (integrity_level
== base::HIGH_INTEGRITY
)
216 base::FilePath default_user_data_dir
;
217 if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir
))
220 if (default_user_data_dir
!= user_data_dir
)
223 base::win::RegKey reg_key
;
225 if (reg_key
.Create(HKEY_CURRENT_USER
, chrome::kMetroRegistryPath
,
226 KEY_READ
) == ERROR_SUCCESS
&&
227 reg_key
.ReadValueDW(chrome::kLaunchModeValue
,
228 ®_value
) == ERROR_SUCCESS
) {
229 return reg_value
== 1;
231 return base::win::IsTouchEnabledDevice();
236 // Microsoft's Softricity virtualization breaks the sandbox processes.
237 // So, if we detect the Softricity DLL we use WMI Win32_Process.Create to
238 // break out of the virtualization environment.
239 // http://code.google.com/p/chromium/issues/detail?id=43650
240 bool ProcessSingleton::EscapeVirtualization(
241 const base::FilePath
& user_data_dir
) {
242 if (::GetModuleHandle(L
"sftldr_wow64.dll") ||
243 ::GetModuleHandle(L
"sftldr.dll")) {
245 if (!installer::WMIProcess::Launch(::GetCommandLineW(), &process_id
))
247 is_virtualized_
= true;
248 // The new window was spawned from WMI, and won't be in the foreground.
249 // So, first we sleep while the new chrome.exe instance starts (because
250 // WaitForInputIdle doesn't work here). Then we poll for up to two more
251 // seconds and make the window foreground if we find it (or we give up).
254 for (int tries
= 200; tries
; --tries
) {
255 hwnd
= chrome::FindRunningChromeWindow(user_data_dir
);
257 ::SetForegroundWindow(hwnd
);
267 ProcessSingleton::ProcessSingleton(
268 const base::FilePath
& user_data_dir
,
269 const NotificationCallback
& notification_callback
)
270 : notification_callback_(notification_callback
),
271 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE
),
272 user_data_dir_(user_data_dir
) {
275 ProcessSingleton::~ProcessSingleton() {
276 if (lock_file_
!= INVALID_HANDLE_VALUE
)
277 ::CloseHandle(lock_file_
);
280 // Code roughly based on Mozilla.
281 ProcessSingleton::NotifyResult
ProcessSingleton::NotifyOtherProcess() {
283 return PROCESS_NOTIFIED
; // We already spawned the process in this case.
284 if (lock_file_
== INVALID_HANDLE_VALUE
&& !remote_window_
) {
286 } else if (!remote_window_
) {
290 switch (chrome::AttemptToNotifyRunningChrome(remote_window_
, false)) {
291 case chrome::NOTIFY_SUCCESS
:
292 return PROCESS_NOTIFIED
;
293 case chrome::NOTIFY_FAILED
:
294 remote_window_
= NULL
;
296 case chrome::NOTIFY_WINDOW_HUNG
:
297 remote_window_
= NULL
;
301 DWORD process_id
= 0;
302 DWORD thread_id
= ::GetWindowThreadProcessId(remote_window_
, &process_id
);
303 if (!thread_id
|| !process_id
) {
304 remote_window_
= NULL
;
308 // The window is hung. Scan for every window to find a visible one.
309 bool visible_window
= false;
310 ::EnumThreadWindows(thread_id
,
311 &BrowserWindowEnumeration
,
312 reinterpret_cast<LPARAM
>(&visible_window
));
314 // If there is a visible browser window, ask the user before killing it.
315 if (visible_window
&&
316 chrome::ShowMessageBox(
318 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
),
319 l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE
),
320 chrome::MESSAGE_BOX_TYPE_QUESTION
) == chrome::MESSAGE_BOX_RESULT_NO
) {
321 // The user denied. Quit silently.
322 return PROCESS_NOTIFIED
;
325 // Time to take action. Kill the browser process.
326 base::KillProcessById(process_id
, content::RESULT_CODE_HUNG
, true);
327 remote_window_
= NULL
;
331 ProcessSingleton::NotifyResult
332 ProcessSingleton::NotifyOtherProcessOrCreate() {
333 ProcessSingleton::NotifyResult result
= PROCESS_NONE
;
335 result
= NotifyOtherProcess();
336 if (result
== PROCESS_NONE
)
337 result
= PROFILE_IN_USE
;
339 g_browser_process
->platform_part()->PlatformSpecificCommandLineProcessing(
340 *CommandLine::ForCurrentProcess());
345 // Look for a Chrome instance that uses the same profile directory. If there
346 // isn't one, create a message window with its title set to the profile
348 bool ProcessSingleton::Create() {
349 static const wchar_t kMutexName
[] = L
"Local\\ChromeProcessSingletonStartup!";
350 static const wchar_t kMetroActivationEventName
[] =
351 L
"Local\\ChromeProcessSingletonStartupMetroActivation!";
353 remote_window_
= chrome::FindRunningChromeWindow(user_data_dir_
);
354 if (!remote_window_
&& !EscapeVirtualization(user_data_dir_
)) {
355 // Make sure we will be the one and only process creating the window.
356 // We use a named Mutex since we are protecting against multi-process
357 // access. As documented, it's clearer to NOT request ownership on creation
358 // since it isn't guaranteed we will get it. It is better to create it
359 // without ownership and explicitly get the ownership afterward.
360 base::win::ScopedHandle
only_me(::CreateMutex(NULL
, FALSE
, kMutexName
));
361 DPCHECK(only_me
.IsValid());
363 AutoLockMutex
auto_lock_only_me(only_me
);
365 // We now own the mutex so we are the only process that can create the
366 // window at this time, but we must still check if someone created it
367 // between the time where we looked for it above and the time the mutex
369 remote_window_
= chrome::FindRunningChromeWindow(user_data_dir_
);
372 // In Win8+, a new Chrome process launched in Desktop mode may need to be
373 // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for
374 // heuristics). To accomplish this, the current Chrome activates Metro
375 // Chrome, releases the startup mutex, and waits for metro Chrome to take
376 // the singleton. From that point onward, the command line for this Chrome
377 // process will be sent to Metro Chrome by the usual channels.
378 if (!remote_window_
&& base::win::GetVersion() >= base::win::VERSION_WIN8
&&
379 !base::win::IsMetroProcess()) {
380 // |metro_activation_event| is created right before activating a Metro
381 // Chrome (note that there can only be one Metro Chrome process; by OS
382 // design); all following Desktop processes will then wait for this event
383 // to be signaled by Metro Chrome which will do so as soon as it grabs
384 // this singleton (should any of the waiting processes timeout waiting for
385 // the signal they will try to grab the singleton for themselves which
386 // will result in a forced Desktop Chrome launch in the worst case).
387 base::win::ScopedHandle
metro_activation_event(
388 ::OpenEvent(SYNCHRONIZE
, FALSE
, kMetroActivationEventName
));
389 if (!metro_activation_event
.IsValid() &&
390 ShouldLaunchInWindows8ImmersiveMode(user_data_dir_
)) {
391 // No Metro activation is under way, but the desire is to launch in
392 // Metro mode: activate and rendez-vous with the activated process.
393 metro_activation_event
.Set(
394 ::CreateEvent(NULL
, TRUE
, FALSE
, kMetroActivationEventName
));
395 if (!chrome::ActivateMetroChrome()) {
396 // Failed to launch immersive Chrome, default to launching on Desktop.
397 LOG(ERROR
) << "Failed to launch immersive chrome";
398 metro_activation_event
.Close();
402 if (metro_activation_event
.IsValid()) {
403 // Release |only_me| (to let Metro Chrome grab this singleton) and wait
404 // until the event is signaled (i.e. Metro Chrome was successfully
405 // activated). Ignore timeout waiting for |metro_activation_event|.
407 AutoUnlockMutex
auto_unlock_only_me(only_me
);
409 DWORD result
= ::WaitForSingleObject(metro_activation_event
,
410 kMetroChromeActivationTimeoutMs
);
411 DPCHECK(result
== WAIT_OBJECT_0
|| result
== WAIT_TIMEOUT
)
412 << "Result = " << result
;
415 // Check if this singleton was successfully grabbed by another process
416 // (hopefully Metro Chrome). Failing to do so, this process will grab
417 // the singleton and launch in Desktop mode.
418 remote_window_
= chrome::FindRunningChromeWindow(user_data_dir_
);
422 if (!remote_window_
) {
423 // We have to make sure there is no Chrome instance running on another
424 // machine that uses the same profile.
425 base::FilePath lock_file_path
= user_data_dir_
.AppendASCII(kLockfile
);
426 lock_file_
= ::CreateFile(lock_file_path
.value().c_str(),
431 FILE_ATTRIBUTE_NORMAL
|
432 FILE_FLAG_DELETE_ON_CLOSE
,
434 DWORD error
= ::GetLastError();
435 LOG_IF(WARNING
, lock_file_
!= INVALID_HANDLE_VALUE
&&
436 error
== ERROR_ALREADY_EXISTS
) << "Lock file exists but is writable.";
437 LOG_IF(ERROR
, lock_file_
== INVALID_HANDLE_VALUE
)
438 << "Lock file can not be created! Error code: " << error
;
440 if (lock_file_
!= INVALID_HANDLE_VALUE
) {
441 // Set the window's title to the path of our user data directory so
442 // other Chrome instances can decide if they should forward to us.
443 bool result
= window_
.CreateNamed(
444 base::Bind(&ProcessLaunchNotification
, notification_callback_
),
445 user_data_dir_
.value());
446 CHECK(result
&& window_
.hwnd());
449 if (base::win::GetVersion() >= base::win::VERSION_WIN8
) {
450 // Make sure no one is still waiting on Metro activation whether it
451 // succeeded (i.e., this is the Metro process) or failed.
452 base::win::ScopedHandle
metro_activation_event(
453 ::OpenEvent(EVENT_MODIFY_STATE
, FALSE
, kMetroActivationEventName
));
454 if (metro_activation_event
.IsValid())
455 ::SetEvent(metro_activation_event
);
460 return window_
.hwnd() != NULL
;
463 void ProcessSingleton::Cleanup() {