Chromium Code Reviews| Index: chrome/browser/process_singleton_win.cc |
| diff --git a/chrome/browser/process_singleton_win.cc b/chrome/browser/process_singleton_win.cc |
| index b8f5c206cbf4d168b0587416861dc8be1cbc8f6c..b6e5af7a93869882f5d073624147f89ccd6403b5 100644 |
| --- a/chrome/browser/process_singleton_win.cc |
| +++ b/chrome/browser/process_singleton_win.cc |
| @@ -47,7 +47,45 @@ const char kLockfile[] = "lockfile"; |
| const char kSearchUrl[] = |
| "http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8"; |
| -const int kImmersiveChromeInitTimeout = 500; |
| +const int kMetroChromeActivationTimeoutMs = 3000; |
| + |
| +// A helper class that acquires the given |mutex| while the AutoLockMutex is in |
| +// scope. |
| +class AutoLockMutex { |
| + public: |
| + explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) { |
| + DWORD result = WaitForSingleObject(mutex_, INFINITE); |
| + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; |
| + } |
| + |
| + ~AutoLockMutex() { |
| + BOOL released = ReleaseMutex(mutex_); |
| + DPCHECK(released); |
| + } |
| + |
| + private: |
| + HANDLE mutex_; |
| + DISALLOW_COPY_AND_ASSIGN(AutoLockMutex); |
| +}; |
| + |
| +// A helper class that releases the given |mutex| while the AutoUnlockMutex is |
| +// in scope and immediately re-acquires it when going out of scope. |
| +class AutoUnlockMutex { |
| + public: |
| + explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) { |
| + BOOL released = ReleaseMutex(mutex_); |
| + DPCHECK(released); |
| + } |
| + |
| + ~AutoUnlockMutex() { |
| + DWORD result = WaitForSingleObject(mutex_, INFINITE); |
| + DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result; |
| + } |
| + |
| + private: |
| + HANDLE mutex_; |
| + DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex); |
| +}; |
| // Checks the visibility of the enumerated window and signals once a visible |
| // window has been found. |
| @@ -208,22 +246,6 @@ bool ShouldLaunchInWindows8ImmersiveMode(const FilePath& user_data_dir) { |
| if (default_user_data_dir != user_data_dir) |
| return false; |
| - // TODO(gab): This is a temporary solution to avoid activating Metro Chrome |
| - // when chrome.exe is invoked with one of the short-lived commands below. The |
| - // long-term and correct solution is to only check/activate Chrome later; |
| - // after handling of these short-lived commands has occured |
| - // (http://crbug.com/155585). |
| - // This is a 1:1 mapping of the switches that force an early exit of Chrome in |
| - // ChromeBrowserMainParts::PreMainMessageLoopRunImpl(). |
| - if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUninstall) || |
| - CommandLine::ForCurrentProcess()->HasSwitch(switches::kHideIcons) || |
| - CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowIcons) || |
| - CommandLine::ForCurrentProcess()->HasSwitch( |
| - switches::kMakeDefaultBrowser) || |
| - CommandLine::ForCurrentProcess()->HasSwitch(switches::kPackExtension)) { |
| - return false; |
| - } |
| - |
| base::win::RegKey reg_key; |
| DWORD reg_value = 0; |
| if (reg_key.Create(HKEY_CURRENT_USER, chrome::kMetroRegistryPath, |
| @@ -268,89 +290,10 @@ bool ProcessSingleton::EscapeVirtualization(const FilePath& user_data_dir) { |
| return false; |
| } |
| -// Look for a Chrome instance that uses the same profile directory. |
| -// If there isn't one, create a message window with its title set to |
| -// the profile directory path. |
| ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir) |
| : window_(NULL), locked_(false), foreground_window_(NULL), |
| - is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE) { |
| - // For Windows 8 and above check if we need to relaunch into Windows 8 |
| - // immersive mode. |
| - if (ShouldLaunchInWindows8ImmersiveMode(user_data_dir)) { |
| - bool immersive_chrome_launched = ActivateMetroChrome(); |
| - if (!immersive_chrome_launched) { |
| - LOG(WARNING) << "Failed to launch immersive chrome"; |
| - } else { |
| - // Sleep to allow the immersive chrome process to create its initial |
| - // message window. |
| - SleepEx(kImmersiveChromeInitTimeout, FALSE); |
| - } |
| - } |
| - remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, |
| - chrome::kMessageWindowClass, |
| - user_data_dir.value().c_str()); |
| - if (!remote_window_ && !EscapeVirtualization(user_data_dir)) { |
| - // Make sure we will be the one and only process creating the window. |
| - // We use a named Mutex since we are protecting against multi-process |
| - // access. As documented, it's clearer to NOT request ownership on creation |
| - // since it isn't guaranteed we will get it. It is better to create it |
| - // without ownership and explicitly get the ownership afterward. |
| - std::wstring mutex_name(L"Local\\ChromeProcessSingletonStartup!"); |
| - base::win::ScopedHandle only_me( |
| - CreateMutex(NULL, FALSE, mutex_name.c_str())); |
| - DCHECK(only_me.Get() != NULL) << "GetLastError = " << GetLastError(); |
| - |
| - // This is how we acquire the mutex (as opposed to the initial ownership). |
| - DWORD result = WaitForSingleObject(only_me, INFINITE); |
| - DCHECK(result == WAIT_OBJECT_0) << "Result = " << result << |
| - "GetLastError = " << GetLastError(); |
| - |
| - // We now own the mutex so we are the only process that can create the |
| - // window at this time, but we must still check if someone created it |
| - // between the time where we looked for it above and the time the mutex |
| - // was given to us. |
| - remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, |
| - chrome::kMessageWindowClass, |
| - user_data_dir.value().c_str()); |
| - if (!remote_window_) { |
| - // We have to make sure there is no Chrome instance running on another |
| - // machine that uses the same profile. |
| - FilePath lock_file_path = user_data_dir.AppendASCII(kLockfile); |
| - lock_file_ = CreateFile(lock_file_path.value().c_str(), |
| - GENERIC_WRITE, |
| - FILE_SHARE_READ, |
| - NULL, |
| - CREATE_ALWAYS, |
| - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, |
| - NULL); |
| - DWORD error = GetLastError(); |
| - LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && |
| - error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; |
| - LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) |
| - << "Lock file can not be created! Error code: " << error; |
| - |
| - if (lock_file_ != INVALID_HANDLE_VALUE) { |
| - HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc); |
| - |
| - WNDCLASSEX wc = {0}; |
| - wc.cbSize = sizeof(wc); |
| - wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>; |
| - wc.hInstance = hinst; |
| - wc.lpszClassName = chrome::kMessageWindowClass; |
| - ATOM clazz = ::RegisterClassEx(&wc); |
| - DCHECK(clazz); |
| - |
| - // Set the window's title to the path of our user data directory so |
| - // other Chrome instances can decide if they should forward to us. |
| - window_ = ::CreateWindow(MAKEINTATOM(clazz), |
| - user_data_dir.value().c_str(), |
| - 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this); |
| - CHECK(window_); |
| - } |
| - } |
| - BOOL success = ReleaseMutex(only_me); |
| - DCHECK(success) << "GetLastError = " << GetLastError(); |
| - } |
| + is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE), |
| + user_data_dir_(user_data_dir) { |
| } |
| ProcessSingleton::~ProcessSingleton() { |
| @@ -366,6 +309,7 @@ ProcessSingleton::~ProcessSingleton() { |
| CloseHandle(lock_file_); |
| } |
| +// Code roughly based on Mozilla. |
| ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { |
| if (is_virtualized_) |
| return PROCESS_NOTIFIED; // We already spawned the process in this case. |
| @@ -481,21 +425,148 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { |
| ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate( |
| const NotificationCallback& notification_callback) { |
| - NotifyResult result = NotifyOtherProcess(); |
| - if (result != PROCESS_NONE) |
| - return result; |
| - return Create(notification_callback) ? PROCESS_NONE : PROFILE_IN_USE; |
| + ProcessSingleton::NotifyResult result = PROCESS_NONE; |
| + if (!Create(notification_callback)) { |
| + result = NotifyOtherProcess(); |
| + if (result == PROCESS_NONE) |
| + result = PROFILE_IN_USE; |
| + } |
| + return result; |
| } |
| -// On Windows, there is no need to call Create() since the message |
| -// window is created in the constructor but to avoid having more |
| -// platform specific code in browser_main.cc we tolerate calls to |
| -// Create(). |
| +// Look for a Chrome instance that uses the same profile directory. If there |
| +// isn't one, create a message window with its title set to the profile |
| +// directory path. |
| bool ProcessSingleton::Create( |
| const NotificationCallback& notification_callback) { |
| - DCHECK(!remote_window_); |
| DCHECK(notification_callback_.is_null()); |
| + static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!"; |
| + static const wchar_t kMetroActivationEventName[] = |
| + L"Local\\ChromeProcessSingletonStartupMetroActivation!"; |
| + |
| + remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, |
| + chrome::kMessageWindowClass, |
| + user_data_dir_.value().c_str()); |
| + if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) { |
| + // Make sure we will be the one and only process creating the window. |
| + // We use a named Mutex since we are protecting against multi-process |
| + // access. As documented, it's clearer to NOT request ownership on creation |
| + // since it isn't guaranteed we will get it. It is better to create it |
| + // without ownership and explicitly get the ownership afterward. |
| + 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
|
| + DPCHECK(only_me.IsValid()); |
| + |
| + AutoLockMutex auto_lock_only_me(only_me); |
| + |
| + // We now own the mutex so we are the only process that can create the |
| + // window at this time, but we must still check if someone created it |
| + // between the time where we looked for it above and the time the mutex |
| + // was given to us. |
| + remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, |
| + chrome::kMessageWindowClass, |
| + user_data_dir_.value().c_str()); |
| + |
| + |
| + // In Win8+, a new Chrome process launched in Desktop mode may need to be |
| + // transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for |
| + // heuristics). To accomplish this, the current Chrome activates Metro |
| + // Chrome, releases the startup mutex, and waits for metro Chrome to take |
| + // the singleton. From that point onward, the command line for this Chrome |
| + // process will be sent to Metro Chrome by the usual channels. |
| + if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 && |
| + !base::win::IsMetroProcess()) { |
| + // |metro_activation_event| is created right before activating a Metro |
| + // Chrome (note that there can only be one Metro Chrome process; by OS |
| + // design); all following Desktop processes will then wait for this event |
| + // to be signaled by Metro Chrome which will do so as soon as it grabs |
| + // this singleton (should any of the waiting processes timeout waiting for |
| + // the signal they will try to grab the singleton for themselves which |
| + // will result in a forced Desktop Chrome launch in the worst case). |
| + base::win::ScopedHandle metro_activation_event( |
| + OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName)); |
| + if (!metro_activation_event.IsValid() && |
| + ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) { |
| + // No Metro activation is under way, but the desire is to launch in |
| + // Metro mode: activate and rendez-vous with the activated process. |
| + metro_activation_event.Set( |
| + CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName)); |
| + if (!ActivateMetroChrome()) { |
| + // Failed to launch immersive Chrome, default to launching on Desktop. |
| + LOG(ERROR) << "Failed to launch immersive chrome"; |
| + metro_activation_event.Close(); |
| + } |
| + } |
| + |
| + if (metro_activation_event.IsValid()) { |
| + // Release |only_me| (to let Metro Chrome grab this singleton) and wait |
| + // until the event is signaled (i.e. Metro Chrome was successfully |
| + // activated). Ignore timeout waiting for |metro_activation_event|. |
| + { |
| + AutoUnlockMutex auto_unlock_only_me(only_me); |
| + |
| + DWORD result = WaitForSingleObject(metro_activation_event, |
| + kMetroChromeActivationTimeoutMs); |
| + DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT) |
| + << "Result = " << result; |
| + } |
| + |
| + // Check if this singleton was successfully grabbed by another process |
| + // (hopefully Metro Chrome). Failing to do so, this process will grab |
| + // the singleton and launch in Desktop mode. |
| + remote_window_ = FindWindowEx(HWND_MESSAGE, NULL, |
| + chrome::kMessageWindowClass, |
| + user_data_dir_.value().c_str()); |
| + } |
| + } |
| + |
| + if (!remote_window_) { |
| + // We have to make sure there is no Chrome instance running on another |
| + // machine that uses the same profile. |
| + FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile); |
| + lock_file_ = CreateFile(lock_file_path.value().c_str(), |
| + GENERIC_WRITE, |
| + FILE_SHARE_READ, |
| + NULL, |
| + CREATE_ALWAYS, |
| + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, |
| + NULL); |
| + DWORD error = GetLastError(); |
| + LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE && |
| + error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable."; |
| + LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE) |
| + << "Lock file can not be created! Error code: " << error; |
| + |
| + if (lock_file_ != INVALID_HANDLE_VALUE) { |
| + HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc); |
| + |
| + WNDCLASSEX wc = {0}; |
| + wc.cbSize = sizeof(wc); |
| + wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>; |
| + wc.hInstance = hinst; |
| + wc.lpszClassName = chrome::kMessageWindowClass; |
| + ATOM clazz = ::RegisterClassEx(&wc); |
| + DCHECK(clazz); |
| + |
| + // Set the window's title to the path of our user data directory so |
| + // other Chrome instances can decide if they should forward to us. |
| + window_ = ::CreateWindow(MAKEINTATOM(clazz), |
| + user_data_dir_.value().c_str(), |
| + 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this); |
| + CHECK(window_); |
| + } |
| + |
| + if (base::win::GetVersion() >= base::win::VERSION_WIN8) { |
| + // Make sure no one is still waiting on Metro activation whether it |
| + // succeeded (i.e., this is the Metro process) or failed. |
| + base::win::ScopedHandle metro_activation_event( |
| + OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName)); |
| + if (metro_activation_event.IsValid()) |
| + SetEvent(metro_activation_event); |
| + } |
| + } |
| + } |
| + |
| if (window_ != NULL) |
| notification_callback_ = notification_callback; |