dom/plugins/ipc/PluginHangUIParent.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:8c5650c7f92c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "PluginHangUI.h"
8
9 #include "PluginHangUIParent.h"
10
11 #include "mozilla/Telemetry.h"
12 #include "mozilla/plugins/PluginModuleParent.h"
13
14 #include "nsContentUtils.h"
15 #include "nsDirectoryServiceDefs.h"
16 #include "nsIFile.h"
17 #include "nsIProperties.h"
18 #include "nsIWindowMediator.h"
19 #include "nsIWinTaskbar.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsThreadUtils.h"
22
23 #include "WidgetUtils.h"
24
25 #define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
26
27 using base::ProcessHandle;
28
29 using mozilla::widget::WidgetUtils;
30
31 using std::string;
32 using std::vector;
33
34 namespace {
35 class nsPluginHangUITelemetry : public nsRunnable
36 {
37 public:
38 nsPluginHangUITelemetry(int aResponseCode, int aDontAskCode,
39 uint32_t aResponseTimeMs, uint32_t aTimeoutMs)
40 : mResponseCode(aResponseCode),
41 mDontAskCode(aDontAskCode),
42 mResponseTimeMs(aResponseTimeMs),
43 mTimeoutMs(aTimeoutMs)
44 {
45 }
46
47 NS_IMETHOD
48 Run()
49 {
50 mozilla::Telemetry::Accumulate(
51 mozilla::Telemetry::PLUGIN_HANG_UI_USER_RESPONSE, mResponseCode);
52 mozilla::Telemetry::Accumulate(
53 mozilla::Telemetry::PLUGIN_HANG_UI_DONT_ASK, mDontAskCode);
54 mozilla::Telemetry::Accumulate(
55 mozilla::Telemetry::PLUGIN_HANG_UI_RESPONSE_TIME, mResponseTimeMs);
56 mozilla::Telemetry::Accumulate(
57 mozilla::Telemetry::PLUGIN_HANG_TIME, mTimeoutMs + mResponseTimeMs);
58 return NS_OK;
59 }
60
61 private:
62 int mResponseCode;
63 int mDontAskCode;
64 uint32_t mResponseTimeMs;
65 uint32_t mTimeoutMs;
66 };
67 } // anonymous namespace
68
69 namespace mozilla {
70 namespace plugins {
71
72 PluginHangUIParent::PluginHangUIParent(PluginModuleParent* aModule,
73 const int32_t aHangUITimeoutPref,
74 const int32_t aChildTimeoutPref)
75 : mMutex("mozilla::plugins::PluginHangUIParent::mMutex"),
76 mModule(aModule),
77 mTimeoutPrefMs(static_cast<uint32_t>(aHangUITimeoutPref) * 1000U),
78 mIPCTimeoutMs(static_cast<uint32_t>(aChildTimeoutPref) * 1000U),
79 mMainThreadMessageLoop(MessageLoop::current()),
80 mIsShowing(false),
81 mLastUserResponse(0),
82 mHangUIProcessHandle(nullptr),
83 mMainWindowHandle(nullptr),
84 mRegWait(nullptr),
85 mShowEvent(nullptr),
86 mShowTicks(0),
87 mResponseTicks(0)
88 {
89 }
90
91 PluginHangUIParent::~PluginHangUIParent()
92 {
93 { // Scope for lock
94 MutexAutoLock lock(mMutex);
95 UnwatchHangUIChildProcess(true);
96 }
97 if (mShowEvent) {
98 ::CloseHandle(mShowEvent);
99 }
100 if (mHangUIProcessHandle) {
101 ::CloseHandle(mHangUIProcessHandle);
102 }
103 }
104
105 bool
106 PluginHangUIParent::DontShowAgain() const
107 {
108 return (mLastUserResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN);
109 }
110
111 bool
112 PluginHangUIParent::WasLastHangStopped() const
113 {
114 return (mLastUserResponse & HANGUI_USER_RESPONSE_STOP);
115 }
116
117 unsigned int
118 PluginHangUIParent::LastShowDurationMs() const
119 {
120 // We only return something if there was a user response
121 if (!mLastUserResponse) {
122 return 0;
123 }
124 return static_cast<unsigned int>(mResponseTicks - mShowTicks);
125 }
126
127 bool
128 PluginHangUIParent::Init(const nsString& aPluginName)
129 {
130 if (mHangUIProcessHandle) {
131 return false;
132 }
133
134 nsresult rv;
135 rv = mMiniShm.Init(this, ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs);
136 NS_ENSURE_SUCCESS(rv, false);
137 nsCOMPtr<nsIProperties>
138 directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
139 if (!directoryService) {
140 return false;
141 }
142 nsCOMPtr<nsIFile> greDir;
143 rv = directoryService->Get(NS_GRE_DIR,
144 NS_GET_IID(nsIFile),
145 getter_AddRefs(greDir));
146 if (NS_FAILED(rv)) {
147 return false;
148 }
149 nsAutoString path;
150 greDir->GetPath(path);
151
152 FilePath exePath(path.get());
153 exePath = exePath.AppendASCII(MOZ_HANGUI_PROCESS_NAME);
154 CommandLine commandLine(exePath.value());
155
156 nsXPIDLString localizedStr;
157 const char16_t* formatParams[] = { aPluginName.get() };
158 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
159 "PluginHangUIMessage",
160 formatParams,
161 localizedStr);
162 if (NS_FAILED(rv)) {
163 return false;
164 }
165 commandLine.AppendLooseValue(localizedStr.get());
166
167 const char* keys[] = { "PluginHangUITitle",
168 "PluginHangUIWaitButton",
169 "PluginHangUIStopButton",
170 "DontAskAgain" };
171 for (unsigned int i = 0; i < ArrayLength(keys); ++i) {
172 rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
173 keys[i],
174 localizedStr);
175 if (NS_FAILED(rv)) {
176 return false;
177 }
178 commandLine.AppendLooseValue(localizedStr.get());
179 }
180
181 rv = GetHangUIOwnerWindowHandle(mMainWindowHandle);
182 if (NS_FAILED(rv)) {
183 return false;
184 }
185 nsAutoString hwndStr;
186 hwndStr.AppendPrintf("%p", mMainWindowHandle);
187 commandLine.AppendLooseValue(hwndStr.get());
188
189 ScopedHandle procHandle(::OpenProcess(SYNCHRONIZE,
190 TRUE,
191 GetCurrentProcessId()));
192 if (!procHandle.IsValid()) {
193 return false;
194 }
195 nsAutoString procHandleStr;
196 procHandleStr.AppendPrintf("%p", procHandle.Get());
197 commandLine.AppendLooseValue(procHandleStr.get());
198
199 // On Win7+, pass the application user model to the child, so it can
200 // register with it. This insures windows created by the Hang UI
201 // properly group with the parent app on the Win7 taskbar.
202 nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID);
203 if (taskbarInfo) {
204 bool isSupported = false;
205 taskbarInfo->GetAvailable(&isSupported);
206 nsAutoString appId;
207 if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) {
208 commandLine.AppendLooseValue(appId.get());
209 } else {
210 commandLine.AppendLooseValue(L"-");
211 }
212 } else {
213 commandLine.AppendLooseValue(L"-");
214 }
215
216 nsAutoString ipcTimeoutStr;
217 ipcTimeoutStr.AppendInt(mIPCTimeoutMs);
218 commandLine.AppendLooseValue(ipcTimeoutStr.get());
219
220 std::wstring ipcCookie;
221 rv = mMiniShm.GetCookie(ipcCookie);
222 if (NS_FAILED(rv)) {
223 return false;
224 }
225 commandLine.AppendLooseValue(ipcCookie);
226
227 ScopedHandle showEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
228 if (!showEvent.IsValid()) {
229 return false;
230 }
231 mShowEvent = showEvent.Get();
232
233 MutexAutoLock lock(mMutex);
234 STARTUPINFO startupInfo = { sizeof(STARTUPINFO) };
235 PROCESS_INFORMATION processInfo = { nullptr };
236 BOOL isProcessCreated = ::CreateProcess(exePath.value().c_str(),
237 const_cast<wchar_t*>(commandLine.command_line_string().c_str()),
238 nullptr,
239 nullptr,
240 TRUE,
241 DETACHED_PROCESS,
242 nullptr,
243 nullptr,
244 &startupInfo,
245 &processInfo);
246 if (isProcessCreated) {
247 ::CloseHandle(processInfo.hThread);
248 mHangUIProcessHandle = processInfo.hProcess;
249 ::RegisterWaitForSingleObject(&mRegWait,
250 processInfo.hProcess,
251 &SOnHangUIProcessExit,
252 this,
253 INFINITE,
254 WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE);
255 ::WaitForSingleObject(mShowEvent, ::IsDebuggerPresent() ? INFINITE
256 : mIPCTimeoutMs);
257 // Setting this to true even if we time out on mShowEvent. This timeout
258 // typically occurs when the machine is thrashing so badly that
259 // plugin-hang-ui.exe is taking a while to start. If we didn't set
260 // this to true, Firefox would keep spawning additional plugin-hang-ui
261 // processes, which is not what we want.
262 mIsShowing = true;
263 }
264 mShowEvent = nullptr;
265 return !(!isProcessCreated);
266 }
267
268 // static
269 VOID CALLBACK PluginHangUIParent::SOnHangUIProcessExit(PVOID aContext,
270 BOOLEAN aIsTimer)
271 {
272 PluginHangUIParent* object = static_cast<PluginHangUIParent*>(aContext);
273 MutexAutoLock lock(object->mMutex);
274 // If the Hang UI child process died unexpectedly, act as if the UI cancelled
275 if (object->IsShowing()) {
276 object->RecvUserResponse(HANGUI_USER_RESPONSE_CANCEL);
277 // Firefox window was disabled automatically when the Hang UI was shown.
278 // If plugin-hang-ui.exe was unexpectedly terminated, we need to re-enable.
279 ::EnableWindow(object->mMainWindowHandle, TRUE);
280 }
281 }
282
283 // A precondition for this function is that the caller has locked mMutex
284 bool
285 PluginHangUIParent::UnwatchHangUIChildProcess(bool aWait)
286 {
287 mMutex.AssertCurrentThreadOwns();
288 if (mRegWait) {
289 // If aWait is false then we want to pass a nullptr (i.e. default
290 // constructor) completionEvent
291 ScopedHandle completionEvent;
292 if (aWait) {
293 completionEvent.Set(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
294 if (!completionEvent.IsValid()) {
295 return false;
296 }
297 }
298
299 // if aWait == false and UnregisterWaitEx fails with ERROR_IO_PENDING,
300 // it is okay to clear mRegWait; Windows is telling us that the wait's
301 // callback is running but will be cleaned up once the callback returns.
302 if (::UnregisterWaitEx(mRegWait, completionEvent) ||
303 !aWait && ::GetLastError() == ERROR_IO_PENDING) {
304 mRegWait = nullptr;
305 if (aWait) {
306 // We must temporarily unlock mMutex while waiting for the registered
307 // wait callback to complete, or else we could deadlock.
308 MutexAutoUnlock unlock(mMutex);
309 ::WaitForSingleObject(completionEvent, INFINITE);
310 }
311 return true;
312 }
313 }
314 return false;
315 }
316
317 bool
318 PluginHangUIParent::Cancel()
319 {
320 MutexAutoLock lock(mMutex);
321 bool result = mIsShowing && SendCancel();
322 if (result) {
323 mIsShowing = false;
324 }
325 return result;
326 }
327
328 bool
329 PluginHangUIParent::SendCancel()
330 {
331 PluginHangUICommand* cmd = nullptr;
332 nsresult rv = mMiniShm.GetWritePtr(cmd);
333 if (NS_FAILED(rv)) {
334 return false;
335 }
336 cmd->mCode = PluginHangUICommand::HANGUI_CMD_CANCEL;
337 return NS_SUCCEEDED(mMiniShm.Send());
338 }
339
340 // A precondition for this function is that the caller has locked mMutex
341 bool
342 PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse)
343 {
344 mMutex.AssertCurrentThreadOwns();
345 if (!mIsShowing && !(aResponse & HANGUI_USER_RESPONSE_CANCEL)) {
346 // Don't process a user response if a cancellation is already pending
347 return true;
348 }
349 mLastUserResponse = aResponse;
350 mResponseTicks = ::GetTickCount();
351 mIsShowing = false;
352 // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel
353 int responseCode;
354 if (aResponse & HANGUI_USER_RESPONSE_STOP) {
355 // User clicked Stop
356 mModule->TerminateChildProcess(mMainThreadMessageLoop);
357 responseCode = 1;
358 } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) {
359 // User clicked Continue
360 responseCode = 2;
361 } else {
362 // Dialog was cancelled
363 responseCode = 3;
364 }
365 int dontAskCode = (aResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN) ? 1 : 0;
366 nsCOMPtr<nsIRunnable> workItem = new nsPluginHangUITelemetry(responseCode,
367 dontAskCode,
368 LastShowDurationMs(),
369 mTimeoutPrefMs);
370 NS_DispatchToMainThread(workItem, NS_DISPATCH_NORMAL);
371 return true;
372 }
373
374 nsresult
375 PluginHangUIParent::GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle)
376 {
377 windowHandle = nullptr;
378
379 nsresult rv;
380 nsCOMPtr<nsIWindowMediator> winMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID,
381 &rv));
382 NS_ENSURE_SUCCESS(rv, rv);
383
384 nsCOMPtr<nsIDOMWindow> navWin;
385 rv = winMediator->GetMostRecentWindow(MOZ_UTF16("navigator:browser"),
386 getter_AddRefs(navWin));
387 NS_ENSURE_SUCCESS(rv, rv);
388 if (!navWin) {
389 return NS_ERROR_FAILURE;
390 }
391
392 nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(navWin);
393 if (!widget) {
394 return NS_ERROR_FAILURE;
395 }
396
397 windowHandle = reinterpret_cast<NativeWindowHandle>(widget->GetNativeData(NS_NATIVE_WINDOW));
398 if (!windowHandle) {
399 return NS_ERROR_FAILURE;
400 }
401
402 return NS_OK;
403 }
404
405 void
406 PluginHangUIParent::OnMiniShmEvent(MiniShmBase *aMiniShmObj)
407 {
408 const PluginHangUIResponse* response = nullptr;
409 nsresult rv = aMiniShmObj->GetReadPtr(response);
410 NS_ASSERTION(NS_SUCCEEDED(rv),
411 "Couldn't obtain read pointer OnMiniShmEvent");
412 if (NS_SUCCEEDED(rv)) {
413 // The child process has returned a response so we shouldn't worry about
414 // its state anymore.
415 MutexAutoLock lock(mMutex);
416 UnwatchHangUIChildProcess(false);
417 RecvUserResponse(response->mResponseBits);
418 }
419 }
420
421 void
422 PluginHangUIParent::OnMiniShmConnect(MiniShmBase* aMiniShmObj)
423 {
424 PluginHangUICommand* cmd = nullptr;
425 nsresult rv = aMiniShmObj->GetWritePtr(cmd);
426 NS_ASSERTION(NS_SUCCEEDED(rv),
427 "Couldn't obtain write pointer OnMiniShmConnect");
428 if (NS_FAILED(rv)) {
429 return;
430 }
431 cmd->mCode = PluginHangUICommand::HANGUI_CMD_SHOW;
432 if (NS_SUCCEEDED(aMiniShmObj->Send())) {
433 mShowTicks = ::GetTickCount();
434 }
435 ::SetEvent(mShowEvent);
436 }
437
438 } // namespace plugins
439 } // namespace mozilla

mercurial