forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			399 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			399 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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/ipc/ProtocolUtils.h"
 | 
						|
#include "mozilla/plugins/PluginModuleParent.h"
 | 
						|
 | 
						|
#include "nsContentUtils.h"
 | 
						|
#include "nsDirectoryServiceDefs.h"
 | 
						|
#include "nsIFile.h"
 | 
						|
#include "nsIProperties.h"
 | 
						|
#include "nsIWindowMediator.h"
 | 
						|
#include "nsIWinTaskbar.h"
 | 
						|
#include "nsServiceManagerUtils.h"
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
 | 
						|
#include "WidgetUtils.h"
 | 
						|
 | 
						|
#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
 | 
						|
 | 
						|
using base::ProcessHandle;
 | 
						|
 | 
						|
using mozilla::widget::WidgetUtils;
 | 
						|
 | 
						|
using std::string;
 | 
						|
using std::vector;
 | 
						|
 | 
						|
namespace {
 | 
						|
class nsPluginHangUITelemetry : public mozilla::Runnable {
 | 
						|
 public:
 | 
						|
  nsPluginHangUITelemetry(int aResponseCode, int aDontAskCode,
 | 
						|
                          uint32_t aResponseTimeMs, uint32_t aTimeoutMs)
 | 
						|
      : Runnable("nsPluginHangUITelemetry"),
 | 
						|
        mResponseCode(aResponseCode),
 | 
						|
        mDontAskCode(aDontAskCode),
 | 
						|
        mResponseTimeMs(aResponseTimeMs),
 | 
						|
        mTimeoutMs(aTimeoutMs) {}
 | 
						|
 | 
						|
  NS_IMETHOD
 | 
						|
  Run() override {
 | 
						|
    mozilla::Telemetry::Accumulate(
 | 
						|
        mozilla::Telemetry::PLUGIN_HANG_UI_USER_RESPONSE, mResponseCode);
 | 
						|
    mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLUGIN_HANG_UI_DONT_ASK,
 | 
						|
                                   mDontAskCode);
 | 
						|
    mozilla::Telemetry::Accumulate(
 | 
						|
        mozilla::Telemetry::PLUGIN_HANG_UI_RESPONSE_TIME, mResponseTimeMs);
 | 
						|
    mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLUGIN_HANG_TIME,
 | 
						|
                                   mTimeoutMs + mResponseTimeMs);
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  int mResponseCode;
 | 
						|
  int mDontAskCode;
 | 
						|
  uint32_t mResponseTimeMs;
 | 
						|
  uint32_t mTimeoutMs;
 | 
						|
};
 | 
						|
}  // namespace
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
namespace plugins {
 | 
						|
 | 
						|
PluginHangUIParent::PluginHangUIParent(PluginModuleChromeParent* aModule,
 | 
						|
                                       const int32_t aHangUITimeoutPref,
 | 
						|
                                       const int32_t aChildTimeoutPref)
 | 
						|
    : mMutex("mozilla::plugins::PluginHangUIParent::mMutex"),
 | 
						|
      mModule(aModule),
 | 
						|
      mTimeoutPrefMs(static_cast<uint32_t>(aHangUITimeoutPref) * 1000U),
 | 
						|
      mIPCTimeoutMs(static_cast<uint32_t>(aChildTimeoutPref) * 1000U),
 | 
						|
      mMainThreadMessageLoop(MessageLoop::current()),
 | 
						|
      mIsShowing(false),
 | 
						|
      mLastUserResponse(0),
 | 
						|
      mHangUIProcessHandle(nullptr),
 | 
						|
      mMainWindowHandle(nullptr),
 | 
						|
      mRegWait(nullptr),
 | 
						|
      mShowEvent(nullptr),
 | 
						|
      mShowTicks(0),
 | 
						|
      mResponseTicks(0) {}
 | 
						|
 | 
						|
PluginHangUIParent::~PluginHangUIParent() {
 | 
						|
  {  // Scope for lock
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
    UnwatchHangUIChildProcess(true);
 | 
						|
  }
 | 
						|
  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 : mIPCTimeoutMs);
 | 
						|
  NS_ENSURE_SUCCESS(rv, false);
 | 
						|
  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());
 | 
						|
 | 
						|
  nsAutoString localizedStr;
 | 
						|
  rv = nsContentUtils::FormatLocalizedString(
 | 
						|
      localizedStr, nsContentUtils::eDOM_PROPERTIES, "PluginHangUIMessage",
 | 
						|
      aPluginName);
 | 
						|
  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.Get());
 | 
						|
  commandLine.AppendLooseValue(procHandleStr.get());
 | 
						|
 | 
						|
  // On Win7+, pass the application user model to the child, so it can
 | 
						|
  // register with it. This insures windows created by the Hang UI
 | 
						|
  // properly group with the parent app on the Win7 taskbar.
 | 
						|
  nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID);
 | 
						|
  if (taskbarInfo) {
 | 
						|
    bool isSupported = false;
 | 
						|
    taskbarInfo->GetAvailable(&isSupported);
 | 
						|
    nsAutoString appId;
 | 
						|
    if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) {
 | 
						|
      commandLine.AppendLooseValue(appId.get());
 | 
						|
    } else {
 | 
						|
      commandLine.AppendLooseValue(L"-");
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    commandLine.AppendLooseValue(L"-");
 | 
						|
  }
 | 
						|
 | 
						|
  nsAutoString ipcTimeoutStr;
 | 
						|
  ipcTimeoutStr.AppendInt(mIPCTimeoutMs);
 | 
						|
  commandLine.AppendLooseValue(ipcTimeoutStr.get());
 | 
						|
 | 
						|
  std::wstring ipcCookie;
 | 
						|
  rv = mMiniShm.GetCookie(ipcCookie);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  commandLine.AppendLooseValue(ipcCookie);
 | 
						|
 | 
						|
  ScopedHandle showEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
 | 
						|
  if (!showEvent.IsValid()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  mShowEvent = showEvent.Get();
 | 
						|
 | 
						|
  MutexAutoLock lock(mMutex);
 | 
						|
  STARTUPINFO startupInfo = {sizeof(STARTUPINFO)};
 | 
						|
  PROCESS_INFORMATION processInfo = {nullptr};
 | 
						|
  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,
 | 
						|
                          ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs);
 | 
						|
    // Setting this to true even if we time out on mShowEvent. This timeout
 | 
						|
    // typically occurs when the machine is thrashing so badly that
 | 
						|
    // plugin-hang-ui.exe is taking a while to start. If we didn't set
 | 
						|
    // this to true, Firefox would keep spawning additional plugin-hang-ui
 | 
						|
    // processes, which is not what we want.
 | 
						|
    mIsShowing = true;
 | 
						|
  }
 | 
						|
  mShowEvent = nullptr;
 | 
						|
  return !(!isProcessCreated);
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
VOID CALLBACK PluginHangUIParent::SOnHangUIProcessExit(PVOID aContext,
 | 
						|
                                                       BOOLEAN aIsTimer) {
 | 
						|
  PluginHangUIParent* object = static_cast<PluginHangUIParent*>(aContext);
 | 
						|
  MutexAutoLock lock(object->mMutex);
 | 
						|
  // 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);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// A precondition for this function is that the caller has locked mMutex
 | 
						|
bool PluginHangUIParent::UnwatchHangUIChildProcess(bool aWait) {
 | 
						|
  mMutex.AssertCurrentThreadOwns();
 | 
						|
  if (mRegWait) {
 | 
						|
    // If aWait is false then we want to pass a nullptr (i.e. default
 | 
						|
    // constructor) completionEvent
 | 
						|
    ScopedHandle completionEvent;
 | 
						|
    if (aWait) {
 | 
						|
      completionEvent.Set(::CreateEventW(nullptr, FALSE, FALSE, nullptr));
 | 
						|
      if (!completionEvent.IsValid()) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // if aWait == false and UnregisterWaitEx fails with ERROR_IO_PENDING,
 | 
						|
    // it is okay to clear mRegWait; Windows is telling us that the wait's
 | 
						|
    // callback is running but will be cleaned up once the callback returns.
 | 
						|
    if (::UnregisterWaitEx(mRegWait, completionEvent) ||
 | 
						|
        (!aWait && ::GetLastError() == ERROR_IO_PENDING)) {
 | 
						|
      mRegWait = nullptr;
 | 
						|
      if (aWait) {
 | 
						|
        // We must temporarily unlock mMutex while waiting for the registered
 | 
						|
        // wait callback to complete, or else we could deadlock.
 | 
						|
        MutexAutoUnlock unlock(mMutex);
 | 
						|
        ::WaitForSingleObject(completionEvent, INFINITE);
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool PluginHangUIParent::Cancel() {
 | 
						|
  MutexAutoLock lock(mMutex);
 | 
						|
  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());
 | 
						|
}
 | 
						|
 | 
						|
// A precondition for this function is that the caller has locked mMutex
 | 
						|
bool PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse) {
 | 
						|
  mMutex.AssertCurrentThreadOwns();
 | 
						|
  if (!mIsShowing && !(aResponse & HANGUI_USER_RESPONSE_CANCEL)) {
 | 
						|
    // Don't process a user response if a cancellation is already pending
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
  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,
 | 
						|
                                   mozilla::ipc::kInvalidProcessId,
 | 
						|
                                   "ModalHangUI"_ns, EmptyString());
 | 
						|
    responseCode = 1;
 | 
						|
  } else if (aResponse & HANGUI_USER_RESPONSE_CONTINUE) {
 | 
						|
    mModule->OnHangUIContinue();
 | 
						|
    // 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, LastShowDurationMs(), mTimeoutPrefMs);
 | 
						|
  NS_DispatchToMainThread(workItem);
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
nsresult PluginHangUIParent::GetHangUIOwnerWindowHandle(
 | 
						|
    NativeWindowHandle& windowHandle) {
 | 
						|
  windowHandle = nullptr;
 | 
						|
 | 
						|
  nsresult rv;
 | 
						|
  nsCOMPtr<nsIWindowMediator> winMediator(
 | 
						|
      do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  nsCOMPtr<mozIDOMWindowProxy> navWin;
 | 
						|
  rv = winMediator->GetMostRecentWindow(u"navigator:browser",
 | 
						|
                                        getter_AddRefs(navWin));
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
  if (!navWin) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
 | 
						|
  nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(win);
 | 
						|
  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)) {
 | 
						|
    // The child process has returned a response so we shouldn't worry about
 | 
						|
    // its state anymore.
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
    UnwatchHangUIChildProcess(false);
 | 
						|
    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;
 | 
						|
  if (NS_SUCCEEDED(aMiniShmObj->Send())) {
 | 
						|
    mShowTicks = ::GetTickCount();
 | 
						|
  }
 | 
						|
  ::SetEvent(mShowEvent);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace plugins
 | 
						|
}  // namespace mozilla
 |