зеркало из https://github.com/mozilla/gecko-dev.git
295 строки
9.6 KiB
C++
295 строки
9.6 KiB
C++
/* -*- 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"
|
|
|
|
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
|