Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / process_singleton_win.cc
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.
4
5 #include "chrome/browser/process_singleton.h"
6
7 #include <shellapi.h>
8
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/windows_version.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/browser_process_platform_part.h"
26 #include "chrome/browser/chrome_process_finder_win.h"
27 #include "chrome/browser/metro_utils/metro_chrome_win.h"
28 #include "chrome/browser/shell_integration.h"
29 #include "chrome/browser/ui/simple_message_box.h"
30 #include "chrome/common/chrome_constants.h"
31 #include "chrome/common/chrome_paths.h"
32 #include "chrome/common/chrome_paths_internal.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "chrome/installer/util/wmi.h"
35 #include "content/public/common/result_codes.h"
36 #include "grit/chromium_strings.h"
37 #include "grit/generated_resources.h"
38 #include "net/base/escape.h"
39 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/gfx/win/hwnd_util.h"
41
42 namespace {
43
44 const char kLockfile[] = "lockfile";
45
46 const int kMetroChromeActivationTimeoutMs = 3000;
47
48 // A helper class that acquires the given |mutex| while the AutoLockMutex is in
49 // scope.
50 class AutoLockMutex {
51  public:
52   explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) {
53     DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
54     DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
55   }
56
57   ~AutoLockMutex() {
58     BOOL released = ::ReleaseMutex(mutex_);
59     DPCHECK(released);
60   }
61
62  private:
63   HANDLE mutex_;
64   DISALLOW_COPY_AND_ASSIGN(AutoLockMutex);
65 };
66
67 // A helper class that releases the given |mutex| while the AutoUnlockMutex is
68 // in scope and immediately re-acquires it when going out of scope.
69 class AutoUnlockMutex {
70  public:
71   explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) {
72     BOOL released = ::ReleaseMutex(mutex_);
73     DPCHECK(released);
74   }
75
76   ~AutoUnlockMutex() {
77     DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
78     DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
79   }
80
81  private:
82   HANDLE mutex_;
83   DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex);
84 };
85
86 // Checks the visibility of the enumerated window and signals once a visible
87 // window has been found.
88 BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
89   bool* result = reinterpret_cast<bool*>(param);
90   *result = ::IsWindowVisible(window) != 0;
91   // Stops enumeration if a visible window has been found.
92   return !*result;
93 }
94
95 bool ParseCommandLine(const COPYDATASTRUCT* cds,
96                       CommandLine* parsed_command_line,
97                       base::FilePath* current_directory) {
98   // We should have enough room for the shortest command (min_message_size)
99   // and also be a multiple of wchar_t bytes. The shortest command
100   // possible is L"START\0\0" (empty current directory and command line).
101   static const int min_message_size = 7;
102   if (cds->cbData < min_message_size * sizeof(wchar_t) ||
103       cds->cbData % sizeof(wchar_t) != 0) {
104     LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
105     return false;
106   }
107
108   // We split the string into 4 parts on NULLs.
109   DCHECK(cds->lpData);
110   const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
111                          cds->cbData / sizeof(wchar_t));
112   const std::wstring::size_type first_null = msg.find_first_of(L'\0');
113   if (first_null == 0 || first_null == std::wstring::npos) {
114     // no NULL byte, don't know what to do
115     LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
116       ", first null = " << first_null;
117     return false;
118   }
119
120   // Decode the command, which is everything until the first NULL.
121   if (msg.substr(0, first_null) == L"START") {
122     // Another instance is starting parse the command line & do what it would
123     // have done.
124     VLOG(1) << "Handling STARTUP request from another process";
125     const std::wstring::size_type second_null =
126         msg.find_first_of(L'\0', first_null + 1);
127     if (second_null == std::wstring::npos ||
128         first_null == msg.length() - 1 || second_null == msg.length()) {
129       LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
130         "parts separated by NULLs";
131       return false;
132     }
133
134     // Get current directory.
135     *current_directory = base::FilePath(msg.substr(first_null + 1,
136                                                    second_null - first_null));
137
138     const std::wstring::size_type third_null =
139         msg.find_first_of(L'\0', second_null + 1);
140     if (third_null == std::wstring::npos ||
141         third_null == msg.length()) {
142       LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
143         "parts separated by NULLs";
144     }
145
146     // Get command line.
147     const std::wstring cmd_line =
148         msg.substr(second_null + 1, third_null - second_null);
149     *parsed_command_line = CommandLine::FromString(cmd_line);
150     return true;
151   }
152   return false;
153 }
154
155 bool ProcessLaunchNotification(
156     const ProcessSingleton::NotificationCallback& notification_callback,
157     UINT message,
158     WPARAM wparam,
159     LPARAM lparam,
160     LRESULT* result) {
161   if (message != WM_COPYDATA)
162     return false;
163
164   // Handle the WM_COPYDATA message from another process.
165   HWND hwnd = reinterpret_cast<HWND>(wparam);
166   const COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(lparam);
167
168   CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
169   base::FilePath current_directory;
170   if (!ParseCommandLine(cds, &parsed_command_line, &current_directory)) {
171     *result = TRUE;
172     return true;
173   }
174
175   *result = notification_callback.Run(parsed_command_line, current_directory) ?
176       TRUE : FALSE;
177   return true;
178 }
179
180 // Returns true if Chrome needs to be relaunched into Windows 8 immersive mode.
181 // Following conditions apply:-
182 // 1. Windows 8 or greater.
183 // 2. Not in Windows 8 immersive mode.
184 // 3. Chrome is default browser.
185 // 4. Process integrity level is not high.
186 // 5. The profile data directory is the default directory.
187 // 6. Last used mode was immersive/machine is a tablet.
188 // TODO(ananta)
189 // Move this function to a common place as the Windows 8 delegate_execute
190 // handler can possibly use this.
191 bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath& user_data_dir) {
192 #if defined(USE_AURA)
193   // Returning false from this function doesn't mean we don't launch immersive
194   // mode in Aura. This function is specifically called in case when we need
195   // to relaunch desktop launched chrome into immersive mode through 'relaunch'
196   // menu. In case of Aura, we will use delegate_execute to do the relaunch.
197   return false;
198 #else
199   if (base::win::GetVersion() < base::win::VERSION_WIN8)
200     return false;
201
202   if (base::win::IsProcessImmersive(base::GetCurrentProcessHandle()))
203     return false;
204
205   if (ShellIntegration::GetDefaultBrowser() != ShellIntegration::IS_DEFAULT)
206     return false;
207
208   base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN;
209   base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(),
210                                  &integrity_level);
211   if (integrity_level == base::HIGH_INTEGRITY)
212     return false;
213
214   base::FilePath default_user_data_dir;
215   if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir))
216     return false;
217
218   if (default_user_data_dir != user_data_dir)
219     return false;
220
221   base::win::RegKey reg_key;
222   DWORD reg_value = 0;
223   if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath,
224                      KEY_READ) == ERROR_SUCCESS &&
225       reg_key.ReadValueDW(chrome::kLaunchModeValue,
226                           &reg_value) == ERROR_SUCCESS) {
227     return reg_value == 1;
228   }
229   return false;
230 #endif
231 }
232
233 }  // namespace
234
235 // Microsoft's Softricity virtualization breaks the sandbox processes.
236 // So, if we detect the Softricity DLL we use WMI Win32_Process.Create to
237 // break out of the virtualization environment.
238 // http://code.google.com/p/chromium/issues/detail?id=43650
239 bool ProcessSingleton::EscapeVirtualization(
240     const base::FilePath& user_data_dir) {
241   if (::GetModuleHandle(L"sftldr_wow64.dll") ||
242       ::GetModuleHandle(L"sftldr.dll")) {
243     int process_id;
244     if (!installer::WMIProcess::Launch(::GetCommandLineW(), &process_id))
245       return false;
246     is_virtualized_ = true;
247     // The new window was spawned from WMI, and won't be in the foreground.
248     // So, first we sleep while the new chrome.exe instance starts (because
249     // WaitForInputIdle doesn't work here). Then we poll for up to two more
250     // seconds and make the window foreground if we find it (or we give up).
251     HWND hwnd = 0;
252     ::Sleep(90);
253     for (int tries = 200; tries; --tries) {
254       hwnd = chrome::FindRunningChromeWindow(user_data_dir);
255       if (hwnd) {
256         ::SetForegroundWindow(hwnd);
257         break;
258       }
259       ::Sleep(10);
260     }
261     return true;
262   }
263   return false;
264 }
265
266 ProcessSingleton::ProcessSingleton(
267     const base::FilePath& user_data_dir,
268     const NotificationCallback& notification_callback)
269     : notification_callback_(notification_callback),
270       is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE),
271       user_data_dir_(user_data_dir) {
272 }
273
274 ProcessSingleton::~ProcessSingleton() {
275   if (lock_file_ != INVALID_HANDLE_VALUE)
276     ::CloseHandle(lock_file_);
277 }
278
279 // Code roughly based on Mozilla.
280 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
281   if (is_virtualized_)
282     return PROCESS_NOTIFIED;  // We already spawned the process in this case.
283   if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) {
284     return LOCK_ERROR;
285   } else if (!remote_window_) {
286     return PROCESS_NONE;
287   }
288
289   switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) {
290     case chrome::NOTIFY_SUCCESS:
291       return PROCESS_NOTIFIED;
292     case chrome::NOTIFY_FAILED:
293       remote_window_ = NULL;
294       return PROCESS_NONE;
295     case chrome::NOTIFY_WINDOW_HUNG:
296       remote_window_ = NULL;
297       break;
298   }
299
300   DWORD process_id = 0;
301   DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id);
302   if (!thread_id || !process_id) {
303     remote_window_ = NULL;
304     return PROCESS_NONE;
305   }
306
307   // The window is hung. Scan for every window to find a visible one.
308   bool visible_window = false;
309   ::EnumThreadWindows(thread_id,
310                       &BrowserWindowEnumeration,
311                       reinterpret_cast<LPARAM>(&visible_window));
312
313   // If there is a visible browser window, ask the user before killing it.
314   if (visible_window &&
315       chrome::ShowMessageBox(
316           NULL,
317           l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
318           l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE),
319           chrome::MESSAGE_BOX_TYPE_QUESTION) == chrome::MESSAGE_BOX_RESULT_NO) {
320     // The user denied. Quit silently.
321     return PROCESS_NOTIFIED;
322   }
323
324   // Time to take action. Kill the browser process.
325   base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true);
326   remote_window_ = NULL;
327   return PROCESS_NONE;
328 }
329
330 ProcessSingleton::NotifyResult
331 ProcessSingleton::NotifyOtherProcessOrCreate() {
332   ProcessSingleton::NotifyResult result = PROCESS_NONE;
333   if (!Create()) {
334     result = NotifyOtherProcess();
335     if (result == PROCESS_NONE)
336       result = PROFILE_IN_USE;
337   } else {
338     g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing(
339         *CommandLine::ForCurrentProcess());
340   }
341   return result;
342 }
343
344 // Look for a Chrome instance that uses the same profile directory. If there
345 // isn't one, create a message window with its title set to the profile
346 // directory path.
347 bool ProcessSingleton::Create() {
348   static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!";
349   static const wchar_t kMetroActivationEventName[] =
350       L"Local\\ChromeProcessSingletonStartupMetroActivation!";
351
352   remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
353   if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) {
354     // Make sure we will be the one and only process creating the window.
355     // We use a named Mutex since we are protecting against multi-process
356     // access. As documented, it's clearer to NOT request ownership on creation
357     // since it isn't guaranteed we will get it. It is better to create it
358     // without ownership and explicitly get the ownership afterward.
359     base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName));
360     DPCHECK(only_me.IsValid());
361
362     AutoLockMutex auto_lock_only_me(only_me);
363
364     // We now own the mutex so we are the only process that can create the
365     // window at this time, but we must still check if someone created it
366     // between the time where we looked for it above and the time the mutex
367     // was given to us.
368     remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
369
370
371     // In Win8+, a new Chrome process launched in Desktop mode may need to be
372     // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for
373     // heuristics). To accomplish this, the current Chrome activates Metro
374     // Chrome, releases the startup mutex, and waits for metro Chrome to take
375     // the singleton. From that point onward, the command line for this Chrome
376     // process will be sent to Metro Chrome by the usual channels.
377     if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 &&
378         !base::win::IsMetroProcess()) {
379       // |metro_activation_event| is created right before activating a Metro
380       // Chrome (note that there can only be one Metro Chrome process; by OS
381       // design); all following Desktop processes will then wait for this event
382       // to be signaled by Metro Chrome which will do so as soon as it grabs
383       // this singleton (should any of the waiting processes timeout waiting for
384       // the signal they will try to grab the singleton for themselves which
385       // will result in a forced Desktop Chrome launch in the worst case).
386       base::win::ScopedHandle metro_activation_event(
387           ::OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName));
388       if (!metro_activation_event.IsValid() &&
389           ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) {
390         // No Metro activation is under way, but the desire is to launch in
391         // Metro mode: activate and rendez-vous with the activated process.
392         metro_activation_event.Set(
393             ::CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName));
394         if (!chrome::ActivateMetroChrome()) {
395           // Failed to launch immersive Chrome, default to launching on Desktop.
396           LOG(ERROR) << "Failed to launch immersive chrome";
397           metro_activation_event.Close();
398         }
399       }
400
401       if (metro_activation_event.IsValid()) {
402         // Release |only_me| (to let Metro Chrome grab this singleton) and wait
403         // until the event is signaled (i.e. Metro Chrome was successfully
404         // activated). Ignore timeout waiting for |metro_activation_event|.
405         {
406           AutoUnlockMutex auto_unlock_only_me(only_me);
407
408           DWORD result = ::WaitForSingleObject(metro_activation_event,
409                                                kMetroChromeActivationTimeoutMs);
410           DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT)
411               << "Result = " << result;
412         }
413
414         // Check if this singleton was successfully grabbed by another process
415         // (hopefully Metro Chrome). Failing to do so, this process will grab
416         // the singleton and launch in Desktop mode.
417         remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
418       }
419     }
420
421     if (!remote_window_) {
422       // We have to make sure there is no Chrome instance running on another
423       // machine that uses the same profile.
424       base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile);
425       lock_file_ = ::CreateFile(lock_file_path.value().c_str(),
426                                 GENERIC_WRITE,
427                                 FILE_SHARE_READ,
428                                 NULL,
429                                 CREATE_ALWAYS,
430                                 FILE_ATTRIBUTE_NORMAL |
431                                 FILE_FLAG_DELETE_ON_CLOSE,
432                                 NULL);
433       DWORD error = ::GetLastError();
434       LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
435           error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable.";
436       LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
437           << "Lock file can not be created! Error code: " << error;
438
439       if (lock_file_ != INVALID_HANDLE_VALUE) {
440         // Set the window's title to the path of our user data directory so
441         // other Chrome instances can decide if they should forward to us.
442         bool result = window_.CreateNamed(
443             base::Bind(&ProcessLaunchNotification, notification_callback_),
444             user_data_dir_.value());
445         CHECK(result && window_.hwnd());
446       }
447
448       if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
449         // Make sure no one is still waiting on Metro activation whether it
450         // succeeded (i.e., this is the Metro process) or failed.
451         base::win::ScopedHandle metro_activation_event(
452             ::OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName));
453         if (metro_activation_event.IsValid())
454           ::SetEvent(metro_activation_event);
455       }
456     }
457   }
458
459   return window_.hwnd() != NULL;
460 }
461
462 void ProcessSingleton::Cleanup() {
463 }