Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(599)

Side by Side Diff: chrome/browser/process_singleton_win.cc

Issue 11099053: Refactor ProcessSingleton to allow a Desktop process to activate Metro Chrome cleanly. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: merge up to r170957 Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/process_singleton.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/process_singleton.h" 5 #include "chrome/browser/process_singleton.h"
6 6
7 #include <shellapi.h> 7 #include <shellapi.h>
8 #include <shobjidl.h> 8 #include <shobjidl.h>
9 9
10 #include "base/base_paths.h" 10 #include "base/base_paths.h"
(...skipping 29 matching lines...) Expand all
40 #include "ui/base/l10n/l10n_util.h" 40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/base/win/hwnd_util.h" 41 #include "ui/base/win/hwnd_util.h"
42 42
43 namespace { 43 namespace {
44 44
45 const char kLockfile[] = "lockfile"; 45 const char kLockfile[] = "lockfile";
46 46
47 const char kSearchUrl[] = 47 const char kSearchUrl[] =
48 "http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8"; 48 "http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8";
49 49
50 const int kImmersiveChromeInitTimeout = 500; 50 const int kMetroChromeActivationTimeoutMs = 3000;
51
52 // A helper class that acquires the given |mutex| while the AutoLockMutex is in
53 // scope.
54 class 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.
73 class 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 };
51 89
52 // Checks the visibility of the enumerated window and signals once a visible 90 // Checks the visibility of the enumerated window and signals once a visible
53 // window has been found. 91 // window has been found.
54 BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { 92 BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
55 bool* result = reinterpret_cast<bool*>(param); 93 bool* result = reinterpret_cast<bool*>(param);
56 *result = IsWindowVisible(window) != 0; 94 *result = IsWindowVisible(window) != 0;
57 // Stops enumeration if a visible window has been found. 95 // Stops enumeration if a visible window has been found.
58 return !*result; 96 return !*result;
59 } 97 }
60 98
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after
201 if (integrity_level == base::HIGH_INTEGRITY) 239 if (integrity_level == base::HIGH_INTEGRITY)
202 return false; 240 return false;
203 241
204 FilePath default_user_data_dir; 242 FilePath default_user_data_dir;
205 if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir)) 243 if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir))
206 return false; 244 return false;
207 245
208 if (default_user_data_dir != user_data_dir) 246 if (default_user_data_dir != user_data_dir)
209 return false; 247 return false;
210 248
211 // TODO(gab): This is a temporary solution to avoid activating Metro Chrome
212 // when chrome.exe is invoked with one of the short-lived commands below. The
213 // long-term and correct solution is to only check/activate Chrome later;
214 // after handling of these short-lived commands has occured
215 // (http://crbug.com/155585).
216 // This is a 1:1 mapping of the switches that force an early exit of Chrome in
217 // ChromeBrowserMainParts::PreMainMessageLoopRunImpl().
218 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUninstall) ||
219 CommandLine::ForCurrentProcess()->HasSwitch(switches::kHideIcons) ||
220 CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowIcons) ||
221 CommandLine::ForCurrentProcess()->HasSwitch(
222 switches::kMakeDefaultBrowser) ||
223 CommandLine::ForCurrentProcess()->HasSwitch(switches::kPackExtension)) {
224 return false;
225 }
226
227 base::win::RegKey reg_key; 249 base::win::RegKey reg_key;
228 DWORD reg_value = 0; 250 DWORD reg_value = 0;
229 if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath, 251 if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath,
230 KEY_READ) == ERROR_SUCCESS && 252 KEY_READ) == ERROR_SUCCESS &&
231 reg_key.ReadValueDW(chrome::kLaunchModeValue, 253 reg_key.ReadValueDW(chrome::kLaunchModeValue,
232 &reg_value) == ERROR_SUCCESS) { 254 &reg_value) == ERROR_SUCCESS) {
233 return reg_value == 1; 255 return reg_value == 1;
234 } 256 }
235 return base::win::IsMachineATablet(); 257 return base::win::IsMachineATablet();
236 } 258 }
(...skipping 24 matching lines...) Expand all
261 ::SetForegroundWindow(hwnd); 283 ::SetForegroundWindow(hwnd);
262 break; 284 break;
263 } 285 }
264 ::Sleep(10); 286 ::Sleep(10);
265 } 287 }
266 return true; 288 return true;
267 } 289 }
268 return false; 290 return false;
269 } 291 }
270 292
271 // Look for a Chrome instance that uses the same profile directory.
272 // If there isn't one, create a message window with its title set to
273 // the profile directory path.
274 ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir) 293 ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir)
275 : window_(NULL), locked_(false), foreground_window_(NULL), 294 : window_(NULL), locked_(false), foreground_window_(NULL),
276 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE) { 295 is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE),
277 // For Windows 8 and above check if we need to relaunch into Windows 8 296 user_data_dir_(user_data_dir) {
278 // immersive mode.
279 if (ShouldLaunchInWindows8ImmersiveMode(user_data_dir)) {
280 bool immersive_chrome_launched = ActivateMetroChrome();
281 if (!immersive_chrome_launched) {
282 LOG(WARNING) << "Failed to launch immersive chrome";
283 } else {
284 // Sleep to allow the immersive chrome process to create its initial
285 // message window.
286 SleepEx(kImmersiveChromeInitTimeout, FALSE);
287 }
288 }
289 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
290 chrome::kMessageWindowClass,
291 user_data_dir.value().c_str());
292 if (!remote_window_ && !EscapeVirtualization(user_data_dir)) {
293 // Make sure we will be the one and only process creating the window.
294 // We use a named Mutex since we are protecting against multi-process
295 // access. As documented, it's clearer to NOT request ownership on creation
296 // since it isn't guaranteed we will get it. It is better to create it
297 // without ownership and explicitly get the ownership afterward.
298 std::wstring mutex_name(L"Local\\ChromeProcessSingletonStartup!");
299 base::win::ScopedHandle only_me(
300 CreateMutex(NULL, FALSE, mutex_name.c_str()));
301 DCHECK(only_me.Get() != NULL) << "GetLastError = " << GetLastError();
302
303 // This is how we acquire the mutex (as opposed to the initial ownership).
304 DWORD result = WaitForSingleObject(only_me, INFINITE);
305 DCHECK(result == WAIT_OBJECT_0) << "Result = " << result <<
306 "GetLastError = " << GetLastError();
307
308 // We now own the mutex so we are the only process that can create the
309 // window at this time, but we must still check if someone created it
310 // between the time where we looked for it above and the time the mutex
311 // was given to us.
312 remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
313 chrome::kMessageWindowClass,
314 user_data_dir.value().c_str());
315 if (!remote_window_) {
316 // We have to make sure there is no Chrome instance running on another
317 // machine that uses the same profile.
318 FilePath lock_file_path = user_data_dir.AppendASCII(kLockfile);
319 lock_file_ = CreateFile(lock_file_path.value().c_str(),
320 GENERIC_WRITE,
321 FILE_SHARE_READ,
322 NULL,
323 CREATE_ALWAYS,
324 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
325 NULL);
326 DWORD error = GetLastError();
327 LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
328 error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable.";
329 LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
330 << "Lock file can not be created! Error code: " << error;
331
332 if (lock_file_ != INVALID_HANDLE_VALUE) {
333 HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc);
334
335 WNDCLASSEX wc = {0};
336 wc.cbSize = sizeof(wc);
337 wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>;
338 wc.hInstance = hinst;
339 wc.lpszClassName = chrome::kMessageWindowClass;
340 ATOM clazz = ::RegisterClassEx(&wc);
341 DCHECK(clazz);
342
343 // Set the window's title to the path of our user data directory so
344 // other Chrome instances can decide if they should forward to us.
345 window_ = ::CreateWindow(MAKEINTATOM(clazz),
346 user_data_dir.value().c_str(),
347 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this);
348 CHECK(window_);
349 }
350 }
351 BOOL success = ReleaseMutex(only_me);
352 DCHECK(success) << "GetLastError = " << GetLastError();
353 }
354 } 297 }
355 298
356 ProcessSingleton::~ProcessSingleton() { 299 ProcessSingleton::~ProcessSingleton() {
357 // We need to unregister the window as late as possible so that we can detect 300 // We need to unregister the window as late as possible so that we can detect
358 // another instance of chrome running. Otherwise we may end up writing out 301 // another instance of chrome running. Otherwise we may end up writing out
359 // data while a new chrome is starting up. 302 // data while a new chrome is starting up.
360 if (window_) { 303 if (window_) {
361 ::DestroyWindow(window_); 304 ::DestroyWindow(window_);
362 ::UnregisterClass(chrome::kMessageWindowClass, 305 ::UnregisterClass(chrome::kMessageWindowClass,
363 base::GetModuleFromAddress(&ThunkWndProc)); 306 base::GetModuleFromAddress(&ThunkWndProc));
364 } 307 }
365 if (lock_file_ != INVALID_HANDLE_VALUE) 308 if (lock_file_ != INVALID_HANDLE_VALUE)
366 CloseHandle(lock_file_); 309 CloseHandle(lock_file_);
367 } 310 }
368 311
312 // Code roughly based on Mozilla.
369 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { 313 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
370 if (is_virtualized_) 314 if (is_virtualized_)
371 return PROCESS_NOTIFIED; // We already spawned the process in this case. 315 return PROCESS_NOTIFIED; // We already spawned the process in this case.
372 if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) { 316 if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) {
373 return LOCK_ERROR; 317 return LOCK_ERROR;
374 } else if (!remote_window_) { 318 } else if (!remote_window_) {
375 g_browser_process->PlatformSpecificCommandLineProcessing( 319 g_browser_process->PlatformSpecificCommandLineProcessing(
376 *CommandLine::ForCurrentProcess()); 320 *CommandLine::ForCurrentProcess());
377 return PROCESS_NONE; 321 return PROCESS_NONE;
378 } 322 }
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
474 } 418 }
475 419
476 // Time to take action. Kill the browser process. 420 // Time to take action. Kill the browser process.
477 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true); 421 base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true);
478 remote_window_ = NULL; 422 remote_window_ = NULL;
479 return PROCESS_NONE; 423 return PROCESS_NONE;
480 } 424 }
481 425
482 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate( 426 ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate(
483 const NotificationCallback& notification_callback) { 427 const NotificationCallback& notification_callback) {
484 NotifyResult result = NotifyOtherProcess(); 428 ProcessSingleton::NotifyResult result = PROCESS_NONE;
485 if (result != PROCESS_NONE) 429 if (!Create(notification_callback)) {
486 return result; 430 result = NotifyOtherProcess();
487 return Create(notification_callback) ? PROCESS_NONE : PROFILE_IN_USE; 431 if (result == PROCESS_NONE)
432 result = PROFILE_IN_USE;
433 }
434 return result;
488 } 435 }
489 436
490 // On Windows, there is no need to call Create() since the message 437 // Look for a Chrome instance that uses the same profile directory. If there
491 // window is created in the constructor but to avoid having more 438 // isn't one, create a message window with its title set to the profile
492 // platform specific code in browser_main.cc we tolerate calls to 439 // directory path.
493 // Create().
494 bool ProcessSingleton::Create( 440 bool ProcessSingleton::Create(
495 const NotificationCallback& notification_callback) { 441 const NotificationCallback& notification_callback) {
496 DCHECK(!remote_window_);
497 DCHECK(notification_callback_.is_null()); 442 DCHECK(notification_callback_.is_null());
498 443
444 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));
ananta 2012/12/04 19:11:54 I think the mutex name needs to be formed off the
gab 2012/12/04 19:44:18 As discussed offline, 1) This has always been the
grt (UTC plus 2) 2012/12/04 19:55:23 We switched to using a global mutex about two year
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
499 if (window_ != NULL) 570 if (window_ != NULL)
500 notification_callback_ = notification_callback; 571 notification_callback_ = notification_callback;
501 572
502 return window_ != NULL; 573 return window_ != NULL;
503 } 574 }
504 575
505 void ProcessSingleton::Cleanup() { 576 void ProcessSingleton::Cleanup() {
506 } 577 }
507 578
508 LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) { 579 LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) {
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
541 switch (message) { 612 switch (message) {
542 case WM_COPYDATA: 613 case WM_COPYDATA:
543 return OnCopyData(reinterpret_cast<HWND>(wparam), 614 return OnCopyData(reinterpret_cast<HWND>(wparam),
544 reinterpret_cast<COPYDATASTRUCT*>(lparam)); 615 reinterpret_cast<COPYDATASTRUCT*>(lparam));
545 default: 616 default:
546 break; 617 break;
547 } 618 }
548 619
549 return ::DefWindowProc(hwnd, message, wparam, lparam); 620 return ::DefWindowProc(hwnd, message, wparam, lparam);
550 } 621 }
OLDNEW
« no previous file with comments | « chrome/browser/process_singleton.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698