Bug 805591 - Win32 implementation of the Plugin Hang UI. r=bsmedberg,bbondy
☠☠ backed out by 1940c5a8eec5 ☠ ☠
authorAaron Klotz <aklotz@mozilla.com>
Thu, 03 Jan 2013 22:24:07 -0500
changeset 117573 a6acebd9c9d54623864ca5aca9baeee875d41fa6
parent 117572 16c6a3cf8a773dee482d1189237122a892704d87
child 117574 f390f26b2f7e1acee07a1b8b55d4931ed1d3a958
push id1267
push userpastithas@mozilla.com
push dateSat, 05 Jan 2013 09:44:07 +0000
treeherderfx-team@d8ca3e1c469e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, bbondy
bugs805591
milestone20.0a1
Bug 805591 - Win32 implementation of the Plugin Hang UI. r=bsmedberg,bbondy
browser/installer/package-manifest.in
dom/Makefile.in
dom/locales/en-US/chrome/dom/dom.properties
dom/plugins/ipc/Makefile.in
dom/plugins/ipc/MiniShmParent.cpp
dom/plugins/ipc/MiniShmParent.h
dom/plugins/ipc/PluginHangUIParent.cpp
dom/plugins/ipc/PluginHangUIParent.h
dom/plugins/ipc/PluginModuleParent.cpp
dom/plugins/ipc/PluginModuleParent.h
dom/plugins/ipc/hangui/HangUIDlg.h
dom/plugins/ipc/hangui/HangUIDlg.rc
dom/plugins/ipc/hangui/Makefile.in
dom/plugins/ipc/hangui/MiniShmBase.h
dom/plugins/ipc/hangui/MiniShmChild.cpp
dom/plugins/ipc/hangui/MiniShmChild.h
dom/plugins/ipc/hangui/PluginHangUI.h
dom/plugins/ipc/hangui/PluginHangUIChild.cpp
dom/plugins/ipc/hangui/PluginHangUIChild.h
dom/plugins/ipc/hangui/module.ver
dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest
modules/libpref/src/init/all.js
toolkit/components/telemetry/Histograms.json
--- 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_ICON                        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_ICON,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_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<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",