From ea9d8f367c448027c59187a102916673918b79ea Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Thu, 3 Jan 2013 22:24:07 -0500 Subject: [PATCH] Bug 805591 - Win32 implementation of the Plugin Hang UI. r=bsmedberg,bbondy --- browser/installer/package-manifest.in | 1 + dom/Makefile.in | 6 + dom/locales/en-US/chrome/dom/dom.properties | 4 + dom/plugins/ipc/Makefile.in | 13 + dom/plugins/ipc/MiniShmParent.cpp | 219 +++++++++++ dom/plugins/ipc/MiniShmParent.h | 96 +++++ dom/plugins/ipc/PluginHangUIParent.cpp | 348 ++++++++++++++++++ dom/plugins/ipc/PluginHangUIParent.h | 152 ++++++++ dom/plugins/ipc/PluginModuleParent.cpp | 203 +++++++++- dom/plugins/ipc/PluginModuleParent.h | 37 ++ dom/plugins/ipc/hangui/HangUIDlg.h | 18 + dom/plugins/ipc/hangui/HangUIDlg.rc | 26 ++ dom/plugins/ipc/hangui/Makefile.in | 39 ++ dom/plugins/ipc/hangui/MiniShmBase.h | 315 ++++++++++++++++ dom/plugins/ipc/hangui/MiniShmChild.cpp | 170 +++++++++ dom/plugins/ipc/hangui/MiniShmChild.h | 68 ++++ dom/plugins/ipc/hangui/PluginHangUI.h | 54 +++ dom/plugins/ipc/hangui/PluginHangUIChild.cpp | 289 +++++++++++++++ dom/plugins/ipc/hangui/PluginHangUIChild.h | 109 ++++++ dom/plugins/ipc/hangui/module.ver | 6 + .../ipc/hangui/plugin-hang-ui.exe.manifest | 35 ++ modules/libpref/src/init/all.js | 11 + toolkit/components/telemetry/Histograms.json | 9 + 23 files changed, 2217 insertions(+), 11 deletions(-) create mode 100644 dom/plugins/ipc/MiniShmParent.cpp create mode 100644 dom/plugins/ipc/MiniShmParent.h create mode 100644 dom/plugins/ipc/PluginHangUIParent.cpp create mode 100644 dom/plugins/ipc/PluginHangUIParent.h create mode 100644 dom/plugins/ipc/hangui/HangUIDlg.h create mode 100644 dom/plugins/ipc/hangui/HangUIDlg.rc create mode 100644 dom/plugins/ipc/hangui/Makefile.in create mode 100644 dom/plugins/ipc/hangui/MiniShmBase.h create mode 100644 dom/plugins/ipc/hangui/MiniShmChild.cpp create mode 100644 dom/plugins/ipc/hangui/MiniShmChild.h create mode 100644 dom/plugins/ipc/hangui/PluginHangUI.h create mode 100644 dom/plugins/ipc/hangui/PluginHangUIChild.cpp create mode 100644 dom/plugins/ipc/hangui/PluginHangUIChild.h create mode 100644 dom/plugins/ipc/hangui/module.ver create mode 100644 dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 73e7be16cdef..ebbc84ac9c74 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -78,6 +78,7 @@ @BINPATH@/@MOZ_CHILD_PROCESS_NAME@ #endif #ifdef XP_WIN32 +@BINPATH@/plugin-hang-ui@BIN_SUFFIX@ #ifndef MOZ_DEBUG #if MOZ_MSVC_REDIST == 1400 @BINPATH@/Microsoft.VC80.CRT.manifest diff --git a/dom/Makefile.in b/dom/Makefile.in index 47cd2534639a..b7433c2fab15 100644 --- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -78,6 +78,12 @@ PARALLEL_DIRS += \ audiochannel \ $(NULL) +ifeq (WINNT,$(OS_ARCH)) +PARALLEL_DIRS += \ + plugins/ipc/hangui \ + $(NULL) +endif + ifdef MOZ_B2G_RIL PARALLEL_DIRS += \ telephony \ diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index cad8698cfbe1..3b7d36b6e8f0 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -114,5 +114,9 @@ MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver MozSliceWarning=Use of mozSlice on the Blob object is deprecated. Use slice instead. # LOCALIZATION NOTE: Do not translate "Components" ComponentsWarning=The Components object is deprecated. It will soon be removed. +PluginHangUITitle=Warning: Unresponsive plugin +PluginHangUIMessage=%S may be busy, or it may have stopped responding. You can stop the plugin now, or you can continue to see if the plugin will complete. +PluginHangUIWaitButton=Continue +PluginHangUIStopButton=Stop plugin # LOCALIZATION NOTE: Do not translate "mozHidden", "mozVisibilityState", "hidden", or "visibilityState" PrefixedVisibilityApiWarning='mozHidden' and 'mozVisibilityState' are deprecated. Please use the unprefixed 'hidden' and 'visibilityState' instead. diff --git a/dom/plugins/ipc/Makefile.in b/dom/plugins/ipc/Makefile.in index 870c2713d437..9dde0b57a04c 100644 --- a/dom/plugins/ipc/Makefile.in +++ b/dom/plugins/ipc/Makefile.in @@ -94,6 +94,12 @@ ifeq (WINNT,$(OS_ARCH)) CPPSRCS += \ COMMessageFilter.cpp \ PluginSurfaceParent.cpp \ + MiniShmParent.cpp \ + PluginHangUIParent.cpp \ + $(NULL) + +DEFINES += \ + -DMOZ_HANGUI_PROCESS_NAME=\"plugin-hang-ui$(BIN_SUFFIX)\" \ $(NULL) EXPORTS_mozilla/plugins += \ @@ -121,6 +127,13 @@ LOCAL_INCLUDES = \ -I$(topsrcdir)/xpcom/base/ \ $(NULL) +ifeq (WINNT,$(OS_ARCH)) +LOCAL_INCLUDES += \ + -I$(srcdir)/hangui \ + -I$(topsrcdir)/widget/shared \ + $(NULL) +endif + include $(topsrcdir)/config/config.mk include $(topsrcdir)/ipc/chromium/chromium-config.mk diff --git a/dom/plugins/ipc/MiniShmParent.cpp b/dom/plugins/ipc/MiniShmParent.cpp new file mode 100644 index 000000000000..8633dcaf33ba --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.cpp @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MiniShmParent.h" + +#include "base/scoped_handle.h" + +#include + +namespace mozilla { +namespace plugins { + +// static +const unsigned int MiniShmParent::kDefaultMiniShmSectionSize = 0x1000; + +MiniShmParent::MiniShmParent() + : mSectionSize(0), + mParentEvent(NULL), + mParentGuard(NULL), + mChildEvent(NULL), + mChildGuard(NULL), + mRegWait(NULL), + mFileMapping(NULL), + mView(nullptr), + mIsConnected(false), + mTimeout(INFINITE) +{ +} + +MiniShmParent::~MiniShmParent() +{ + CleanUp(); +} + +void +MiniShmParent::CleanUp() +{ + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = NULL; + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + mParentEvent = NULL; + } + if (mParentGuard) { + ::CloseHandle(mParentGuard); + mParentGuard = NULL; + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + mChildEvent = NULL; + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + mChildGuard = NULL; + } + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + mFileMapping = NULL; + } +} + +nsresult +MiniShmParent::Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize) +{ + if (!aObserver || !aSectionSize || (aSectionSize % 0x1000) || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + SECURITY_ATTRIBUTES securityAttributes = {sizeof(securityAttributes), + nullptr, + TRUE}; + ScopedHandle parentEvent(::CreateEvent(&securityAttributes, + FALSE, + FALSE, + nullptr)); + if (!parentEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle parentGuard(::CreateEvent(&securityAttributes, + FALSE, + TRUE, + nullptr)); + if (!parentGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childEvent(::CreateEvent(&securityAttributes, + FALSE, + FALSE, + nullptr)); + if (!childEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childGuard(::CreateEvent(&securityAttributes, + FALSE, + TRUE, + nullptr)); + if (!childGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle mapping(::CreateFileMapping(INVALID_HANDLE_VALUE, + &securityAttributes, + PAGE_READWRITE, + 0, + aSectionSize, + nullptr)); + if (!mapping.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedMappedFileView view(::MapViewOfFile(mapping, + FILE_MAP_WRITE, + 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, aSectionSize, false); + NS_ENSURE_SUCCESS(rv, rv); + + MiniShmInit* initStruct = nullptr; + rv = GetWritePtrInternal(initStruct); + NS_ENSURE_SUCCESS(rv, rv); + initStruct->mParentEvent = parentEvent; + initStruct->mParentGuard = parentGuard; + initStruct->mChildEvent = childEvent; + initStruct->mChildGuard = childGuard; + + if (!::RegisterWaitForSingleObject(&mRegWait, + parentEvent, + &SOnEvent, + this, + INFINITE, + WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + mParentEvent = parentEvent.Take(); + mParentGuard = parentGuard.Take(); + mChildEvent = childEvent.Take(); + mChildGuard = childGuard.Take(); + mFileMapping = mapping.Take(); + mView = view.Take(); + mSectionSize = aSectionSize; + SetObserver(aObserver); + mTimeout = aTimeout; + return NS_OK; +} + +nsresult +MiniShmParent::GetCookie(std::wstring& cookie) +{ + if (!mFileMapping) { + return NS_ERROR_NOT_INITIALIZED; + } + std::wostringstream oss; + oss << mFileMapping; + if (!oss) { + return NS_ERROR_FAILURE; + } + cookie = oss.str(); + return NS_OK; +} + +nsresult +MiniShmParent::Send() +{ + if (!mChildEvent || !mChildGuard) { + return NS_ERROR_NOT_INITIALIZED; + } + if (::WaitForSingleObject(mChildGuard, mTimeout) != WAIT_OBJECT_0) { + return NS_ERROR_FAILURE; + } + if (!::SetEvent(mChildEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +bool +MiniShmParent::IsConnected() const +{ + return mIsConnected; +} + +void +MiniShmParent::OnEvent() +{ + if (mIsConnected) { + MiniShmBase::OnEvent(); + } else { + FinalizeConnection(); + } + ::SetEvent(mParentGuard); +} + +void +MiniShmParent::FinalizeConnection() +{ + const MiniShmInitComplete* initCompleteStruct = nullptr; + nsresult rv = GetReadPtr(initCompleteStruct); + mIsConnected = NS_SUCCEEDED(rv) && initCompleteStruct->mSucceeded; + if (mIsConnected) { + OnConnect(); + } +} + +} // namespace plugins +} // namespace mozilla + diff --git a/dom/plugins/ipc/MiniShmParent.h b/dom/plugins/ipc/MiniShmParent.h new file mode 100644 index 000000000000..e27c17328a8d --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmParent_h +#define mozilla_plugins_MiniShmParent_h + +#include "MiniShmBase.h" + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a parent + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it creates inheritable handles. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmChild + */ +class MiniShmParent : public MiniShmBase +{ +public: + MiniShmParent(); + virtual ~MiniShmParent(); + + static const unsigned int kDefaultMiniShmSectionSize; + + /** + * Initialize shared memory on the parent side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aTimeout Timeout in milliseconds. + * @param aSectionSize Desired size of the shared memory section. This is + * expected to be a multiple of 0x1000 (4KiB). + * @return nsresult error code + */ + nsresult + Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize = kDefaultMiniShmSectionSize); + + /** + * Destroys the shared memory section. Useful to explicitly release + * resources if it is known that they won't be needed again. + */ + void + CleanUp(); + + /** + * Provides a cookie string that should be passed to MiniShmChild + * during its initialization. + * + * @param aCookie A std::wstring variable to receive the cookie. + * @return nsresult error code + */ + nsresult + GetCookie(std::wstring& aCookie); + + virtual nsresult + Send() MOZ_OVERRIDE; + + bool + IsConnected() const; + +protected: + void + OnEvent() MOZ_OVERRIDE; + +private: + void + FinalizeConnection(); + + unsigned int mSectionSize; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mRegWait; + HANDLE mFileMapping; + LPVOID mView; + bool mIsConnected; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmParent_h + diff --git a/dom/plugins/ipc/PluginHangUIParent.cpp b/dom/plugins/ipc/PluginHangUIParent.cpp new file mode 100644 index 000000000000..2749f2ca2d5f --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.cpp @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginHangUI.h" + +#include "PluginHangUIParent.h" + +#include "mozilla/Telemetry.h" +#include "mozilla/plugins/PluginModuleParent.h" + +#include "nsContentUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsIWindowMediator.h" +#include "nsServiceManagerUtils.h" + +#include "WidgetUtils.h" + +using base::ProcessHandle; + +using mozilla::widget::WidgetUtils; + +using std::string; +using std::vector; + +namespace { +class nsPluginHangUITelemetry : public nsRunnable +{ +public: + nsPluginHangUITelemetry(int aResponseCode, int aDontAskCode) + : mResponseCode(aResponseCode), + mDontAskCode(aDontAskCode) + { + } + + NS_IMETHOD + Run() + { + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_USER_RESPONSE, mResponseCode); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLUGIN_HANG_UI_DONT_ASK, + mDontAskCode); + return NS_OK; + } + +private: + int mResponseCode; + int mDontAskCode; +}; +} // anonymous namespace + +namespace mozilla { +namespace plugins { + +const DWORD PluginHangUIParent::kTimeout = 5000U; + +PluginHangUIParent::PluginHangUIParent(PluginModuleParent* aModule) + : mModule(aModule), + mMainThreadMessageLoop(MessageLoop::current()), + mIsShowing(false), + mLastUserResponse(0), + mHangUIProcessHandle(NULL), + mMainWindowHandle(NULL), + mRegWait(NULL), + mShowEvent(NULL), + mShowTicks(0), + mResponseTicks(0) +{ +} + +PluginHangUIParent::~PluginHangUIParent() +{ + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + } + if (mShowEvent) { + ::CloseHandle(mShowEvent); + } + if (mHangUIProcessHandle) { + ::CloseHandle(mHangUIProcessHandle); + } +} + +bool +PluginHangUIParent::DontShowAgain() const +{ + return (mLastUserResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); +} + +bool +PluginHangUIParent::WasLastHangStopped() const +{ + return (mLastUserResponse & HANGUI_USER_RESPONSE_STOP); +} + +unsigned int +PluginHangUIParent::LastShowDurationMs() const +{ + // We only return something if there was a user response + if (!mLastUserResponse) { + return 0; + } + return static_cast(mResponseTicks - mShowTicks); +} + +bool +PluginHangUIParent::Init(const nsString& aPluginName) +{ + if (mHangUIProcessHandle) { + return false; + } + + nsresult rv; + rv = mMiniShm.Init(this, ::IsDebuggerPresent() ? INFINITE : kTimeout); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + return false; + } + nsCOMPtr greDir; + rv = directoryService->Get(NS_GRE_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString path; + greDir->GetPath(path); + + FilePath exePath(path.get()); + exePath = exePath.AppendASCII(MOZ_HANGUI_PROCESS_NAME); + CommandLine commandLine(exePath.value()); + + nsXPIDLString localizedStr; + const PRUnichar* formatParams[] = { aPluginName.get() }; + rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "PluginHangUIMessage", + formatParams, + localizedStr); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + + const char* keys[] = { "PluginHangUITitle", + "PluginHangUIWaitButton", + "PluginHangUIStopButton", + "DontAskAgain" }; + for (unsigned int i = 0; i < ArrayLength(keys); ++i) { + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + keys[i], + localizedStr); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + } + + rv = GetHangUIOwnerWindowHandle(mMainWindowHandle); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString hwndStr; + hwndStr.AppendPrintf("%p", mMainWindowHandle); + commandLine.AppendLooseValue(hwndStr.get()); + + ScopedHandle procHandle(::OpenProcess(SYNCHRONIZE, + TRUE, + GetCurrentProcessId())); + if (!procHandle.IsValid()) { + return false; + } + nsAutoString procHandleStr; + procHandleStr.AppendPrintf("%p", procHandle); + commandLine.AppendLooseValue(procHandleStr.get()); + + std::wstring ipcCookie; + rv = mMiniShm.GetCookie(ipcCookie); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(ipcCookie); + + mShowEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); + ScopedHandle showEvent(::CreateEvent(NULL, FALSE, FALSE, NULL)); + if (!showEvent.IsValid()) { + return false; + } + mShowEvent = showEvent.Get(); + + STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; + PROCESS_INFORMATION processInfo = { NULL }; + BOOL isProcessCreated = ::CreateProcess(exePath.value().c_str(), + const_cast(commandLine.command_line_string().c_str()), + nullptr, + nullptr, + TRUE, + DETACHED_PROCESS, + nullptr, + nullptr, + &startupInfo, + &processInfo); + if (isProcessCreated) { + ::CloseHandle(processInfo.hThread); + mHangUIProcessHandle = processInfo.hProcess; + ::RegisterWaitForSingleObject(&mRegWait, + processInfo.hProcess, + &SOnHangUIProcessExit, + this, + INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + ::WaitForSingleObject(mShowEvent, kTimeout); + } + mShowEvent = NULL; + return !(!isProcessCreated); +} + +// static +VOID CALLBACK PluginHangUIParent::SOnHangUIProcessExit(PVOID aContext, + BOOLEAN aIsTimer) +{ + PluginHangUIParent* object = static_cast(aContext); + // If the Hang UI child process died unexpectedly, act as if the UI cancelled + if (object->IsShowing()) { + object->RecvUserResponse(HANGUI_USER_RESPONSE_CANCEL); + // Firefox window was disabled automatically when the Hang UI was shown. + // If plugin-hang-ui.exe was unexpectedly terminated, we need to re-enable. + ::EnableWindow(object->mMainWindowHandle, TRUE); + } + object->mMiniShm.CleanUp(); +} + +bool +PluginHangUIParent::Cancel() +{ + bool result = mIsShowing && SendCancel(); + if (result) { + mIsShowing = false; + } + return result; +} + +bool +PluginHangUIParent::SendCancel() +{ + PluginHangUICommand* cmd = nullptr; + nsresult rv = mMiniShm.GetWritePtr(cmd); + if (NS_FAILED(rv)) { + return false; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_CANCEL; + return NS_SUCCEEDED(mMiniShm.Send()); +} + +bool +PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse) +{ + mLastUserResponse = aResponse; + mResponseTicks = GetTickCount(); + mIsShowing = false; + // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel + int responseCode; + if (aResponse & HANGUI_USER_RESPONSE_STOP) { + // User clicked Stop + mModule->TerminateChildProcess(mMainThreadMessageLoop); + responseCode = 1; + } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) { + // User clicked Continue + responseCode = 2; + } else { + // Dialog was cancelled + responseCode = 3; + } + int dontAskCode = (aResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN) ? 1 : 0; + nsCOMPtr workItem = new nsPluginHangUITelemetry(responseCode, + dontAskCode); + NS_DispatchToMainThread(workItem, NS_DISPATCH_NORMAL); + return true; +} + +nsresult +PluginHangUIParent::GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle) +{ + windowHandle = NULL; + + nsresult rv; + nsCOMPtr winMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr navWin; + rv = winMediator->GetMostRecentWindow(NS_LITERAL_STRING("navigator:browser").get(), + getter_AddRefs(navWin)); + NS_ENSURE_SUCCESS(rv, rv); + if (!navWin) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr widget = WidgetUtils::DOMWindowToWidget(navWin); + if (!widget) { + return NS_ERROR_FAILURE; + } + + windowHandle = reinterpret_cast(widget->GetNativeData(NS_NATIVE_WINDOW)); + if (!windowHandle) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +PluginHangUIParent::OnMiniShmEvent(MiniShmBase *aMiniShmObj) +{ + const PluginHangUIResponse* response = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(response); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Couldn't obtain read pointer OnMiniShmEvent"); + if (NS_SUCCEEDED(rv)) { + RecvUserResponse(response->mResponseBits); + } +} + +void +PluginHangUIParent::OnMiniShmConnect(MiniShmBase* aMiniShmObj) +{ + PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetWritePtr(cmd); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Couldn't obtain write pointer OnMiniShmConnect"); + if (NS_FAILED(rv)) { + return; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_SHOW; + mIsShowing = NS_SUCCEEDED(aMiniShmObj->Send()); + if (mIsShowing) { + mShowTicks = GetTickCount(); + } + ::SetEvent(mShowEvent); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginHangUIParent.h b/dom/plugins/ipc/PluginHangUIParent.h new file mode 100644 index 000000000000..d5ef0e915251 --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginHangUIParent_h +#define mozilla_plugins_PluginHangUIParent_h + +#include "nsString.h" + +#include "base/process.h" +#include "base/process_util.h" + +#include "mozilla/plugins/PluginMessageUtils.h" + +#include "MiniShmParent.h" + +namespace mozilla { +namespace plugins { + +class PluginModuleParent; + +/** + * This class is responsible for launching and communicating with the + * plugin-hang-ui process. + * + * NOTE: PluginHangUIParent is *not* an IPDL actor! In this case, "Parent" + * is describing the fact that firefox is the parent process to the + * plugin-hang-ui process, which is the PluginHangUIChild. + * PluginHangUIParent and PluginHangUIChild are a matched pair. + * @see PluginHangUIChild + */ +class PluginHangUIParent : public MiniShmObserver +{ +public: + PluginHangUIParent(PluginModuleParent* aModule); + virtual ~PluginHangUIParent(); + + /** + * Spawn the plugin-hang-ui.exe child process and terminate the given + * plugin container process if the user elects to stop the hung plugin. + * + * @param aPluginName Human-readable name of the affected plugin. + * @return true if the plugin hang ui process was successfully launched, + * otherwise false. + */ + bool + Init(const nsString& aPluginName); + + /** + * If the Plugin Hang UI is being shown, send a cancel notification to the + * Plugin Hang UI child process. + * + * @return true if the UI was shown and the cancel command was successfully + * sent to the child process, otherwise false. + */ + bool + Cancel(); + + /** + * Returns whether the Plugin Hang UI is currently being displayed. + * + * @return true if the Plugin Hang UI is showing, otherwise false. + */ + bool + IsShowing() const { return mIsShowing; } + + /** + * Returns whether this Plugin Hang UI instance has been shown. Note + * that this does not necessarily mean that the UI is showing right now. + * + * @return true if the Plugin Hang UI has shown, otherwise false. + */ + bool + WasShown() const { return mIsShowing || mLastUserResponse != 0; } + + /** + * Returns whether the user checked the "Don't ask me again" checkbox. + * + * @return true if the user does not want to see the Hang UI again. + */ + bool + DontShowAgain() const; + + /** + * Returns whether the user clicked stop during the last time that the + * Plugin Hang UI was displayed, if applicable. + * + * @return true if the UI was shown and the user chose to stop the + * plugin, otherwise false + */ + bool + WasLastHangStopped() const; + + /** + * @return unsigned int containing the response bits from the last + * time the Plugin Hang UI ran. + */ + unsigned int + LastUserResponse() const { return mLastUserResponse; } + + /** + * @return unsigned int containing the number of milliseconds that + * the Plugin Hang UI was displayed before the user responded. + * Returns 0 if the Plugin Hang UI has not been shown or was cancelled. + */ + unsigned int + LastShowDurationMs() const; + + virtual void + OnMiniShmEvent(MiniShmBase* aMiniShmObj) MOZ_OVERRIDE; + + virtual void + OnMiniShmConnect(MiniShmBase* aMiniShmObj) MOZ_OVERRIDE; + +private: + nsresult + GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle); + + bool + SendCancel(); + + bool + RecvUserResponse(const unsigned int& aResponse); + + static + VOID CALLBACK SOnHangUIProcessExit(PVOID aContext, BOOLEAN aIsTimer); + +private: + PluginModuleParent* mModule; + MessageLoop* mMainThreadMessageLoop; + volatile bool mIsShowing; + unsigned int mLastUserResponse; + base::ProcessHandle mHangUIProcessHandle; + NativeWindowHandle mMainWindowHandle; + HANDLE mRegWait; + HANDLE mShowEvent; + DWORD mShowTicks; + DWORD mResponseTicks; + MiniShmParent mMiniShm; + + static const DWORD kTimeout; + + DISALLOW_COPY_AND_ASSIGN(PluginHangUIParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUIParent_h + diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 35952c4b1154..c288b89762bc 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -35,6 +35,7 @@ #include "prsystem.h" #ifdef XP_WIN +#include "PluginHangUIParent.h" #include "mozilla/widget/AudioSession.h" #endif #include "sampler.h" @@ -59,6 +60,13 @@ using namespace CrashReporter; static const char kChildTimeoutPref[] = "dom.ipc.plugins.timeoutSecs"; static const char kParentTimeoutPref[] = "dom.ipc.plugins.parentTimeoutSecs"; static const char kLaunchTimeoutPref[] = "dom.ipc.plugins.processLaunchTimeoutSecs"; +#ifdef XP_WIN +static const char kHangUITimeoutPref[] = "dom.ipc.plugins.hangUITimeoutSecs"; +static const char kHangUIMinDisplayPref[] = "dom.ipc.plugins.hangUIMinDisplaySecs"; +#define CHILD_TIMEOUT_PREF kHangUITimeoutPref +#else +#define CHILD_TIMEOUT_PREF kChildTimeoutPref +#endif template<> struct RunnableMethodTraits @@ -87,7 +95,7 @@ PluginModuleParent::LoadModule(const char* aFilePath) parent->Open(parent->mSubprocess->GetChannel(), parent->mSubprocess->GetChildProcessHandle()); - TimeoutChanged(kChildTimeoutPref, parent); + TimeoutChanged(CHILD_TIMEOUT_PREF, parent); #ifdef MOZ_CRASHREPORTER // If this fails, we're having IPC troubles, and we're doomed anyways. @@ -111,6 +119,9 @@ PluginModuleParent::PluginModuleParent(const char* aFilePath) , mTaskFactory(this) #ifdef XP_WIN , mPluginCpuUsageOnHang() + , mHangUIParent(nullptr) + , mHangUIEnabled(true) + , mIsTimerReset(true) #endif #ifdef MOZ_CRASHREPORTER_INJECTOR , mFlashProcess1(0) @@ -123,6 +134,10 @@ PluginModuleParent::PluginModuleParent(const char* aFilePath) Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this); Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this); +#ifdef XP_WIN + Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this); + Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); +#endif } PluginModuleParent::~PluginModuleParent() @@ -150,6 +165,15 @@ PluginModuleParent::~PluginModuleParent() Preferences::UnregisterCallback(TimeoutChanged, kChildTimeoutPref, this); Preferences::UnregisterCallback(TimeoutChanged, kParentTimeoutPref, this); +#ifdef XP_WIN + Preferences::UnregisterCallback(TimeoutChanged, kHangUITimeoutPref, this); + Preferences::UnregisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); + + if (mHangUIParent) { + delete mHangUIParent; + mHangUIParent = nullptr; + } +#endif } #ifdef MOZ_CRASHREPORTER @@ -206,16 +230,29 @@ PluginModuleParent::WriteExtraDataForMinidump(AnnotationTable& notes) } #endif // MOZ_CRASHREPORTER +void +PluginModuleParent::SetChildTimeout(const int32_t aChildTimeout) +{ + int32_t timeoutMs = (aChildTimeout > 0) ? (1000 * aChildTimeout) : + SyncChannel::kNoTimeout; + SetReplyTimeoutMs(timeoutMs); +} + int PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule) { - NS_ASSERTION(NS_IsMainThread(), "Wrong thead!"); + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); +#ifndef XP_WIN if (!strcmp(aPref, kChildTimeoutPref)) { // The timeout value used by the parent for children int32_t timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0); - int32_t timeoutMs = (timeoutSecs > 0) ? (1000 * timeoutSecs) : - SyncChannel::kNoTimeout; - static_cast(aModule)->SetReplyTimeoutMs(timeoutMs); + static_cast(aModule)->SetChildTimeout(timeoutSecs); +#else + if (!strcmp(aPref, kChildTimeoutPref) || + !strcmp(aPref, kHangUIMinDisplayPref) || + !strcmp(aPref, kHangUITimeoutPref)) { + static_cast(aModule)->EvaluateHangUIState(true); +#endif // XP_WIN } else if (!strcmp(aPref, kParentTimeoutPref)) { // The timeout value used by the child for its parent int32_t timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0); @@ -308,6 +345,13 @@ GetProcessCpuUsage(const InfallibleTArray& processHandles, } } // anonymous namespace + +void +PluginModuleParent::ExitedCxxStack() +{ + FinishHangUI(); +} + #endif // #ifdef XP_WIN #ifdef MOZ_CRASHREPORTER_INJECTOR @@ -334,10 +378,36 @@ CreateFlashMinidump(DWORD processId, ThreadId childThread, bool PluginModuleParent::ShouldContinueFromReplyTimeout() { +#ifdef XP_WIN + if (LaunchHangUI()) { + return true; + } + // If LaunchHangUI returned false then we should proceed with the + // original plugin hang behaviour and kill the plugin container. + FinishHangUI(); +#endif // XP_WIN + TerminateChildProcess(MessageLoop::current()); + return false; +} + +void +PluginModuleParent::TerminateChildProcess(MessageLoop* aMsgLoop) +{ #ifdef MOZ_CRASHREPORTER CrashReporterParent* crashReporter = CrashReporter(); crashReporter->AnnotateCrashReport(NS_LITERAL_CSTRING("PluginHang"), NS_LITERAL_CSTRING("1")); +#ifdef XP_WIN + if (mHangUIParent) { + unsigned int hangUIDuration = mHangUIParent->LastShowDurationMs(); + if (hangUIDuration) { + nsPrintfCString strHangUIDuration("%u", hangUIDuration); + crashReporter->AnnotateCrashReport( + NS_LITERAL_CSTRING("PluginHangUIDuration"), + strHangUIDuration); + } + } +#endif // XP_WIN if (crashReporter->GeneratePairedMinidump(this)) { mPluginDumpID = crashReporter->ChildDumpID(); PLUGIN_LOG_DEBUG( @@ -395,17 +465,128 @@ PluginModuleParent::ShouldContinueFromReplyTimeout() // this must run before the error notification from the channel, // or not at all - MessageLoop::current()->PostTask( - FROM_HERE, - mTaskFactory.NewRunnableMethod( - &PluginModuleParent::CleanupFromTimeout)); + if (aMsgLoop == MessageLoop::current()) { + aMsgLoop->PostTask( + FROM_HERE, + mTaskFactory.NewRunnableMethod( + &PluginModuleParent::CleanupFromTimeout)); + } else { + // If we're posting from a different thread we can't create + // the task via mTaskFactory + aMsgLoop->PostTask(FROM_HERE, + NewRunnableMethod(this, + &PluginModuleParent::CleanupFromTimeout)); + } if (!KillProcess(OtherProcess(), 1, false)) NS_WARNING("failed to kill subprocess!"); - - return false; } +#ifdef XP_WIN +void +PluginModuleParent::EvaluateHangUIState(const bool aReset) +{ + int32_t minDispSecs = Preferences::GetInt(kHangUIMinDisplayPref, 10); + int32_t autoStopSecs = Preferences::GetInt(kChildTimeoutPref, 0); + int32_t timeoutSecs = 0; + if (autoStopSecs > 0 && autoStopSecs < minDispSecs) { + /* If we're going to automatically terminate the plugin within a + time frame shorter than minDispSecs, there's no point in + showing the hang UI; it would just flash briefly on the screen. */ + mHangUIEnabled = false; + } else { + timeoutSecs = Preferences::GetInt(kHangUITimeoutPref, 0); + mHangUIEnabled = timeoutSecs > 0; + } + if (mHangUIEnabled) { + if (aReset) { + mIsTimerReset = true; + SetChildTimeout(timeoutSecs); + return; + } else if (mIsTimerReset) { + /* The Hang UI is being shown, so now we're setting the + timeout to kChildTimeoutPref while we wait for a user + response. ShouldContinueFromReplyTimeout will fire + after (reply timeout / 2) seconds, which is not what + we want. Doubling the timeout value here so that we get + the right result. */ + autoStopSecs *= 2; + } + } + mIsTimerReset = false; + SetChildTimeout(autoStopSecs); +} + +bool +PluginModuleParent::GetPluginName(nsAString& aPluginName) +{ + nsPluginHost* host = nsPluginHost::GetInst(); + if (!host) { + return false; + } + nsPluginTag* pluginTag = host->TagForPlugin(mPlugin); + if (!pluginTag) { + return false; + } + CopyUTF8toUTF16(pluginTag->mName, aPluginName); + return true; +} + +bool +PluginModuleParent::LaunchHangUI() +{ + if (!mHangUIEnabled) { + return false; + } + if (mHangUIParent) { + if (mHangUIParent->IsShowing()) { + // We've already shown the UI but the timeout has expired again. + return false; + } + if (mHangUIParent->DontShowAgain()) { + return !mHangUIParent->WasLastHangStopped(); + } + delete mHangUIParent; + mHangUIParent = nullptr; + } + mHangUIParent = new PluginHangUIParent(this); + nsAutoString pluginName; + if (!GetPluginName(pluginName)) { + return false; + } + bool retval = mHangUIParent->Init(pluginName); + if (retval) { + /* Once the UI is shown we switch the timeout over to use + kChildTimeoutPref, allowing us to terminate a hung plugin + after kChildTimeoutPref seconds if the user doesn't respond to + the hang UI. */ + EvaluateHangUIState(false); + } + return retval; +} + +void +PluginModuleParent::FinishHangUI() +{ + if (mHangUIEnabled && mHangUIParent) { + bool needsCancel = mHangUIParent->IsShowing(); + // If we're still showing, send a Cancel notification + if (needsCancel) { + mHangUIParent->Cancel(); + } + /* If we cancelled the UI or if the user issued a response, + we need to reset the child process timeout. */ + if (needsCancel || + !mIsTimerReset && mHangUIParent->WasShown()) { + /* We changed the timeout to kChildTimeoutPref when the plugin hang + UI was displayed. Now that we're finishing the UI, we need to + switch it back to kHangUITimeoutPref. */ + EvaluateHangUIState(true); + } + } +} +#endif // XP_WIN + #ifdef MOZ_CRASHREPORTER CrashReporterParent* PluginModuleParent::CrashReporter() diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h index 2005929d7bbc..e3ad28a9bdf5 100644 --- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -45,6 +45,10 @@ namespace plugins { class BrowserStreamParent; +#ifdef XP_WIN +class PluginHangUIParent; +#endif + /** * PluginModuleParent * @@ -130,6 +134,13 @@ public: void ProcessRemoteNativeEventsInRPCCall(); + void TerminateChildProcess(MessageLoop* aMsgLoop); + +#ifdef XP_WIN + void + ExitedCxxStack() MOZ_OVERRIDE; +#endif // XP_WIN + protected: virtual mozilla::ipc::RPCChannel::RacyRPCPolicy MediateRPCRace(const Message& parent, const Message& child) MOZ_OVERRIDE @@ -286,6 +297,7 @@ private: void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes); #endif void CleanupFromTimeout(); + void SetChildTimeout(const int32_t aChildTimeout); static int TimeoutChanged(const char* aPref, void* aModule); void NotifyPluginCrashed(); @@ -304,6 +316,31 @@ private: nsString mHangID; #ifdef XP_WIN InfallibleTArray mPluginCpuUsageOnHang; + PluginHangUIParent *mHangUIParent; + bool mHangUIEnabled; + bool mIsTimerReset; + + void + EvaluateHangUIState(const bool aReset); + + bool + GetPluginName(nsAString& aPluginName); + + /** + * Launches the Plugin Hang UI. + * + * @return true if plugin-hang-ui.exe has been successfully launched. + * false if the Plugin Hang UI is disabled, already showing, + * or the launch failed. + */ + bool + LaunchHangUI(); + + /** + * Finishes the Plugin Hang UI and cancels if it is being shown to the user. + */ + void + FinishHangUI(); #endif #ifdef MOZ_X11 diff --git a/dom/plugins/ipc/hangui/HangUIDlg.h b/dom/plugins/ipc/hangui/HangUIDlg.h new file mode 100644 index 000000000000..2ea4250a299a --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_HangUIDlg_h +#define mozilla_plugins_HangUIDlg_h + +#define IDD_HANGUIDLG 102 +#define IDC_MSG 1000 +#define IDC_CONTINUE 1001 +#define IDC_STOP 1002 +#define IDC_NOFUTURE 1003 +#define IDC_ICON 1004 + +#endif // mozilla_plugins_HangUIDlg_h + diff --git a/dom/plugins/ipc/hangui/HangUIDlg.rc b/dom/plugins/ipc/hangui/HangUIDlg.rc new file mode 100644 index 000000000000..840780586ccc --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.rc @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HangUIDlg.h" +#include + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_HANGUIDLG DIALOGEX 0, 0, 400, 75 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Continue",IDC_CONTINUE,283,51,50,18 + PUSHBUTTON "Stop",IDC_STOP,341,51,50,18 + CONTROL "Check1",IDC_NOFUTURE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,37,32,354,10 + LTEXT "Static",IDC_MSG,37,7,353,24 + ICON "",IDC_ICON,7,7,20,20 +END + diff --git a/dom/plugins/ipc/hangui/Makefile.in b/dom/plugins/ipc/hangui/Makefile.in new file mode 100644 index 000000000000..a759dbdb22a8 --- /dev/null +++ b/dom/plugins/ipc/hangui/Makefile.in @@ -0,0 +1,39 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +FAIL_ON_WARNINGS := 1 + +include $(DEPTH)/config/autoconf.mk + +CPPSRCS = \ + MiniShmChild.cpp \ + PluginHangUIChild.cpp \ + $(NULL) + +PROGRAM = plugin-hang-ui$(BIN_SUFFIX) + +OS_LIBS = comctl32.lib + +RCINCLUDE = HangUIDlg.rc + +include $(topsrcdir)/config/config.mk + +DEFINES += \ + -DNS_NO_XPCOM \ + $(NULL) + +STL_FLAGS = \ + -D_HAS_EXCEPTIONS=0 \ + $(NULL) + +MOZ_GLUE_LDFLAGS = + +include $(topsrcdir)/ipc/chromium/chromium-config.mk + +include $(topsrcdir)/config/rules.mk + diff --git a/dom/plugins/ipc/hangui/MiniShmBase.h b/dom/plugins/ipc/hangui/MiniShmBase.h new file mode 100644 index 000000000000..ac1203e08c51 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmBase.h @@ -0,0 +1,315 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmBase_h +#define mozilla_plugins_MiniShmBase_h + +#include "base/basictypes.h" + +#include "nsDebug.h" + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class is used to provide RAII semantics for mapped views. + * @see ScopedHandle + */ +class ScopedMappedFileView +{ +public: + explicit + ScopedMappedFileView(LPVOID aView) + : mView(aView) + { + } + + ~ScopedMappedFileView() + { + Close(); + } + + void + Close() + { + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + } + + void + Set(LPVOID aView) + { + Close(); + mView = aView; + } + + LPVOID + Get() const + { + return mView; + } + + LPVOID + Take() + { + LPVOID result = mView; + mView = nullptr; + return result; + } + + operator LPVOID() + { + return mView; + } + + bool + IsValid() const + { + return (mView); + } + +private: + DISALLOW_COPY_AND_ASSIGN(ScopedMappedFileView); + + LPVOID mView; +}; + +class MiniShmBase; + +class MiniShmObserver +{ +public: + /** + * This function is called whenever there is a new shared memory request. + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmEvent(MiniShmBase *aMiniShmObj) = 0; + /** + * This function is called once when a MiniShmParent and a MiniShmChild + * object have successfully negotiated a connection. + * + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmConnect(MiniShmBase *aMiniShmObj) { } +}; + +/** + * Base class for MiniShm connections. This class defines the common + * interfaces and code between parent and child. + */ +class MiniShmBase +{ +public: + /** + * Obtains a writable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template nsresult + GetWritePtr(T*& aPtr) + { + if (!mWriteHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + T::identifier <= RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast(mWriteHeader + 1); + return NS_OK; + } + + /** + * Obtains a readable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm or if + * type T does not match the type of the data + * stored in shared memory. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template nsresult + GetReadPtr(const T*& aPtr) + { + if (!mReadHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (mReadHeader->mId != T::identifier || + sizeof(T) != mReadHeader->mPayloadLen) { + return NS_ERROR_ILLEGAL_VALUE; + } + aPtr = reinterpret_cast(mReadHeader + 1); + return NS_OK; + } + + /** + * Fires the peer's event causing its request handler to execute. + * + * @return Should return NS_OK if the send was successful. + */ + virtual nsresult + Send() = 0; + +protected: + /** + * MiniShm reserves some identifier codes for its own use. Any + * identifiers used by MiniShm protocol implementations must be + * greater than RESERVED_CODE_LAST. + */ + enum ReservedCodes + { + RESERVED_CODE_INIT = 0, + RESERVED_CODE_INIT_COMPLETE = 1, + RESERVED_CODE_LAST = RESERVED_CODE_INIT_COMPLETE + }; + + struct MiniShmHeader + { + unsigned int mId; + unsigned int mPayloadLen; + }; + + struct MiniShmInit + { + enum identifier_t + { + identifier = RESERVED_CODE_INIT + }; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + }; + + struct MiniShmInitComplete + { + enum identifier_t + { + identifier = RESERVED_CODE_INIT_COMPLETE + }; + bool mSucceeded; + }; + + MiniShmBase() + : mObserver(nullptr), + mWriteHeader(nullptr), + mReadHeader(nullptr), + mPayloadMaxLen(0) + { + } + virtual ~MiniShmBase() + { } + + virtual void + OnEvent() + { + if (mObserver) { + mObserver->OnMiniShmEvent(this); + } + } + + virtual void + OnConnect() + { + if (mObserver) { + mObserver->OnMiniShmConnect(this); + } + } + + nsresult + SetView(LPVOID aView, const unsigned int aSize, bool aIsChild) + { + if (!aView || aSize <= 2 * sizeof(MiniShmHeader)) { + return NS_ERROR_ILLEGAL_VALUE; + } + // Divide the region into halves for parent and child + if (aIsChild) { + mReadHeader = static_cast(aView); + mWriteHeader = reinterpret_cast(static_cast(aView) + + aSize / 2U); + } else { + mWriteHeader = static_cast(aView); + mReadHeader = reinterpret_cast(static_cast(aView) + + aSize / 2U); + } + mPayloadMaxLen = aSize / 2U - sizeof(MiniShmHeader); + return NS_OK; + } + + inline void + SetObserver(MiniShmObserver *aObserver) { mObserver = aObserver; } + + /** + * Obtains a writable pointer into shared memory of type T. This version + * differs from GetWritePtr in that it allows typename T to be one of + * the private data structures declared in MiniShmBase. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T not an internal MiniShm struct. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template nsresult + GetWritePtrInternal(T*& aPtr) + { + if (!mWriteHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + T::identifier > RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast(mWriteHeader + 1); + return NS_OK; + } + + static VOID CALLBACK + SOnEvent(PVOID aContext, BOOLEAN aIsTimer) + { + MiniShmBase* object = static_cast(aContext); + object->OnEvent(); + } + +private: + MiniShmObserver* mObserver; + MiniShmHeader* mWriteHeader; + MiniShmHeader* mReadHeader; + unsigned int mPayloadMaxLen; + + DISALLOW_COPY_AND_ASSIGN(MiniShmBase); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmBase_h + diff --git a/dom/plugins/ipc/hangui/MiniShmChild.cpp b/dom/plugins/ipc/hangui/MiniShmChild.cpp new file mode 100644 index 000000000000..2fd1a6bbfd33 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MiniShmChild.h" + +#include +#include + +namespace mozilla { +namespace plugins { + +MiniShmChild::MiniShmChild() + : mParentEvent(NULL), + mParentGuard(NULL), + mChildEvent(NULL), + mChildGuard(NULL), + mFileMapping(NULL), + mRegWait(NULL), + mView(nullptr), + mTimeout(INFINITE) +{} + +MiniShmChild::~MiniShmChild() +{ + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + } + if (mParentGuard) { + ::CloseHandle(mParentGuard); + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + } + if (mView) { + ::UnmapViewOfFile(mView); + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + } +} + +nsresult +MiniShmChild::Init(MiniShmObserver* aObserver, const std::wstring& aCookie, + const DWORD aTimeout) +{ + if (aCookie.empty() || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + std::wistringstream iss(aCookie); + HANDLE mapHandle = NULL; + iss >> mapHandle; + if (!iss) { + return NS_ERROR_ILLEGAL_VALUE; + } + ScopedMappedFileView view(::MapViewOfFile(mapHandle, + FILE_MAP_WRITE, + 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + MEMORY_BASIC_INFORMATION memInfo = {0}; + SIZE_T querySize = ::VirtualQuery(view, &memInfo, sizeof(memInfo)); + unsigned int mappingSize = 0; + if (querySize) { + if (memInfo.RegionSize <= std::numeric_limits::max()) { + mappingSize = static_cast(memInfo.RegionSize); + } + } + if (!querySize || !mappingSize) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, mappingSize, true); + if (NS_FAILED(rv)) { + return rv; + } + + const MiniShmInit* initStruct = nullptr; + rv = GetReadPtr(initStruct); + if (NS_FAILED(rv)) { + return rv; + } + if (!initStruct->mParentEvent || !initStruct->mParentGuard || + !initStruct->mChildEvent || !initStruct->mChildGuard) { + return NS_ERROR_FAILURE; + } + if (!::RegisterWaitForSingleObject(&mRegWait, + initStruct->mChildEvent, + &SOnEvent, + this, + INFINITE, + WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + MiniShmInitComplete* initCompleteStruct = nullptr; + rv = GetWritePtrInternal(initCompleteStruct); + if (NS_FAILED(rv)) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = NULL; + return NS_ERROR_FAILURE; + } + + initCompleteStruct->mSucceeded = true; + + // We must set the member variables before we signal the event + mFileMapping = mapHandle; + mView = view.Take(); + mParentEvent = initStruct->mParentEvent; + mParentGuard = initStruct->mParentGuard; + mChildEvent = initStruct->mChildEvent; + mChildGuard = initStruct->mChildGuard; + SetObserver(aObserver); + mTimeout = aTimeout; + + rv = Send(); + if (NS_FAILED(rv)) { + initCompleteStruct->mSucceeded = false; + mFileMapping = NULL; + view.Set(mView); + mView = nullptr; + mParentEvent = NULL; + mParentGuard = NULL; + mChildEvent = NULL; + mChildGuard = NULL; + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = NULL; + return rv; + } + + OnConnect(); + return NS_OK; +} + +nsresult +MiniShmChild::Send() +{ + if (!mParentEvent || !mParentGuard) { + return NS_ERROR_NOT_INITIALIZED; + } + if (::WaitForSingleObject(mParentGuard, mTimeout) != WAIT_OBJECT_0) { + return NS_ERROR_FAILURE; + } + if (!::SetEvent(mParentEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void +MiniShmChild::OnEvent() +{ + MiniShmBase::OnEvent(); + ::SetEvent(mChildGuard); +} + +} // namespace plugins +} // namespace mozilla + diff --git a/dom/plugins/ipc/hangui/MiniShmChild.h b/dom/plugins/ipc/hangui/MiniShmChild.h new file mode 100644 index 000000000000..b6660103355e --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmChild_h +#define mozilla_plugins_MiniShmChild_h + +#include "MiniShmBase.h" + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a child + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it inherits handles from the parent process. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmParent + */ +class MiniShmChild : public MiniShmBase +{ +public: + MiniShmChild(); + virtual ~MiniShmChild(); + + /** + * Initialize shared memory on the child side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aCookie Cookie obtained from MiniShmParent::GetCookie + * @param aTimeout Timeout in milliseconds. + * @return nsresult error code + */ + nsresult + Init(MiniShmObserver* aObserver, const std::wstring& aCookie, + const DWORD aTimeout); + + virtual nsresult + Send() MOZ_OVERRIDE; + +protected: + void + OnEvent() MOZ_OVERRIDE; + +private: + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mFileMapping; + HANDLE mRegWait; + LPVOID mView; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmChild_h + diff --git a/dom/plugins/ipc/hangui/PluginHangUI.h b/dom/plugins/ipc/hangui/PluginHangUI.h new file mode 100644 index 000000000000..2c6df78bb209 --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUI.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginHangUI_h +#define mozilla_plugins_PluginHangUI_h + +namespace mozilla { +namespace plugins { + +enum HangUIUserResponse +{ + HANGUI_USER_RESPONSE_CANCEL = 1, + HANGUI_USER_RESPONSE_CONTINUE = 2, + HANGUI_USER_RESPONSE_STOP = 4, + HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN = 8 +}; + +enum PluginHangUIStructID +{ + PLUGIN_HANGUI_COMMAND = 0x10, + PLUGIN_HANGUI_RESULT +}; + +struct PluginHangUICommand +{ + enum + { + identifier = PLUGIN_HANGUI_COMMAND + }; + enum CmdCode + { + HANGUI_CMD_SHOW = 1, + HANGUI_CMD_CANCEL = 2 + }; + CmdCode mCode; +}; + +struct PluginHangUIResponse +{ + enum + { + identifier = PLUGIN_HANGUI_RESULT + }; + unsigned int mResponseBits; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUI_h + diff --git a/dom/plugins/ipc/hangui/PluginHangUIChild.cpp b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp new file mode 100644 index 000000000000..c27ea86d756e --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp @@ -0,0 +1,289 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginHangUI.h" + +#include "PluginHangUIChild.h" +#include "HangUIDlg.h" + +#include +#include +#include +#include + +namespace mozilla { +namespace plugins { + +PluginHangUIChild* PluginHangUIChild::sSelf = nullptr; +const int PluginHangUIChild::kExpectedMinimumArgc = 9; +const DWORD PluginHangUIChild::kProcessTimeout = 1200000U; +const DWORD PluginHangUIChild::kShmTimeout = 5000U; + +PluginHangUIChild::PluginHangUIChild() + : mResponseBits(0), + mParentWindow(NULL), + mDlgHandle(NULL), + mMainThread(NULL), + mParentProcess(NULL), + mRegWaitProcess(NULL) +{ +} + +PluginHangUIChild::~PluginHangUIChild() +{ + if (mMainThread) { + CloseHandle(mMainThread); + } + if (mRegWaitProcess) { + UnregisterWaitEx(mRegWaitProcess, INVALID_HANDLE_VALUE); + } + if (mParentProcess) { + CloseHandle(mParentProcess); + } + sSelf = nullptr; +} + +bool +PluginHangUIChild::Init(int aArgc, wchar_t* aArgv[]) +{ + if (aArgc < kExpectedMinimumArgc) { + return false; + } + unsigned int i = 1; + mMessageText = aArgv[i]; + mWindowTitle = aArgv[++i]; + mWaitBtnText = aArgv[++i]; + mKillBtnText = aArgv[++i]; + mNoFutureText = aArgv[++i]; + std::wistringstream issHwnd(aArgv[++i]); + issHwnd >> reinterpret_cast(mParentWindow); + if (!issHwnd) { + return false; + } + std::wistringstream issProc(aArgv[++i]); + issProc >> mParentProcess; + if (!issProc) { + return false; + } + + nsresult rv = mMiniShm.Init(this, + std::wstring(aArgv[++i]), + IsDebuggerPresent() ? INFINITE : kShmTimeout); + if (NS_FAILED(rv)) { + return false; + } + sSelf = this; + return true; +} + +void +PluginHangUIChild::OnMiniShmEvent(MiniShmBase* aMiniShmObj) +{ + const PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(cmd); + assert(NS_SUCCEEDED(rv)); + bool returnStatus = false; + if (NS_SUCCEEDED(rv)) { + switch (cmd->mCode) { + case PluginHangUICommand::HANGUI_CMD_SHOW: + returnStatus = RecvShow(); + break; + case PluginHangUICommand::HANGUI_CMD_CANCEL: + returnStatus = RecvCancel(); + break; + default: + break; + } + } +} + +// static +INT_PTR CALLBACK +PluginHangUIChild::SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, + WPARAM aWParam, LPARAM aLParam) +{ + PluginHangUIChild *self = PluginHangUIChild::sSelf; + if (self) { + return self->HangUIDlgProc(aDlgHandle, aMsgCode, aWParam, aLParam); + } + return FALSE; +} + +INT_PTR +PluginHangUIChild::HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, + LPARAM aLParam) +{ + mDlgHandle = aDlgHandle; + switch (aMsgCode) { + case WM_INITDIALOG: { + // Disentangle our input queue from the hung Firefox process + AttachThreadInput(GetCurrentThreadId(), + GetWindowThreadProcessId(mParentWindow, nullptr), + FALSE); + // Register a wait on the Firefox process so that we will be informed + // if it dies while the dialog is showing + RegisterWaitForSingleObject(&mRegWaitProcess, + mParentProcess, + &SOnParentProcessExit, + this, + INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + SetWindowText(aDlgHandle, mWindowTitle); + SetDlgItemText(aDlgHandle, IDC_MSG, mMessageText); + SetDlgItemText(aDlgHandle, IDC_NOFUTURE, mNoFutureText); + SetDlgItemText(aDlgHandle, IDC_CONTINUE, mWaitBtnText); + SetDlgItemText(aDlgHandle, IDC_STOP, mKillBtnText); + HANDLE icon = LoadImage(NULL, IDI_QUESTION, IMAGE_ICON, 0, 0, + LR_DEFAULTSIZE | LR_SHARED); + if (icon) { + SendDlgItemMessage(aDlgHandle, IDC_ICON, STM_SETICON, (WPARAM)icon, 0); + } + return TRUE; + } + case WM_CLOSE: { + mResponseBits |= HANGUI_USER_RESPONSE_CANCEL; + EndDialog(aDlgHandle, 0); + return TRUE; + } + case WM_COMMAND: { + switch (LOWORD(aWParam)) { + case IDC_CONTINUE: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_CONTINUE; + EndDialog(aDlgHandle, 1); + return TRUE; + } + break; + case IDC_STOP: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_STOP; + EndDialog(aDlgHandle, 1); + return TRUE; + } + break; + case IDC_NOFUTURE: + if (HIWORD(aWParam) == BN_CLICKED) { + if (Button_GetCheck(GetDlgItem(aDlgHandle, + IDC_NOFUTURE)) == BST_CHECKED) { + mResponseBits |= HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN; + } else { + mResponseBits &= + ~static_cast(HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); + } + return TRUE; + } + default: + break; + } + return FALSE; + } + default: + return FALSE; + } +} + +// static +VOID CALLBACK +PluginHangUIChild::SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer) +{ + // Simulate a cancel if the parent process died + PluginHangUIChild* object = static_cast(aObject); + object->RecvCancel(); +} + +bool +PluginHangUIChild::RecvShow() +{ + return (QueueUserAPC(&ShowAPC, + mMainThread, + reinterpret_cast(this))); +} + +bool +PluginHangUIChild::Show() +{ + INT_PTR dlgResult = DialogBox(GetModuleHandle(NULL), + MAKEINTRESOURCE(IDD_HANGUIDLG), + mParentWindow, + &SHangUIDlgProc); + mDlgHandle = NULL; + assert(dlgResult != -1); + bool result = false; + if (dlgResult != -1) { + PluginHangUIResponse* response = nullptr; + nsresult rv = mMiniShm.GetWritePtr(response); + if (NS_SUCCEEDED(rv)) { + response->mResponseBits = mResponseBits; + result = NS_SUCCEEDED(mMiniShm.Send()); + } + } + return result; +} + +// static +VOID CALLBACK +PluginHangUIChild::ShowAPC(ULONG_PTR aContext) +{ + PluginHangUIChild* object = reinterpret_cast(aContext); + object->Show(); +} + +bool +PluginHangUIChild::RecvCancel() +{ + if (mDlgHandle) { + PostMessage(mDlgHandle, WM_CLOSE, 0, 0); + } + return true; +} + +bool +PluginHangUIChild::WaitForDismissal() +{ + if (!SetMainThread()) { + return false; + } + DWORD waitResult = WaitForSingleObjectEx(mParentProcess, + kProcessTimeout, + TRUE); + return waitResult == WAIT_OBJECT_0 || + waitResult == WAIT_IO_COMPLETION; +} + +bool +PluginHangUIChild::SetMainThread() +{ + if (mMainThread) { + CloseHandle(mMainThread); + mMainThread = NULL; + } + mMainThread = OpenThread(THREAD_SET_CONTEXT, + FALSE, + GetCurrentThreadId()); + return !(!mMainThread); +} + +} // namespace plugins +} // namespace mozilla + +int +wmain(int argc, wchar_t *argv[]) +{ + INITCOMMONCONTROLSEX icc = { sizeof(INITCOMMONCONTROLSEX), + ICC_STANDARD_CLASSES }; + if (!InitCommonControlsEx(&icc)) { + return 1; + } + mozilla::plugins::PluginHangUIChild hangui; + if (!hangui.Init(argc, argv)) { + return 1; + } + if (!hangui.WaitForDismissal()) { + return 1; + } + return 0; +} + diff --git a/dom/plugins/ipc/hangui/PluginHangUIChild.h b/dom/plugins/ipc/hangui/PluginHangUIChild.h new file mode 100644 index 000000000000..13fbfacfeac7 --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginHangUIChild_h +#define mozilla_plugins_PluginHangUIChild_h + +#include "MiniShmChild.h" + +#include + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class implements the plugin-hang-ui. + * + * NOTE: PluginHangUIChild is *not* an IPDL actor! In this case, "Child" + * is describing the fact that plugin-hang-ui is a child process to the + * firefox process, which is the PluginHangUIParent. + * PluginHangUIParent and PluginHangUIChild are a matched pair. + * @see PluginHangUIParent + */ +class PluginHangUIChild : public MiniShmObserver +{ +public: + PluginHangUIChild(); + virtual ~PluginHangUIChild(); + + bool + Init(int aArgc, wchar_t* aArgv[]); + + /** + * Displays the Plugin Hang UI and does not return until the UI has + * been dismissed. + * + * @return true if the UI was displayed and the user response was + * successfully sent back to the parent. Otherwise false. + */ + bool + Show(); + + /** + * Causes the calling thread to wait either for the Hang UI to be + * dismissed or for the parent process to terminate. This should + * be called by the main thread. + * + * @return true unless there was an error initiating the wait + */ + bool + WaitForDismissal(); + + virtual void + OnMiniShmEvent(MiniShmBase* aMiniShmObj) MOZ_OVERRIDE; + +private: + bool + RecvShow(); + + bool + RecvCancel(); + + bool + SetMainThread(); + + INT_PTR + HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, LPARAM aLParam); + + static VOID CALLBACK + ShowAPC(ULONG_PTR aContext); + + static INT_PTR CALLBACK + SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, + LPARAM aLParam); + + static VOID CALLBACK + SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer); + + static PluginHangUIChild *sSelf; + + const wchar_t* mMessageText; + const wchar_t* mWindowTitle; + const wchar_t* mWaitBtnText; + const wchar_t* mKillBtnText; + const wchar_t* mNoFutureText; + unsigned int mResponseBits; + HWND mParentWindow; + HWND mDlgHandle; + HANDLE mMainThread; + HANDLE mParentProcess; + HANDLE mRegWaitProcess; + MiniShmChild mMiniShm; + + static const int kExpectedMinimumArgc; + static const DWORD kProcessTimeout; + static const DWORD kShmTimeout; + + DISALLOW_COPY_AND_ASSIGN(PluginHangUIChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUIChild_h + diff --git a/dom/plugins/ipc/hangui/module.ver b/dom/plugins/ipc/hangui/module.ver new file mode 100644 index 000000000000..18bee790e889 --- /dev/null +++ b/dom/plugins/ipc/hangui/module.ver @@ -0,0 +1,6 @@ +WIN32_MODULE_COMPANYNAME=Mozilla Corporation +WIN32_MODULE_PRODUCTVERSION=@MOZ_APP_WINVERSION@ +WIN32_MODULE_PRODUCTVERSION_STRING=@MOZ_APP_VERSION@ +WIN32_MODULE_DESCRIPTION=Plugin Hang UI for @MOZ_APP_DISPLAYNAME@ +WIN32_MODULE_PRODUCTNAME=@MOZ_APP_DISPLAYNAME@ +WIN32_MODULE_NAME=@MOZ_APP_DISPLAYNAME@ diff --git a/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest new file mode 100644 index 000000000000..004e47d35ebb --- /dev/null +++ b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest @@ -0,0 +1,35 @@ + + + +Firefox Plugin Hang User Interface + + + + + + + + + + + + + + + + + + + diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 97d42cb998ca..33f3a05a17fe 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1755,11 +1755,22 @@ pref("dom.ipc.plugins.parentTimeoutSecs", 0); // How long a plugin launch is allowed to take before // we consider it failed. pref("dom.ipc.plugins.processLaunchTimeoutSecs", 45); +#ifdef XP_WIN +// How long a plugin is allowed to process a synchronous IPC message +// before we display the plugin hang UI +pref("dom.ipc.plugins.hangUITimeoutSecs", 5); +// Minimum time that the plugin hang UI will be displayed +pref("dom.ipc.plugins.hangUIMinDisplaySecs", 10); +#endif #else // No timeout in DEBUG builds pref("dom.ipc.plugins.timeoutSecs", 0); pref("dom.ipc.plugins.processLaunchTimeoutSecs", 0); pref("dom.ipc.plugins.parentTimeoutSecs", 0); +#ifdef XP_WIN +pref("dom.ipc.plugins.hangUITimeoutSecs", 0); +pref("dom.ipc.plugins.hangUIMinDisplaySecs", 0); +#endif #endif #ifdef XP_WIN diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 82289a9e60e0..df619ca33190 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -1463,6 +1463,15 @@ "n_buckets": 10, "description": "Time spent checking if Java is enabled (ms)" }, + "PLUGIN_HANG_UI_USER_RESPONSE": { + "kind": "enumerated", + "n_values": 3, + "description": "User response to Plugin Hang UI" + }, + "PLUGIN_HANG_UI_DONT_ASK": { + "kind": "boolean", + "description": "Whether the user has requested not to see the Plugin Hang UI again" + }, "PLUGIN_SHUTDOWN_MS": { "kind": "exponential", "high": "5000",