diff options
Diffstat (limited to '')
-rw-r--r-- | widget/windows/WinCompositorWindowThread.cpp | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/widget/windows/WinCompositorWindowThread.cpp b/widget/windows/WinCompositorWindowThread.cpp new file mode 100644 index 0000000000..f2d28fd66f --- /dev/null +++ b/widget/windows/WinCompositorWindowThread.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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 "base/platform_thread.h" +#include "WinCompositorWindowThread.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/StaticPtr.h" +#include "transport/runnable_utils.h" +#include "mozilla/StaticPrefs_apz.h" + +#if WINVER < 0x0602 +# define WS_EX_NOREDIRECTIONBITMAP 0x00200000L +#endif + +namespace mozilla { +namespace widget { + +static StaticRefPtr<WinCompositorWindowThread> sWinCompositorWindowThread; + +/// A window procedure that logs when an input event is received to the gfx +/// error log +/// +/// This is done because this window is supposed to be WM_DISABLED, but +/// malfunctioning software may still end up targetting this window. If that +/// happens, it's almost-certainly a bug and should be brought to the attention +/// of the developers that are debugging the issue. +static LRESULT CALLBACK InputEventRejectingWindowProc(HWND window, UINT msg, + WPARAM wparam, + LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_MOUSEMOVE: + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + gfxCriticalNoteOnce + << "The compositor window received an input event even though it's " + "disabled. There is likely malfunctioning " + "software on the user's machine."; + + break; + default: + break; + } + return ::DefWindowProcW(window, msg, wparam, lparam); +} + +WinCompositorWindowThread::WinCompositorWindowThread(base::Thread* aThread) + : mThread(aThread), mMonitor("WinCompositorWindowThread") {} + +/* static */ +WinCompositorWindowThread* WinCompositorWindowThread::Get() { + if (!sWinCompositorWindowThread || + sWinCompositorWindowThread->mHasAttemptedShutdown) { + return nullptr; + } + return sWinCompositorWindowThread; +} + +/* static */ +void WinCompositorWindowThread::Start() { + MOZ_ASSERT(NS_IsMainThread()); + + base::Thread::Options options; + // HWND requests ui thread. + options.message_loop_type = MessageLoop::TYPE_UI; + + if (sWinCompositorWindowThread) { + // Try to reuse the thread, which involves stopping and restarting it. + sWinCompositorWindowThread->mThread->Stop(); + if (sWinCompositorWindowThread->mThread->StartWithOptions(options)) { + // Success! + sWinCompositorWindowThread->mHasAttemptedShutdown = false; + return; + } + // Restart failed, so null out our sWinCompositorWindowThread and + // try again with a new thread. This will cause the old singleton + // instance to be deallocated, which will destroy its mThread as well. + sWinCompositorWindowThread = nullptr; + } + + base::Thread* thread = new base::Thread("WinCompositor"); + if (!thread->StartWithOptions(options)) { + delete thread; + return; + } + + sWinCompositorWindowThread = new WinCompositorWindowThread(thread); +} + +/* static */ +void WinCompositorWindowThread::ShutDown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sWinCompositorWindowThread); + + sWinCompositorWindowThread->mHasAttemptedShutdown = true; + + // Our thread could hang while we're waiting for it to stop. + // Since we're shutting down, that's not a critical problem. + // We set a reasonable amount of time to wait for shutdown, + // and if it succeeds within that time, we correctly stop + // our thread by nulling out the refptr, which will cause it + // to be deallocated and join the thread. If it times out, + // we do nothing, which means that the thread will not be + // joined and sWinCompositorWindowThread memory will leak. + CVStatus status; + { + // It's important to hold the lock before posting the + // runnable. This ensures that the runnable can't begin + // until we've started our Wait, which prevents us from + // Waiting on a monitor that has already been notified. + MonitorAutoLock lock(sWinCompositorWindowThread->mMonitor); + + static const TimeDuration TIMEOUT = TimeDuration::FromSeconds(2.0); + RefPtr<Runnable> runnable = + NewRunnableMethod("WinCompositorWindowThread::ShutDownTask", + sWinCompositorWindowThread.get(), + &WinCompositorWindowThread::ShutDownTask); + Loop()->PostTask(runnable.forget()); + + // Monitor uses SleepConditionVariableSRW, which can have + // spurious wakeups which are reported as timeouts, so we + // check timestamps to ensure that we've waited as long we + // intended to. If we wake early, we don't bother calculating + // a precise amount for the next wait; we just wait the same + // amount of time. This means timeout might happen after as + // much as 2x the TIMEOUT time. + TimeStamp timeStart = TimeStamp::NowLoRes(); + do { + status = sWinCompositorWindowThread->mMonitor.Wait(TIMEOUT); + } while ((status == CVStatus::Timeout) && + ((TimeStamp::NowLoRes() - timeStart) < TIMEOUT)); + } + + if (status == CVStatus::NoTimeout) { + sWinCompositorWindowThread = nullptr; + } +} + +void WinCompositorWindowThread::ShutDownTask() { + MonitorAutoLock lock(mMonitor); + + MOZ_ASSERT(IsInCompositorWindowThread()); + mMonitor.NotifyAll(); +} + +/* static */ +MessageLoop* WinCompositorWindowThread::Loop() { + return sWinCompositorWindowThread + ? sWinCompositorWindowThread->mThread->message_loop() + : nullptr; +} + +/* static */ +bool WinCompositorWindowThread::IsInCompositorWindowThread() { + return sWinCompositorWindowThread && + sWinCompositorWindowThread->mThread->thread_id() == + PlatformThread::CurrentId(); +} + +const wchar_t kClassNameCompositorInitalParent[] = + L"MozillaCompositorInitialParentClass"; +const wchar_t kClassNameCompositor[] = L"MozillaCompositorWindowClass"; + +ATOM g_compositor_inital_parent_window_class; +ATOM g_compositor_window_class; + +// This runs on the window owner thread. +void InitializeInitialParentWindowClass() { + if (g_compositor_inital_parent_window_class) { + return; + } + + WNDCLASSW wc; + wc.style = 0; + wc.lpfnWndProc = ::DefWindowProcW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = kClassNameCompositorInitalParent; + g_compositor_inital_parent_window_class = ::RegisterClassW(&wc); +} + +// This runs on the window owner thread. +void InitializeWindowClass() { + if (g_compositor_window_class) { + return; + } + + WNDCLASSW wc; + wc.style = CS_OWNDC; + wc.lpfnWndProc = InputEventRejectingWindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = kClassNameCompositor; + g_compositor_window_class = ::RegisterClassW(&wc); +} + +/* static */ +WinCompositorWnds WinCompositorWindowThread::CreateCompositorWindow() { + MOZ_ASSERT(Loop()); + + if (!Loop()) { + return WinCompositorWnds(nullptr, nullptr); + } + + layers::SynchronousTask task("Create compositor window"); + + HWND initialParentWnd = nullptr; + HWND compositorWnd = nullptr; + + RefPtr<Runnable> runnable = NS_NewRunnableFunction( + "WinCompositorWindowThread::CreateCompositorWindow::Runnable", [&]() { + layers::AutoCompleteTask complete(&task); + + InitializeInitialParentWindowClass(); + InitializeWindowClass(); + + // Create initial parent window. + // We could not directly create a compositor window with a main window + // as parent window, so instead create it with a temporary placeholder + // parent. Its parent is set as main window in UI process. + initialParentWnd = + ::CreateWindowEx(WS_EX_TOOLWINDOW, kClassNameCompositorInitalParent, + nullptr, WS_POPUP | WS_DISABLED, 0, 0, 1, 1, + nullptr, 0, GetModuleHandle(nullptr), 0); + if (!initialParentWnd) { + gfxCriticalNoteOnce << "Inital parent window failed " + << ::GetLastError(); + return; + } + + DWORD extendedStyle = WS_EX_NOPARENTNOTIFY | WS_EX_NOREDIRECTIONBITMAP; + + if (!StaticPrefs::apz_windows_force_disable_direct_manipulation()) { + extendedStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT; + } + + compositorWnd = ::CreateWindowEx( + extendedStyle, kClassNameCompositor, nullptr, + WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0, 1, 1, + initialParentWnd, 0, GetModuleHandle(nullptr), 0); + if (!compositorWnd) { + gfxCriticalNoteOnce << "Compositor window failed " + << ::GetLastError(); + } + }); + + Loop()->PostTask(runnable.forget()); + + task.Wait(); + + return WinCompositorWnds(compositorWnd, initialParentWnd); +} + +/* static */ +void WinCompositorWindowThread::DestroyCompositorWindow( + WinCompositorWnds aWnds) { + MOZ_ASSERT(aWnds.mCompositorWnd); + MOZ_ASSERT(aWnds.mInitialParentWnd); + MOZ_ASSERT(Loop()); + + if (!Loop()) { + return; + } + + RefPtr<Runnable> runnable = NS_NewRunnableFunction( + "WinCompositorWidget::CreateNativeWindow::Runnable", [aWnds]() { + ::DestroyWindow(aWnds.mCompositorWnd); + ::DestroyWindow(aWnds.mInitialParentWnd); + }); + + Loop()->PostTask(runnable.forget()); +} + +} // namespace widget +} // namespace mozilla |