summaryrefslogtreecommitdiffstats
path: root/dom/media/systemservices/video_engine/platform_uithread.cc
blob: 701a989a18b9d5b3a8448f96e73a3223cee448b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
 *  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