author | Aaron Klotz <aklotz@mozilla.com> |
Fri, 04 Jan 2013 17:59:05 -0500 | |
changeset 117691 | 1308210aefb40f23cf88feb9bde1346ffbd62b5a |
parent 117690 | 012ce2bf7607a39466413cea50e502a37b2b6b34 |
child 117692 | b2df7d0c61d1a6c8dde14ed4e8c4f6dde5328cb4 |
push id | 24110 |
push user | philringnalda@gmail.com |
push date | Sat, 05 Jan 2013 23:57:49 +0000 |
treeherder | mozilla-central@20d1a5916ef6 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bsmedberg, bbondy |
bugs | 805591 |
milestone | 20.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -73,16 +73,17 @@ #endif #ifdef XP_MACOSX @BINPATH@/@MOZ_CHILD_PROCESS_NAME@.app/ @BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@ #else @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 @BINPATH@/msvcm80.dll @BINPATH@/msvcp80.dll @BINPATH@/msvcr80.dll #elif MOZ_MSVC_REDIST == 1500 @BINPATH@/Microsoft.VC90.CRT.manifest
--- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -73,16 +73,22 @@ PARALLEL_DIRS += \ system \ ipc \ identity \ workers \ camera \ audiochannel \ $(NULL) +ifeq (WINNT,$(OS_ARCH)) +PARALLEL_DIRS += \ + plugins/ipc/hangui \ + $(NULL) +endif + ifdef MOZ_B2G_RIL PARALLEL_DIRS += \ telephony \ wifi \ icc \ cellbroadcast \ $(NULL) endif
--- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -109,10 +109,14 @@ DOMExceptionCodeWarning=Use of DOMExcept # LOCALIZATION NOTE: Do not translate "__exposedProps__" NoExposedPropsWarning=Exposing chrome JS objects to content without __exposedProps__ is insecure and deprecated. See https://developer.mozilla.org/en/XPConnect_wrappers for more information. # LOCALIZATION NOTE: Do not translate "Mutation Event" and "MutationObserver" MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver instead. # LOCALIZATION NOTE: Do not translate "Blob", "mozSlice", or "slice" 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.
--- a/dom/plugins/ipc/Makefile.in +++ b/dom/plugins/ipc/Makefile.in @@ -89,16 +89,22 @@ CPPSRCS = \ PluginStreamChild.cpp \ PluginStreamParent.cpp \ $(NULL) 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 += \ PluginSurfaceParent.h \ $(NULL) endif ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) @@ -116,16 +122,23 @@ EXPORTS_mozilla/plugins += \ $(NULL) endif LOCAL_INCLUDES = \ -I$(srcdir)/../base \ -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 include $(topsrcdir)/config/rules.mk CXXFLAGS += $(TK_CFLAGS) DEFINES += -DFORCE_PR_LOG
new file mode 100644 --- /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 <sstream> + +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 +
new file mode 100644 --- /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 <string> + +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 +
new file mode 100644 --- /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<unsigned int>(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<nsIProperties> + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + return false; + } + nsCOMPtr<nsIFile> 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<wchar_t*>(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<PluginHangUIParent*>(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<nsIRunnable> workItem = new nsPluginHangUITelemetry(responseCode, + dontAskCode); + NS_DispatchToMainThread(workItem, NS_DISPATCH_NORMAL); + return true; +} + +nsresult +PluginHangUIParent::GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle) +{ + windowHandle = NULL; + + nsresult rv; + nsCOMPtr<nsIWindowMediator> winMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMWindow> 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<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(navWin); + if (!widget) { + return NS_ERROR_FAILURE; + } + + windowHandle = reinterpret_cast<NativeWindowHandle>(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
new file mode 100644 --- /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 +
--- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -30,16 +30,17 @@ #include "nsCRT.h" #include "nsNPAPIPlugin.h" #include "nsIFile.h" #include "nsPrintfCString.h" #include "prsystem.h" #ifdef XP_WIN +#include "PluginHangUIParent.h" #include "mozilla/widget/AudioSession.h" #endif #include "sampler.h" using base::KillProcess; using mozilla::PluginLibrary; using mozilla::ipc::SyncChannel; @@ -54,16 +55,23 @@ using namespace mozilla::plugins::parent #include "mozilla/dom/CrashReporterParent.h" using namespace CrashReporter; #endif 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<mozilla::plugins::PluginModuleParent> { typedef mozilla::plugins::PluginModuleParent Class; static void RetainCallee(Class* obj) { } static void ReleaseCallee(Class* obj) { } }; @@ -82,17 +90,17 @@ PluginModuleParent::LoadModule(const cha if (!launched) { // Need to set this so the destructor doesn't complain. parent->mShutdown = true; return nullptr; } 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. if (!CrashReporterParent::CreateCrashReporter(parent.get())) { parent->mShutdown = true; return nullptr; } #endif @@ -106,28 +114,35 @@ PluginModuleParent::PluginModuleParent(c , mShutdown(false) , mClearSiteDataSupported(false) , mGetSitesWithDataSupported(false) , mNPNIface(NULL) , mPlugin(NULL) , mTaskFactory(this) #ifdef XP_WIN , mPluginCpuUsageOnHang() + , mHangUIParent(nullptr) + , mHangUIEnabled(true) + , mIsTimerReset(true) #endif #ifdef MOZ_CRASHREPORTER_INJECTOR , mFlashProcess1(0) , mFlashProcess2(0) #endif { NS_ASSERTION(mSubprocess, "Out of memory!"); mIdentifiers.Init(); 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() { NS_ASSERTION(OkToCleanup(), "unsafe destruction"); if (!mShutdown) { NS_WARNING("Plugin host deleted the module without shutting down."); @@ -145,16 +160,25 @@ PluginModuleParent::~PluginModuleParent( if (mFlashProcess1) UnregisterInjectorCallback(mFlashProcess1); if (mFlashProcess2) UnregisterInjectorCallback(mFlashProcess2); #endif 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 void PluginModuleParent::WriteExtraDataForMinidump(AnnotationTable& notes) { typedef nsDependentCString CS; @@ -201,26 +225,39 @@ PluginModuleParent::WriteExtraDataForMin } #endif } #endif } } #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<PluginModuleParent*>(aModule)->SetReplyTimeoutMs(timeoutMs); + static_cast<PluginModuleParent*>(aModule)->SetChildTimeout(timeoutSecs); +#else + if (!strcmp(aPref, kChildTimeoutPref) || + !strcmp(aPref, kHangUIMinDisplayPref) || + !strcmp(aPref, kHangUITimeoutPref)) { + static_cast<PluginModuleParent*>(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); unused << static_cast<PluginModuleParent*>(aModule)->SendSetParentHangTimeout(timeoutSecs); } return 0; } @@ -303,16 +340,23 @@ GetProcessCpuUsage(const InfallibleTArra const float usage = 100.f * (float(deltaCpuTime) / deltaSampleTime) / numberOfProcessors; cpuUsage.AppendElement(usage); } return true; } } // anonymous namespace + +void +PluginModuleParent::ExitedCxxStack() +{ + FinishHangUI(); +} + #endif // #ifdef XP_WIN #ifdef MOZ_CRASHREPORTER_INJECTOR static bool CreateFlashMinidump(DWORD processId, ThreadId childThread, nsIFile* parentMinidump, const nsACString& name) { if (processId == 0) { @@ -329,20 +373,46 @@ CreateFlashMinidump(DWORD processId, Thr return res; } #endif 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( ("generated paired browser/plugin minidumps: %s)", NS_ConvertUTF16toUTF8(mPluginDumpID).get())); nsAutoCString additionalDumps("browser"); @@ -390,27 +460,138 @@ PluginModuleParent::ShouldContinueFromRe if (!GetProcessCpuUsage(processHandles, mPluginCpuUsageOnHang)) { mPluginCpuUsageOnHang.Clear(); } #endif // 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() { return static_cast<CrashReporterParent*>(ManagedPCrashReporterParent()[0]); } #ifdef MOZ_CRASHREPORTER_INJECTOR
--- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -40,16 +40,20 @@ class PCrashReporterParent; class CrashReporterParent; } namespace plugins { //----------------------------------------------------------------------------- class BrowserStreamParent; +#ifdef XP_WIN +class PluginHangUIParent; +#endif + /** * PluginModuleParent * * This class implements the NPP API from the perspective of the rest * of Gecko, forwarding NPP calls along to the child process that is * actually running the plugin. * * This class /also/ implements a version of the NPN API, because the @@ -125,16 +129,23 @@ public: * is intended only for use by StackIdentifier and the scriptable * Enumerate hook. */ PluginIdentifierParent* GetIdentifierForNPIdentifier(NPP npp, NPIdentifier aIdentifier); 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 { return MediateRace(parent, child); } virtual bool RecvXXX_HACK_FIXME_cjones(Shmem& mem) { NS_RUNTIMEABORT("not reached"); return false; } @@ -281,16 +292,17 @@ private: private: CrashReporterParent* CrashReporter(); #ifdef MOZ_CRASHREPORTER void ProcessFirstMinidump(); void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes); #endif void CleanupFromTimeout(); + void SetChildTimeout(const int32_t aChildTimeout); static int TimeoutChanged(const char* aPref, void* aModule); void NotifyPluginCrashed(); PluginProcessParent* mSubprocess; // the plugin thread in mSubprocess NativeThreadId mPluginThread; bool mShutdown; bool mClearSiteDataSupported; @@ -299,16 +311,41 @@ private: nsDataHashtable<nsPtrHashKey<void>, PluginIdentifierParent*> mIdentifiers; nsNPAPIPlugin* mPlugin; ScopedRunnableMethodFactory<PluginModuleParent> mTaskFactory; nsString mPluginDumpID; nsString mBrowserDumpID; nsString mHangID; #ifdef XP_WIN InfallibleTArray<float> 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 // Dup of plugin's X socket, used to scope its resources to this // object instead of the plugin process's lifetime ScopedClose mPluginXSocketFdDup; #endif
new file mode 100644 --- /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_DLGICON 1004 + +#endif // mozilla_plugins_HangUIDlg_h +
new file mode 100644 --- /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 <windows.h> + +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_DLGICON,7,7,20,20 +END +
new file mode 100644 --- /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 +
new file mode 100644 --- /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 <windows.h> + +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<typename T> 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<T*>(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<typename T> 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<const T*>(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<MiniShmHeader*>(aView); + mWriteHeader = reinterpret_cast<MiniShmHeader*>(static_cast<char*>(aView) + + aSize / 2U); + } else { + mWriteHeader = static_cast<MiniShmHeader*>(aView); + mReadHeader = reinterpret_cast<MiniShmHeader*>(static_cast<char*>(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<typename T> 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<T*>(mWriteHeader + 1); + return NS_OK; + } + + static VOID CALLBACK + SOnEvent(PVOID aContext, BOOLEAN aIsTimer) + { + MiniShmBase* object = static_cast<MiniShmBase*>(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 +
new file mode 100644 --- /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 <limits> +#include <sstream> + +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<unsigned int>::max()) { + mappingSize = static_cast<unsigned int>(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 +
new file mode 100644 --- /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 <string> + +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 +
new file mode 100644 --- /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 +
new file mode 100644 --- /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 <assert.h> +#include <commctrl.h> +#include <windowsx.h> +#include <sstream> + +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<HANDLE&>(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_DLGICON, 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<DWORD>(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<PluginHangUIChild*>(aObject); + object->RecvCancel(); +} + +bool +PluginHangUIChild::RecvShow() +{ + return (QueueUserAPC(&ShowAPC, + mMainThread, + reinterpret_cast<ULONG_PTR>(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<PluginHangUIChild*>(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; +} +
new file mode 100644 --- /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 <string> + +#include <windows.h> + +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 +
new file mode 100644 --- /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@
new file mode 100644 --- /dev/null +++ b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="plugin-hang-ui" + type="win32" +/> +<description>Firefox Plugin Hang User Interface</description> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> + <ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> + </ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" /> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /> + </application> + </compatibility> +</assembly>
--- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1750,21 +1750,32 @@ pref("plugins.click_to_play", false); pref("dom.ipc.plugins.timeoutSecs", 45); // How long a plugin process will wait for a response from the parent // to a synchronous request before terminating itself. After this // point the child assumes the parent is hung. Currently disabled. 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 // Disable oopp for java on windows. They run their own // process isolation which conflicts with our implementation. pref("dom.ipc.plugins.java.enabled", false); #endif
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -1458,16 +1458,25 @@ "description": "Time spent scanning filesystem for plugins (ms)" }, "CHECK_JAVA_ENABLED": { "kind": "exponential", "high": "3000", "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", "n_buckets": 20, "description": "Time spent shutting down plugins (ms)" }, "MOZ_SQLITE_OPEN_MS": { "kind": "exponential",