1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/plugins/ipc/PluginHangUIParent.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,439 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "PluginHangUI.h" 1.11 + 1.12 +#include "PluginHangUIParent.h" 1.13 + 1.14 +#include "mozilla/Telemetry.h" 1.15 +#include "mozilla/plugins/PluginModuleParent.h" 1.16 + 1.17 +#include "nsContentUtils.h" 1.18 +#include "nsDirectoryServiceDefs.h" 1.19 +#include "nsIFile.h" 1.20 +#include "nsIProperties.h" 1.21 +#include "nsIWindowMediator.h" 1.22 +#include "nsIWinTaskbar.h" 1.23 +#include "nsServiceManagerUtils.h" 1.24 +#include "nsThreadUtils.h" 1.25 + 1.26 +#include "WidgetUtils.h" 1.27 + 1.28 +#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" 1.29 + 1.30 +using base::ProcessHandle; 1.31 + 1.32 +using mozilla::widget::WidgetUtils; 1.33 + 1.34 +using std::string; 1.35 +using std::vector; 1.36 + 1.37 +namespace { 1.38 +class nsPluginHangUITelemetry : public nsRunnable 1.39 +{ 1.40 +public: 1.41 + nsPluginHangUITelemetry(int aResponseCode, int aDontAskCode, 1.42 + uint32_t aResponseTimeMs, uint32_t aTimeoutMs) 1.43 + : mResponseCode(aResponseCode), 1.44 + mDontAskCode(aDontAskCode), 1.45 + mResponseTimeMs(aResponseTimeMs), 1.46 + mTimeoutMs(aTimeoutMs) 1.47 + { 1.48 + } 1.49 + 1.50 + NS_IMETHOD 1.51 + Run() 1.52 + { 1.53 + mozilla::Telemetry::Accumulate( 1.54 + mozilla::Telemetry::PLUGIN_HANG_UI_USER_RESPONSE, mResponseCode); 1.55 + mozilla::Telemetry::Accumulate( 1.56 + mozilla::Telemetry::PLUGIN_HANG_UI_DONT_ASK, mDontAskCode); 1.57 + mozilla::Telemetry::Accumulate( 1.58 + mozilla::Telemetry::PLUGIN_HANG_UI_RESPONSE_TIME, mResponseTimeMs); 1.59 + mozilla::Telemetry::Accumulate( 1.60 + mozilla::Telemetry::PLUGIN_HANG_TIME, mTimeoutMs + mResponseTimeMs); 1.61 + return NS_OK; 1.62 + } 1.63 + 1.64 +private: 1.65 + int mResponseCode; 1.66 + int mDontAskCode; 1.67 + uint32_t mResponseTimeMs; 1.68 + uint32_t mTimeoutMs; 1.69 +}; 1.70 +} // anonymous namespace 1.71 + 1.72 +namespace mozilla { 1.73 +namespace plugins { 1.74 + 1.75 +PluginHangUIParent::PluginHangUIParent(PluginModuleParent* aModule, 1.76 + const int32_t aHangUITimeoutPref, 1.77 + const int32_t aChildTimeoutPref) 1.78 + : mMutex("mozilla::plugins::PluginHangUIParent::mMutex"), 1.79 + mModule(aModule), 1.80 + mTimeoutPrefMs(static_cast<uint32_t>(aHangUITimeoutPref) * 1000U), 1.81 + mIPCTimeoutMs(static_cast<uint32_t>(aChildTimeoutPref) * 1000U), 1.82 + mMainThreadMessageLoop(MessageLoop::current()), 1.83 + mIsShowing(false), 1.84 + mLastUserResponse(0), 1.85 + mHangUIProcessHandle(nullptr), 1.86 + mMainWindowHandle(nullptr), 1.87 + mRegWait(nullptr), 1.88 + mShowEvent(nullptr), 1.89 + mShowTicks(0), 1.90 + mResponseTicks(0) 1.91 +{ 1.92 +} 1.93 + 1.94 +PluginHangUIParent::~PluginHangUIParent() 1.95 +{ 1.96 + { // Scope for lock 1.97 + MutexAutoLock lock(mMutex); 1.98 + UnwatchHangUIChildProcess(true); 1.99 + } 1.100 + if (mShowEvent) { 1.101 + ::CloseHandle(mShowEvent); 1.102 + } 1.103 + if (mHangUIProcessHandle) { 1.104 + ::CloseHandle(mHangUIProcessHandle); 1.105 + } 1.106 +} 1.107 + 1.108 +bool 1.109 +PluginHangUIParent::DontShowAgain() const 1.110 +{ 1.111 + return (mLastUserResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); 1.112 +} 1.113 + 1.114 +bool 1.115 +PluginHangUIParent::WasLastHangStopped() const 1.116 +{ 1.117 + return (mLastUserResponse & HANGUI_USER_RESPONSE_STOP); 1.118 +} 1.119 + 1.120 +unsigned int 1.121 +PluginHangUIParent::LastShowDurationMs() const 1.122 +{ 1.123 + // We only return something if there was a user response 1.124 + if (!mLastUserResponse) { 1.125 + return 0; 1.126 + } 1.127 + return static_cast<unsigned int>(mResponseTicks - mShowTicks); 1.128 +} 1.129 + 1.130 +bool 1.131 +PluginHangUIParent::Init(const nsString& aPluginName) 1.132 +{ 1.133 + if (mHangUIProcessHandle) { 1.134 + return false; 1.135 + } 1.136 + 1.137 + nsresult rv; 1.138 + rv = mMiniShm.Init(this, ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); 1.139 + NS_ENSURE_SUCCESS(rv, false); 1.140 + nsCOMPtr<nsIProperties> 1.141 + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); 1.142 + if (!directoryService) { 1.143 + return false; 1.144 + } 1.145 + nsCOMPtr<nsIFile> greDir; 1.146 + rv = directoryService->Get(NS_GRE_DIR, 1.147 + NS_GET_IID(nsIFile), 1.148 + getter_AddRefs(greDir)); 1.149 + if (NS_FAILED(rv)) { 1.150 + return false; 1.151 + } 1.152 + nsAutoString path; 1.153 + greDir->GetPath(path); 1.154 + 1.155 + FilePath exePath(path.get()); 1.156 + exePath = exePath.AppendASCII(MOZ_HANGUI_PROCESS_NAME); 1.157 + CommandLine commandLine(exePath.value()); 1.158 + 1.159 + nsXPIDLString localizedStr; 1.160 + const char16_t* formatParams[] = { aPluginName.get() }; 1.161 + rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES, 1.162 + "PluginHangUIMessage", 1.163 + formatParams, 1.164 + localizedStr); 1.165 + if (NS_FAILED(rv)) { 1.166 + return false; 1.167 + } 1.168 + commandLine.AppendLooseValue(localizedStr.get()); 1.169 + 1.170 + const char* keys[] = { "PluginHangUITitle", 1.171 + "PluginHangUIWaitButton", 1.172 + "PluginHangUIStopButton", 1.173 + "DontAskAgain" }; 1.174 + for (unsigned int i = 0; i < ArrayLength(keys); ++i) { 1.175 + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, 1.176 + keys[i], 1.177 + localizedStr); 1.178 + if (NS_FAILED(rv)) { 1.179 + return false; 1.180 + } 1.181 + commandLine.AppendLooseValue(localizedStr.get()); 1.182 + } 1.183 + 1.184 + rv = GetHangUIOwnerWindowHandle(mMainWindowHandle); 1.185 + if (NS_FAILED(rv)) { 1.186 + return false; 1.187 + } 1.188 + nsAutoString hwndStr; 1.189 + hwndStr.AppendPrintf("%p", mMainWindowHandle); 1.190 + commandLine.AppendLooseValue(hwndStr.get()); 1.191 + 1.192 + ScopedHandle procHandle(::OpenProcess(SYNCHRONIZE, 1.193 + TRUE, 1.194 + GetCurrentProcessId())); 1.195 + if (!procHandle.IsValid()) { 1.196 + return false; 1.197 + } 1.198 + nsAutoString procHandleStr; 1.199 + procHandleStr.AppendPrintf("%p", procHandle.Get()); 1.200 + commandLine.AppendLooseValue(procHandleStr.get()); 1.201 + 1.202 + // On Win7+, pass the application user model to the child, so it can 1.203 + // register with it. This insures windows created by the Hang UI 1.204 + // properly group with the parent app on the Win7 taskbar. 1.205 + nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID); 1.206 + if (taskbarInfo) { 1.207 + bool isSupported = false; 1.208 + taskbarInfo->GetAvailable(&isSupported); 1.209 + nsAutoString appId; 1.210 + if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) { 1.211 + commandLine.AppendLooseValue(appId.get()); 1.212 + } else { 1.213 + commandLine.AppendLooseValue(L"-"); 1.214 + } 1.215 + } else { 1.216 + commandLine.AppendLooseValue(L"-"); 1.217 + } 1.218 + 1.219 + nsAutoString ipcTimeoutStr; 1.220 + ipcTimeoutStr.AppendInt(mIPCTimeoutMs); 1.221 + commandLine.AppendLooseValue(ipcTimeoutStr.get()); 1.222 + 1.223 + std::wstring ipcCookie; 1.224 + rv = mMiniShm.GetCookie(ipcCookie); 1.225 + if (NS_FAILED(rv)) { 1.226 + return false; 1.227 + } 1.228 + commandLine.AppendLooseValue(ipcCookie); 1.229 + 1.230 + ScopedHandle showEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); 1.231 + if (!showEvent.IsValid()) { 1.232 + return false; 1.233 + } 1.234 + mShowEvent = showEvent.Get(); 1.235 + 1.236 + MutexAutoLock lock(mMutex); 1.237 + STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; 1.238 + PROCESS_INFORMATION processInfo = { nullptr }; 1.239 + BOOL isProcessCreated = ::CreateProcess(exePath.value().c_str(), 1.240 + const_cast<wchar_t*>(commandLine.command_line_string().c_str()), 1.241 + nullptr, 1.242 + nullptr, 1.243 + TRUE, 1.244 + DETACHED_PROCESS, 1.245 + nullptr, 1.246 + nullptr, 1.247 + &startupInfo, 1.248 + &processInfo); 1.249 + if (isProcessCreated) { 1.250 + ::CloseHandle(processInfo.hThread); 1.251 + mHangUIProcessHandle = processInfo.hProcess; 1.252 + ::RegisterWaitForSingleObject(&mRegWait, 1.253 + processInfo.hProcess, 1.254 + &SOnHangUIProcessExit, 1.255 + this, 1.256 + INFINITE, 1.257 + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); 1.258 + ::WaitForSingleObject(mShowEvent, ::IsDebuggerPresent() ? INFINITE 1.259 + : mIPCTimeoutMs); 1.260 + // Setting this to true even if we time out on mShowEvent. This timeout 1.261 + // typically occurs when the machine is thrashing so badly that 1.262 + // plugin-hang-ui.exe is taking a while to start. If we didn't set 1.263 + // this to true, Firefox would keep spawning additional plugin-hang-ui 1.264 + // processes, which is not what we want. 1.265 + mIsShowing = true; 1.266 + } 1.267 + mShowEvent = nullptr; 1.268 + return !(!isProcessCreated); 1.269 +} 1.270 + 1.271 +// static 1.272 +VOID CALLBACK PluginHangUIParent::SOnHangUIProcessExit(PVOID aContext, 1.273 + BOOLEAN aIsTimer) 1.274 +{ 1.275 + PluginHangUIParent* object = static_cast<PluginHangUIParent*>(aContext); 1.276 + MutexAutoLock lock(object->mMutex); 1.277 + // If the Hang UI child process died unexpectedly, act as if the UI cancelled 1.278 + if (object->IsShowing()) { 1.279 + object->RecvUserResponse(HANGUI_USER_RESPONSE_CANCEL); 1.280 + // Firefox window was disabled automatically when the Hang UI was shown. 1.281 + // If plugin-hang-ui.exe was unexpectedly terminated, we need to re-enable. 1.282 + ::EnableWindow(object->mMainWindowHandle, TRUE); 1.283 + } 1.284 +} 1.285 + 1.286 +// A precondition for this function is that the caller has locked mMutex 1.287 +bool 1.288 +PluginHangUIParent::UnwatchHangUIChildProcess(bool aWait) 1.289 +{ 1.290 + mMutex.AssertCurrentThreadOwns(); 1.291 + if (mRegWait) { 1.292 + // If aWait is false then we want to pass a nullptr (i.e. default 1.293 + // constructor) completionEvent 1.294 + ScopedHandle completionEvent; 1.295 + if (aWait) { 1.296 + completionEvent.Set(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); 1.297 + if (!completionEvent.IsValid()) { 1.298 + return false; 1.299 + } 1.300 + } 1.301 + 1.302 + // if aWait == false and UnregisterWaitEx fails with ERROR_IO_PENDING, 1.303 + // it is okay to clear mRegWait; Windows is telling us that the wait's 1.304 + // callback is running but will be cleaned up once the callback returns. 1.305 + if (::UnregisterWaitEx(mRegWait, completionEvent) || 1.306 + !aWait && ::GetLastError() == ERROR_IO_PENDING) { 1.307 + mRegWait = nullptr; 1.308 + if (aWait) { 1.309 + // We must temporarily unlock mMutex while waiting for the registered 1.310 + // wait callback to complete, or else we could deadlock. 1.311 + MutexAutoUnlock unlock(mMutex); 1.312 + ::WaitForSingleObject(completionEvent, INFINITE); 1.313 + } 1.314 + return true; 1.315 + } 1.316 + } 1.317 + return false; 1.318 +} 1.319 + 1.320 +bool 1.321 +PluginHangUIParent::Cancel() 1.322 +{ 1.323 + MutexAutoLock lock(mMutex); 1.324 + bool result = mIsShowing && SendCancel(); 1.325 + if (result) { 1.326 + mIsShowing = false; 1.327 + } 1.328 + return result; 1.329 +} 1.330 + 1.331 +bool 1.332 +PluginHangUIParent::SendCancel() 1.333 +{ 1.334 + PluginHangUICommand* cmd = nullptr; 1.335 + nsresult rv = mMiniShm.GetWritePtr(cmd); 1.336 + if (NS_FAILED(rv)) { 1.337 + return false; 1.338 + } 1.339 + cmd->mCode = PluginHangUICommand::HANGUI_CMD_CANCEL; 1.340 + return NS_SUCCEEDED(mMiniShm.Send()); 1.341 +} 1.342 + 1.343 +// A precondition for this function is that the caller has locked mMutex 1.344 +bool 1.345 +PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse) 1.346 +{ 1.347 + mMutex.AssertCurrentThreadOwns(); 1.348 + if (!mIsShowing && !(aResponse & HANGUI_USER_RESPONSE_CANCEL)) { 1.349 + // Don't process a user response if a cancellation is already pending 1.350 + return true; 1.351 + } 1.352 + mLastUserResponse = aResponse; 1.353 + mResponseTicks = ::GetTickCount(); 1.354 + mIsShowing = false; 1.355 + // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel 1.356 + int responseCode; 1.357 + if (aResponse & HANGUI_USER_RESPONSE_STOP) { 1.358 + // User clicked Stop 1.359 + mModule->TerminateChildProcess(mMainThreadMessageLoop); 1.360 + responseCode = 1; 1.361 + } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) { 1.362 + // User clicked Continue 1.363 + responseCode = 2; 1.364 + } else { 1.365 + // Dialog was cancelled 1.366 + responseCode = 3; 1.367 + } 1.368 + int dontAskCode = (aResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN) ? 1 : 0; 1.369 + nsCOMPtr<nsIRunnable> workItem = new nsPluginHangUITelemetry(responseCode, 1.370 + dontAskCode, 1.371 + LastShowDurationMs(), 1.372 + mTimeoutPrefMs); 1.373 + NS_DispatchToMainThread(workItem, NS_DISPATCH_NORMAL); 1.374 + return true; 1.375 +} 1.376 + 1.377 +nsresult 1.378 +PluginHangUIParent::GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle) 1.379 +{ 1.380 + windowHandle = nullptr; 1.381 + 1.382 + nsresult rv; 1.383 + nsCOMPtr<nsIWindowMediator> winMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, 1.384 + &rv)); 1.385 + NS_ENSURE_SUCCESS(rv, rv); 1.386 + 1.387 + nsCOMPtr<nsIDOMWindow> navWin; 1.388 + rv = winMediator->GetMostRecentWindow(MOZ_UTF16("navigator:browser"), 1.389 + getter_AddRefs(navWin)); 1.390 + NS_ENSURE_SUCCESS(rv, rv); 1.391 + if (!navWin) { 1.392 + return NS_ERROR_FAILURE; 1.393 + } 1.394 + 1.395 + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(navWin); 1.396 + if (!widget) { 1.397 + return NS_ERROR_FAILURE; 1.398 + } 1.399 + 1.400 + windowHandle = reinterpret_cast<NativeWindowHandle>(widget->GetNativeData(NS_NATIVE_WINDOW)); 1.401 + if (!windowHandle) { 1.402 + return NS_ERROR_FAILURE; 1.403 + } 1.404 + 1.405 + return NS_OK; 1.406 +} 1.407 + 1.408 +void 1.409 +PluginHangUIParent::OnMiniShmEvent(MiniShmBase *aMiniShmObj) 1.410 +{ 1.411 + const PluginHangUIResponse* response = nullptr; 1.412 + nsresult rv = aMiniShmObj->GetReadPtr(response); 1.413 + NS_ASSERTION(NS_SUCCEEDED(rv), 1.414 + "Couldn't obtain read pointer OnMiniShmEvent"); 1.415 + if (NS_SUCCEEDED(rv)) { 1.416 + // The child process has returned a response so we shouldn't worry about 1.417 + // its state anymore. 1.418 + MutexAutoLock lock(mMutex); 1.419 + UnwatchHangUIChildProcess(false); 1.420 + RecvUserResponse(response->mResponseBits); 1.421 + } 1.422 +} 1.423 + 1.424 +void 1.425 +PluginHangUIParent::OnMiniShmConnect(MiniShmBase* aMiniShmObj) 1.426 +{ 1.427 + PluginHangUICommand* cmd = nullptr; 1.428 + nsresult rv = aMiniShmObj->GetWritePtr(cmd); 1.429 + NS_ASSERTION(NS_SUCCEEDED(rv), 1.430 + "Couldn't obtain write pointer OnMiniShmConnect"); 1.431 + if (NS_FAILED(rv)) { 1.432 + return; 1.433 + } 1.434 + cmd->mCode = PluginHangUICommand::HANGUI_CMD_SHOW; 1.435 + if (NS_SUCCEEDED(aMiniShmObj->Send())) { 1.436 + mShowTicks = ::GetTickCount(); 1.437 + } 1.438 + ::SetEvent(mShowEvent); 1.439 +} 1.440 + 1.441 +} // namespace plugins 1.442 +} // namespace mozilla