dom/plugins/ipc/PluginHangUIParent.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial