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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/process_singleton.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
« 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