michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "PluginHangUI.h" michael@0: michael@0: #include "PluginHangUIChild.h" michael@0: #include "HangUIDlg.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace plugins { michael@0: michael@0: struct WinInfo michael@0: { michael@0: WinInfo(HWND aHwnd, POINT& aPos, SIZE& aSize) michael@0: :hwnd(aHwnd) michael@0: { michael@0: pos.x = aPos.x; michael@0: pos.y = aPos.y; michael@0: size.cx = aSize.cx; michael@0: size.cy = aSize.cy; michael@0: } michael@0: HWND hwnd; michael@0: POINT pos; michael@0: SIZE size; michael@0: }; michael@0: typedef std::vector WinInfoVec; michael@0: michael@0: PluginHangUIChild* PluginHangUIChild::sSelf = nullptr; michael@0: const int PluginHangUIChild::kExpectedMinimumArgc = 10; michael@0: michael@0: PluginHangUIChild::PluginHangUIChild() michael@0: : mResponseBits(0), michael@0: mParentWindow(nullptr), michael@0: mDlgHandle(nullptr), michael@0: mMainThread(nullptr), michael@0: mParentProcess(nullptr), michael@0: mRegWaitProcess(nullptr), michael@0: mIPCTimeoutMs(0) michael@0: { michael@0: } michael@0: michael@0: PluginHangUIChild::~PluginHangUIChild() michael@0: { michael@0: if (mMainThread) { michael@0: CloseHandle(mMainThread); michael@0: } michael@0: if (mRegWaitProcess) { michael@0: UnregisterWaitEx(mRegWaitProcess, INVALID_HANDLE_VALUE); michael@0: } michael@0: if (mParentProcess) { michael@0: CloseHandle(mParentProcess); michael@0: } michael@0: sSelf = nullptr; michael@0: } michael@0: michael@0: bool michael@0: PluginHangUIChild::Init(int aArgc, wchar_t* aArgv[]) michael@0: { michael@0: if (aArgc < kExpectedMinimumArgc) { michael@0: return false; michael@0: } michael@0: unsigned int i = 1; michael@0: mMessageText = aArgv[i]; michael@0: mWindowTitle = aArgv[++i]; michael@0: mWaitBtnText = aArgv[++i]; michael@0: mKillBtnText = aArgv[++i]; michael@0: mNoFutureText = aArgv[++i]; michael@0: std::wistringstream issHwnd(aArgv[++i]); michael@0: issHwnd >> reinterpret_cast(mParentWindow); michael@0: if (!issHwnd) { michael@0: return false; michael@0: } michael@0: std::wistringstream issProc(aArgv[++i]); michael@0: issProc >> mParentProcess; michael@0: if (!issProc) { michael@0: return false; michael@0: } michael@0: // Only set the App User Model ID if it's present in the args michael@0: if (wcscmp(aArgv[++i], L"-")) { michael@0: HMODULE shell32 = LoadLibrary(L"shell32.dll"); michael@0: if (shell32) { michael@0: SETAPPUSERMODELID fSetAppUserModelID = (SETAPPUSERMODELID) michael@0: GetProcAddress(shell32, "SetCurrentProcessExplicitAppUserModelID"); michael@0: if (fSetAppUserModelID) { michael@0: fSetAppUserModelID(aArgv[i]); michael@0: } michael@0: FreeLibrary(shell32); michael@0: } michael@0: } michael@0: std::wistringstream issTimeout(aArgv[++i]); michael@0: issTimeout >> mIPCTimeoutMs; michael@0: if (!issTimeout) { michael@0: return false; michael@0: } michael@0: michael@0: nsresult rv = mMiniShm.Init(this, michael@0: std::wstring(aArgv[++i]), michael@0: IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: sSelf = this; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: PluginHangUIChild::OnMiniShmEvent(MiniShmBase* aMiniShmObj) michael@0: { michael@0: const PluginHangUICommand* cmd = nullptr; michael@0: nsresult rv = aMiniShmObj->GetReadPtr(cmd); michael@0: assert(NS_SUCCEEDED(rv)); michael@0: bool returnStatus = false; michael@0: if (NS_SUCCEEDED(rv)) { michael@0: switch (cmd->mCode) { michael@0: case PluginHangUICommand::HANGUI_CMD_SHOW: michael@0: returnStatus = RecvShow(); michael@0: break; michael@0: case PluginHangUICommand::HANGUI_CMD_CANCEL: michael@0: returnStatus = RecvCancel(); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // static michael@0: INT_PTR CALLBACK michael@0: PluginHangUIChild::SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, michael@0: WPARAM aWParam, LPARAM aLParam) michael@0: { michael@0: PluginHangUIChild *self = PluginHangUIChild::sSelf; michael@0: if (self) { michael@0: return self->HangUIDlgProc(aDlgHandle, aMsgCode, aWParam, aLParam); michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: void michael@0: PluginHangUIChild::ResizeButtons() michael@0: { michael@0: // Control IDs are specified right-to-left as they appear in the dialog michael@0: UINT ids[] = { IDC_STOP, IDC_CONTINUE }; michael@0: UINT numIds = sizeof(ids)/sizeof(ids[0]); michael@0: michael@0: // Pass 1: Compute the ideal size michael@0: bool needResizing = false; michael@0: SIZE idealSize = {0}; michael@0: WinInfoVec winInfo; michael@0: for (UINT i = 0; i < numIds; ++i) { michael@0: HWND wnd = GetDlgItem(mDlgHandle, ids[i]); michael@0: if (!wnd) { michael@0: return; michael@0: } michael@0: michael@0: // Get the button's dimensions in screen coordinates michael@0: RECT curRect; michael@0: if (!GetWindowRect(wnd, &curRect)) { michael@0: return; michael@0: } michael@0: michael@0: // Get (x,y) position of the button in client coordinates michael@0: POINT pt; michael@0: pt.x = curRect.left; michael@0: pt.y = curRect.top; michael@0: if (!ScreenToClient(mDlgHandle, &pt)) { michael@0: return; michael@0: } michael@0: michael@0: // Request the button's text margins michael@0: RECT margins; michael@0: if (!Button_GetTextMargin(wnd, &margins)) { michael@0: return; michael@0: } michael@0: michael@0: // Compute the button's width and height michael@0: SIZE curSize; michael@0: curSize.cx = curRect.right - curRect.left; michael@0: curSize.cy = curRect.bottom - curRect.top; michael@0: michael@0: // Request the button's ideal width and height and add in the margins michael@0: SIZE size = {0}; michael@0: if (!Button_GetIdealSize(wnd, &size)) { michael@0: return; michael@0: } michael@0: size.cx += margins.left + margins.right; michael@0: size.cy += margins.top + margins.bottom; michael@0: michael@0: // Size all buttons to be the same width as the longest button encountered michael@0: idealSize.cx = std::max(idealSize.cx, size.cx); michael@0: idealSize.cy = std::max(idealSize.cy, size.cy); michael@0: michael@0: // We won't bother resizing unless we need extra space michael@0: if (idealSize.cx > curSize.cx) { michael@0: needResizing = true; michael@0: } michael@0: michael@0: // Save the relevant info for the resize, if any. We do this even if michael@0: // needResizing is false because another button may trigger a resize later. michael@0: winInfo.push_back(WinInfo(wnd, pt, curSize)); michael@0: } michael@0: michael@0: if (!needResizing) { michael@0: return; michael@0: } michael@0: michael@0: // Pass 2: Resize the windows michael@0: int deltaX = 0; michael@0: HDWP hwp = BeginDeferWindowPos((int) winInfo.size()); michael@0: if (!hwp) { michael@0: return; michael@0: } michael@0: for (WinInfoVec::const_iterator itr = winInfo.begin(); michael@0: itr != winInfo.end(); ++itr) { michael@0: // deltaX accumulates the size changes so that each button's x coordinate michael@0: // can compensate for the width increases michael@0: deltaX += idealSize.cx - itr->size.cx; michael@0: hwp = DeferWindowPos(hwp, itr->hwnd, nullptr, michael@0: itr->pos.x - deltaX, itr->pos.y, michael@0: idealSize.cx, itr->size.cy, michael@0: SWP_NOZORDER | SWP_NOACTIVATE); michael@0: if (!hwp) { michael@0: return; michael@0: } michael@0: } michael@0: EndDeferWindowPos(hwp); michael@0: } michael@0: michael@0: INT_PTR michael@0: PluginHangUIChild::HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, michael@0: LPARAM aLParam) michael@0: { michael@0: mDlgHandle = aDlgHandle; michael@0: switch (aMsgCode) { michael@0: case WM_INITDIALOG: { michael@0: // Register a wait on the Firefox process so that we will be informed michael@0: // if it dies while the dialog is showing michael@0: RegisterWaitForSingleObject(&mRegWaitProcess, michael@0: mParentProcess, michael@0: &SOnParentProcessExit, michael@0: this, michael@0: INFINITE, michael@0: WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); michael@0: SetWindowText(aDlgHandle, mWindowTitle); michael@0: SetDlgItemText(aDlgHandle, IDC_MSG, mMessageText); michael@0: SetDlgItemText(aDlgHandle, IDC_NOFUTURE, mNoFutureText); michael@0: SetDlgItemText(aDlgHandle, IDC_CONTINUE, mWaitBtnText); michael@0: SetDlgItemText(aDlgHandle, IDC_STOP, mKillBtnText); michael@0: ResizeButtons(); michael@0: HANDLE icon = LoadImage(nullptr, IDI_QUESTION, IMAGE_ICON, 0, 0, michael@0: LR_DEFAULTSIZE | LR_SHARED); michael@0: if (icon) { michael@0: SendDlgItemMessage(aDlgHandle, IDC_DLGICON, STM_SETICON, (WPARAM)icon, 0); michael@0: } michael@0: EnableWindow(mParentWindow, FALSE); michael@0: return TRUE; michael@0: } michael@0: case WM_CLOSE: { michael@0: mResponseBits |= HANGUI_USER_RESPONSE_CANCEL; michael@0: EndDialog(aDlgHandle, 0); michael@0: SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); michael@0: return TRUE; michael@0: } michael@0: case WM_COMMAND: { michael@0: switch (LOWORD(aWParam)) { michael@0: case IDC_CONTINUE: michael@0: if (HIWORD(aWParam) == BN_CLICKED) { michael@0: mResponseBits |= HANGUI_USER_RESPONSE_CONTINUE; michael@0: EndDialog(aDlgHandle, 1); michael@0: SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); michael@0: return TRUE; michael@0: } michael@0: break; michael@0: case IDC_STOP: michael@0: if (HIWORD(aWParam) == BN_CLICKED) { michael@0: mResponseBits |= HANGUI_USER_RESPONSE_STOP; michael@0: EndDialog(aDlgHandle, 1); michael@0: SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); michael@0: return TRUE; michael@0: } michael@0: break; michael@0: case IDC_NOFUTURE: michael@0: if (HIWORD(aWParam) == BN_CLICKED) { michael@0: if (Button_GetCheck(GetDlgItem(aDlgHandle, michael@0: IDC_NOFUTURE)) == BST_CHECKED) { michael@0: mResponseBits |= HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN; michael@0: } else { michael@0: mResponseBits &= michael@0: ~static_cast(HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); michael@0: } michael@0: SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); michael@0: return TRUE; michael@0: } michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: case WM_DESTROY: { michael@0: EnableWindow(mParentWindow, TRUE); michael@0: SetForegroundWindow(mParentWindow); michael@0: break; michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: // static michael@0: VOID CALLBACK michael@0: PluginHangUIChild::SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer) michael@0: { michael@0: // Simulate a cancel if the parent process died michael@0: PluginHangUIChild* object = static_cast(aObject); michael@0: object->RecvCancel(); michael@0: } michael@0: michael@0: bool michael@0: PluginHangUIChild::RecvShow() michael@0: { michael@0: return (QueueUserAPC(&ShowAPC, michael@0: mMainThread, michael@0: reinterpret_cast(this))); michael@0: } michael@0: michael@0: bool michael@0: PluginHangUIChild::Show() michael@0: { michael@0: INT_PTR dlgResult = DialogBox(GetModuleHandle(nullptr), michael@0: MAKEINTRESOURCE(IDD_HANGUIDLG), michael@0: nullptr, michael@0: &SHangUIDlgProc); michael@0: mDlgHandle = nullptr; michael@0: assert(dlgResult != -1); michael@0: bool result = false; michael@0: if (dlgResult != -1) { michael@0: PluginHangUIResponse* response = nullptr; michael@0: nsresult rv = mMiniShm.GetWritePtr(response); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: response->mResponseBits = mResponseBits; michael@0: result = NS_SUCCEEDED(mMiniShm.Send()); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: // static michael@0: VOID CALLBACK michael@0: PluginHangUIChild::ShowAPC(ULONG_PTR aContext) michael@0: { michael@0: PluginHangUIChild* object = reinterpret_cast(aContext); michael@0: object->Show(); michael@0: } michael@0: michael@0: bool michael@0: PluginHangUIChild::RecvCancel() michael@0: { michael@0: if (mDlgHandle) { michael@0: PostMessage(mDlgHandle, WM_CLOSE, 0, 0); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: PluginHangUIChild::WaitForDismissal() michael@0: { michael@0: if (!SetMainThread()) { michael@0: return false; michael@0: } michael@0: DWORD waitResult = WaitForSingleObjectEx(mParentProcess, michael@0: mIPCTimeoutMs, michael@0: TRUE); michael@0: return waitResult == WAIT_OBJECT_0 || michael@0: waitResult == WAIT_IO_COMPLETION; michael@0: } michael@0: michael@0: bool michael@0: PluginHangUIChild::SetMainThread() michael@0: { michael@0: if (mMainThread) { michael@0: CloseHandle(mMainThread); michael@0: mMainThread = nullptr; michael@0: } michael@0: mMainThread = OpenThread(THREAD_SET_CONTEXT, michael@0: FALSE, michael@0: GetCurrentThreadId()); michael@0: return !(!mMainThread); michael@0: } michael@0: michael@0: } // namespace plugins michael@0: } // namespace mozilla michael@0: michael@0: #ifdef __MINGW32__ michael@0: extern "C" michael@0: #endif michael@0: int michael@0: wmain(int argc, wchar_t *argv[]) michael@0: { michael@0: INITCOMMONCONTROLSEX icc = { sizeof(INITCOMMONCONTROLSEX), michael@0: ICC_STANDARD_CLASSES }; michael@0: if (!InitCommonControlsEx(&icc)) { michael@0: return 1; michael@0: } michael@0: mozilla::plugins::PluginHangUIChild hangui; michael@0: if (!hangui.Init(argc, argv)) { michael@0: return 1; michael@0: } michael@0: if (!hangui.WaitForDismissal()) { michael@0: return 1; michael@0: } michael@0: return 0; michael@0: } michael@0: