Source code

Revision control

Copy as Markdown

Other Tools

/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#if defined(WEBRTC_WIN)
# include "platform_uithread.h"
namespace rtc {
// timer id used in delayed callbacks
static const UINT_PTR kTimerId = 1;
static const wchar_t kThisProperty[] = L"ThreadWindowsUIPtr";
static const wchar_t kThreadWindow[] = L"WebrtcWindowsUIThread";
PlatformUIThread::~PlatformUIThread() {
CritScope scoped_lock(&cs_);
switch (state_) {
case State::STARTED: {
MOZ_DIAGNOSTIC_ASSERT(
false, "PlatformUIThread must be stopped before destruction");
break;
}
case State::STOPPED:
break;
case State::UNSTARTED:
break;
}
}
bool PlatformUIThread::InternalInit() {
// Create an event window for use in generating callbacks to capture
// objects.
CritScope scoped_lock(&cs_);
switch (state_) {
// We have already started there is nothing todo. Should this be assert?
case State::STARTED:
break;
// Stop() has already been called so there is likewise nothing to do.
case State::STOPPED:
break;
// Stop() has not been called yet, setup the UI thread, and set our
// state to STARTED.
case State::UNSTARTED: {
WNDCLASSW wc;
HMODULE hModule = GetModuleHandle(NULL);
if (!GetClassInfoW(hModule, kThreadWindow, &wc)) {
ZeroMemory(&wc, sizeof(WNDCLASSW));
wc.hInstance = hModule;
wc.lpfnWndProc = EventWindowProc;
wc.lpszClassName = kThreadWindow;
RegisterClassW(&wc);
}
hwnd_ = CreateWindowW(kThreadWindow, L"", 0, 0, 0, 0, 0, NULL, NULL,
hModule, NULL);
// Added in review of bug 1760843, follow up to remove 1767861
MOZ_RELEASE_ASSERT(hwnd_);
// Expected to always work but if it doesn't we should still fulfill the
// contract of always running the process loop at least a single
// iteration.
// This could be rexamined in the future.
if (hwnd_) {
SetPropW(hwnd_, kThisProperty, this);
// state_ needs to be STARTED before we request the initial timer
state_ = State::STARTED;
if (timeout_) {
// if someone set the timer before we started
RequestCallbackTimer(timeout_);
}
}
break;
}
};
return state_ == State::STARTED;
}
bool PlatformUIThread::RequestCallbackTimer(unsigned int milliseconds) {
CritScope scoped_lock(&cs_);
switch (state_) {
// InternalInit() has yet to run so we do not have a UI thread to use as a
// target of the timer. We should just remember what timer interval was
// requested and let InternalInit() call this function again when it is
// ready.
case State::UNSTARTED: {
timeout_ = milliseconds;
return false;
}
// We have already stopped, do not schedule a new timer.
case State::STOPPED:
return false;
case State::STARTED: {
if (timerid_) {
KillTimer(hwnd_, timerid_);
}
timeout_ = milliseconds;
timerid_ = SetTimer(hwnd_, kTimerId, milliseconds, NULL);
return !!timerid_;
}
}
// UNREACHABLE
}
void PlatformUIThread::Stop() {
{
RTC_DCHECK_RUN_ON(&thread_checker_);
CritScope scoped_lock(&cs_);
// Shut down the dispatch loop and let the background thread exit.
if (timerid_) {
MOZ_ASSERT(hwnd_);
KillTimer(hwnd_, timerid_);
timerid_ = 0;
}
switch (state_) {
// If we haven't started yet there is nothing to do, we will go into
// the STOPPED state at the end of the function and InternalInit()
// will not move us to STARTED.
case State::UNSTARTED:
break;
// If we have started, that means that InternalInit() has run and the
// message wait loop has or will run. We need to signal it to stop. wich
// will allow PlatformThread::Stop to join that thread.
case State::STARTED: {
MOZ_ASSERT(hwnd_);
PostMessage(hwnd_, WM_CLOSE, 0, 0);
break;
}
// We have already stopped. There is nothing to do.
case State::STOPPED:
break;
}
// Always set our state to STOPPED
state_ = State::STOPPED;
}
monitor_thread_.Finalize();
}
void PlatformUIThread::Run() {
// InternalInit() will return false when the thread is already in shutdown.
// otherwise we must run until we get a Windows WM_QUIT msg.
const bool runUntilQuitMsg = InternalInit();
// The interface contract of Start/Stop is that for a successful call to
// Start, there should be at least one call to the run function.
NativeEventCallback();
while (runUntilQuitMsg) {
// Alertable sleep to receive WM_QUIT (following a WM_CLOSE triggering a
// WM_DESTROY)
if (MsgWaitForMultipleObjectsEx(0, nullptr, INFINITE, QS_ALLINPUT,
MWMO_ALERTABLE | MWMO_INPUTAVAILABLE) ==
WAIT_OBJECT_0) {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
// THE ONLY WAY to exit the thread loop
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
}
void PlatformUIThread::NativeEventCallback() { native_event_callback_(); }
/* static */
LRESULT CALLBACK PlatformUIThread::EventWindowProc(HWND hwnd, UINT uMsg,
WPARAM wParam,
LPARAM lParam) {
if (uMsg == WM_DESTROY) {
RemovePropW(hwnd, kThisProperty);
PostQuitMessage(0);
return 0;
}
PlatformUIThread* twui =
static_cast<PlatformUIThread*>(GetPropW(hwnd, kThisProperty));
if (!twui) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
if (uMsg == WM_TIMER && wParam == kTimerId) {
twui->NativeEventCallback();
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
} // namespace rtc
#endif