blob: 21746674d758a185bcb7aa0bad64f9dc6968bf42 [file] [log] [blame]
[email protected]fc14cef2009-01-27 22:17:291// Copyright (c) 2009 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/message_window.h"
6
7#include "base/base_paths.h"
8#include "base/command_line.h"
9#include "base/process_util.h"
10#include "base/win_util.h"
11#include "chrome/app/result_codes.h"
12#include "chrome/browser/browser_init.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/profile.h"
15#include "chrome/browser/profile_manager.h"
16#include "chrome/common/chrome_constants.h"
17#include "chrome/common/chrome_paths.h"
18#include "chrome/common/l10n_util.h"
19#include "chrome/common/win_util.h"
20
21#include "chromium_strings.h"
22#include "generated_resources.h"
23
24namespace {
25
26// Checks the visiblilty of the enumerated window and signals once a visible
27// window has been found.
28BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
29 bool* result = reinterpret_cast<bool*>(param);
30 *result = IsWindowVisible(window) != 0;
31 // Stops enumeration if a visible window has been found.
32 return !*result;
33}
34
35} // namespace
36
[email protected]f7011fcb2009-01-28 21:54:3237MessageWindow::MessageWindow(const FilePath& user_data_dir)
[email protected]fc14cef2009-01-27 22:17:2938 : window_(NULL),
39 locked_(false) {
40 // Look for a Chrome instance that uses the same profile directory:
41 remote_window_ = FindWindowEx(HWND_MESSAGE,
42 NULL,
43 chrome::kMessageWindowClass,
[email protected]f7011fcb2009-01-28 21:54:3244 user_data_dir.ToWStringHack().c_str());
[email protected]fc14cef2009-01-27 22:17:2945}
46
47MessageWindow::~MessageWindow() {
48 if (window_)
49 DestroyWindow(window_);
50}
51
52bool MessageWindow::NotifyOtherProcess() {
53 if (!remote_window_)
54 return false;
55
56 // Found another window, send our command line to it
57 // format is "START\0<<<current directory>>>\0<<<commandline>>>".
58 std::wstring to_send(L"START\0", 6); // want the NULL in the string.
59 std::wstring cur_dir;
60 if (!PathService::Get(base::DIR_CURRENT, &cur_dir))
61 return false;
62 to_send.append(cur_dir);
63 to_send.append(L"\0", 1); // Null separator.
64 to_send.append(GetCommandLineW());
65 to_send.append(L"\0", 1); // Null separator.
66
67 // Allow the current running browser window making itself the foreground
68 // window (otherwise it will just flash in the taskbar).
69 DWORD process_id = 0;
70 DWORD thread_id = GetWindowThreadProcessId(remote_window_, &process_id);
[email protected]0815b6d2009-02-11 00:39:3771 // It is possible that the process owning this window may have died by now.
72 if (!thread_id || !process_id) {
73 remote_window_ = NULL;
74 return false;
75 }
76
[email protected]fc14cef2009-01-27 22:17:2977 AllowSetForegroundWindow(process_id);
78
79 // Gives 20 seconds timeout for the current browser process to respond.
80 const int kTimeout = 20000;
81 COPYDATASTRUCT cds;
82 cds.dwData = 0;
83 cds.cbData = static_cast<DWORD>((to_send.length() + 1) * sizeof(wchar_t));
84 cds.lpData = const_cast<wchar_t*>(to_send.c_str());
85 DWORD_PTR result = 0;
86 if (SendMessageTimeout(remote_window_,
87 WM_COPYDATA,
88 NULL,
89 reinterpret_cast<LPARAM>(&cds),
90 SMTO_ABORTIFHUNG,
91 kTimeout,
92 &result)) {
[email protected]0815b6d2009-02-11 00:39:3793 // It is possible that the process owning this window may have died by now.
94 if (!result) {
95 remote_window_ = NULL;
96 return false;
97 }
[email protected]fc14cef2009-01-27 22:17:2998 return true;
99 }
100
[email protected]0815b6d2009-02-11 00:39:37101 // It is possible that the process owning this window may have died by now.
102 if (!IsWindow(remote_window_)) {
103 remote_window_ = NULL;
104 return false;
105 }
106
[email protected]fc14cef2009-01-27 22:17:29107 // The window is hung. Scan for every window to find a visible one.
108 bool visible_window = false;
109 EnumThreadWindows(thread_id,
110 &BrowserWindowEnumeration,
111 reinterpret_cast<LPARAM>(&visible_window));
112
113 // If there is a visible browser window, ask the user before killing it.
114 if (visible_window) {
115 std::wstring text = l10n_util::GetString(IDS_BROWSER_HUNGBROWSER_MESSAGE);
116 std::wstring caption = l10n_util::GetString(IDS_PRODUCT_NAME);
117 if (IDYES != win_util::MessageBox(NULL, text, caption,
118 MB_YESNO | MB_ICONSTOP | MB_TOPMOST)) {
119 // The user denied. Quit silently.
120 return true;
121 }
122 }
123
124 // Time to take action. Kill the browser process.
[email protected]cd4fd152009-02-09 19:28:41125 base::KillProcessById(process_id, ResultCodes::HUNG, true);
[email protected]fc14cef2009-01-27 22:17:29126 remote_window_ = NULL;
127 return false;
128}
129
130void MessageWindow::Create() {
131 DCHECK(!window_);
132 DCHECK(!remote_window_);
133 HINSTANCE hinst = GetModuleHandle(NULL);
134
135 WNDCLASSEX wc = {0};
136 wc.cbSize = sizeof(wc);
137 wc.lpfnWndProc = MessageWindow::WndProcStatic;
138 wc.hInstance = hinst;
139 wc.lpszClassName = chrome::kMessageWindowClass;
140 RegisterClassEx(&wc);
141
142 std::wstring user_data_dir;
143 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
144
145 // Set the window's title to the path of our user data directory so other
146 // Chrome instances can decide if they should forward to us or not.
147 window_ = CreateWindow(chrome::kMessageWindowClass, user_data_dir.c_str(),
148 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, 0);
149 DCHECK(window_);
150
151 win_util::SetWindowUserData(window_, this);
152}
153
154LRESULT MessageWindow::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) {
155 // Ignore the request if the browser process is already in shutdown path.
[email protected]0815b6d2009-02-11 00:39:37156 if (!g_browser_process || g_browser_process->IsShuttingDown()) {
157 LOG(WARNING) << "Not handling WM_COPYDATA as browser is shutting down";
158 return FALSE;
159 }
[email protected]fc14cef2009-01-27 22:17:29160
161 // If locked, it means we are not ready to process this message because
162 // we are probably in a first run critical phase.
163 if (locked_)
164 return TRUE;
165
166 // We should have enough room for the shortest command (min_message_size)
167 // and also be a multiple of wchar_t bytes.
168 static const int min_message_size = 7;
169 if (cds->cbData < min_message_size || cds->cbData % sizeof(wchar_t) != 0) {
170 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
171 return TRUE;
172 }
173
174 // We split the string into 4 parts on NULLs.
175 const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
176 cds->cbData / sizeof(wchar_t));
177 const std::wstring::size_type first_null = msg.find_first_of(L'\0');
178 if (first_null == 0 || first_null == std::wstring::npos) {
179 // no NULL byte, don't know what to do
180 LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
181 ", first null = " << first_null;
182 return TRUE;
183 }
184
185 // Decode the command, which is everything until the first NULL.
186 if (msg.substr(0, first_null) == L"START") {
187 // Another instance is starting parse the command line & do what it would
188 // have done.
189 LOG(INFO) << "Handling STARTUP request from another process";
190 const std::wstring::size_type second_null =
191 msg.find_first_of(L'\0', first_null + 1);
192 if (second_null == std::wstring::npos ||
193 first_null == msg.length() - 1 || second_null == msg.length()) {
194 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
195 "parts separated by NULLs";
196 return TRUE;
197 }
198
199 // Get current directory.
200 const std::wstring cur_dir =
201 msg.substr(first_null + 1, second_null - first_null);
202
203 const std::wstring::size_type third_null =
204 msg.find_first_of(L'\0', second_null + 1);
205 if (third_null == std::wstring::npos ||
206 third_null == msg.length()) {
207 LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
208 "parts separated by NULLs";
209 }
210
211 // Get command line.
212 const std::wstring cmd_line =
213 msg.substr(second_null + 1, third_null - second_null);
214
215 CommandLine parsed_command_line(L"");
216 parsed_command_line.ParseFromString(cmd_line);
217 PrefService* prefs = g_browser_process->local_state();
218 DCHECK(prefs);
219
[email protected]f7011fcb2009-01-28 21:54:32220 FilePath user_data_dir;
[email protected]fc14cef2009-01-27 22:17:29221 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
222 ProfileManager* profile_manager = g_browser_process->profile_manager();
223 Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
224 if (!profile) {
225 // We should only be able to get here if the profile already exists and
226 // has been created.
227 NOTREACHED();
228 return TRUE;
229 }
[email protected]0303f31c2009-02-02 06:42:05230
231 // Run the browser startup sequence again, with the command line of the
232 // signalling process.
233 BrowserInit::ProcessCommandLine(parsed_command_line, cur_dir, prefs, false,
234 profile, NULL);
[email protected]fc14cef2009-01-27 22:17:29235 return TRUE;
236 }
237 return TRUE;
238}
239
240LRESULT CALLBACK MessageWindow::WndProc(HWND hwnd, UINT message,
241 WPARAM wparam, LPARAM lparam) {
242 switch (message) {
243 case WM_COPYDATA:
244 return OnCopyData(reinterpret_cast<HWND>(wparam),
245 reinterpret_cast<COPYDATASTRUCT*>(lparam));
246 default:
247 break;
248 }
249
250 return ::DefWindowProc(hwnd, message, wparam, lparam);
251}
252
253void MessageWindow::HuntForZombieChromeProcesses() {
254 // Detecting dead renderers is simple:
255 // - The process is named chrome.exe.
256 // - The process' parent doesn't exist anymore.
257 // - The process doesn't have a chrome::kMessageWindowClass window.
258 // If these conditions hold, the process is a zombie renderer or plugin.
259
260 // Retrieve the list of browser processes on start. This list is then used to
261 // detect zombie renderer process or plugin process.
262 class ZombieDetector : public base::ProcessFilter {
263 public:
264 ZombieDetector() {
265 for (HWND window = NULL;;) {
266 window = FindWindowEx(HWND_MESSAGE,
267 window,
268 chrome::kMessageWindowClass,
269 NULL);
270 if (!window)
271 break;
272 DWORD process = 0;
273 GetWindowThreadProcessId(window, &process);
274 if (process)
275 browsers_.push_back(process);
276 }
277 // We are also a browser, regardless of having the window or not.
278 browsers_.push_back(::GetCurrentProcessId());
279 }
280
281 virtual bool Includes(uint32 pid, uint32 parent_pid) const {
282 // Don't kill ourself eh.
283 if (GetCurrentProcessId() == pid)
284 return false;
285
286 // Is this a browser? If so, ignore it.
287 if (std::find(browsers_.begin(), browsers_.end(), pid) != browsers_.end())
288 return false;
289
290 // Is the parent a browser? If so, ignore it.
291 if (std::find(browsers_.begin(), browsers_.end(), parent_pid)
292 != browsers_.end())
293 return false;
294
295 // The chrome process is orphan.
296 return true;
297 }
298
299 protected:
300 std::vector<uint32> browsers_;
301 };
302
303 ZombieDetector zombie_detector;
304 base::KillProcesses(L"chrome.exe", ResultCodes::HUNG, &zombie_detector);
305}