xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/win/wgc_capturer_win_unittest.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "modules/desktop_capture/win/wgc_capturer_win.h"
12 
13 #include <string>
14 #include <utility>
15 #include <vector>
16 
17 #include "modules/desktop_capture/desktop_capture_options.h"
18 #include "modules/desktop_capture/desktop_capture_types.h"
19 #include "modules/desktop_capture/desktop_capturer.h"
20 #include "modules/desktop_capture/win/test_support/test_window.h"
21 #include "modules/desktop_capture/win/wgc_capture_session.h"
22 #include "modules/desktop_capture/win/window_capture_utils.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/logging.h"
25 #include "rtc_base/task_queue_for_test.h"
26 #include "rtc_base/thread.h"
27 #include "rtc_base/time_utils.h"
28 #include "rtc_base/win/scoped_com_initializer.h"
29 #include "rtc_base/win/windows_version.h"
30 #include "system_wrappers/include/metrics.h"
31 #include "system_wrappers/include/sleep.h"
32 #include "test/gtest.h"
33 
34 namespace webrtc {
35 namespace {
36 
37 constexpr char kWindowThreadName[] = "wgc_capturer_test_window_thread";
38 constexpr WCHAR kWindowTitle[] = L"WGC Capturer Test Window";
39 
40 constexpr char kCapturerImplHistogram[] =
41     "WebRTC.DesktopCapture.Win.DesktopCapturerImpl";
42 
43 constexpr char kCapturerResultHistogram[] =
44     "WebRTC.DesktopCapture.Win.WgcCapturerResult";
45 constexpr int kSuccess = 0;
46 constexpr int kSessionStartFailure = 4;
47 
48 constexpr char kCaptureSessionResultHistogram[] =
49     "WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult";
50 constexpr int kSourceClosed = 1;
51 
52 constexpr char kCaptureTimeHistogram[] =
53     "WebRTC.DesktopCapture.Win.WgcCapturerFrameTime";
54 
55 // The capturer keeps `kNumBuffers` in its frame pool, so we need to request
56 // that many frames to clear those out. The next frame will have the new size
57 // (if the size has changed) so we will resize the frame pool at this point.
58 // Then, we need to clear any frames that may have delivered to the frame pool
59 // before the resize. Finally, the next frame will be guaranteed to be the new
60 // size.
61 constexpr int kNumCapturesToFlushBuffers =
62     WgcCaptureSession::kNumBuffers * 2 + 1;
63 
64 constexpr int kSmallWindowWidth = 200;
65 constexpr int kSmallWindowHeight = 100;
66 constexpr int kMediumWindowWidth = 300;
67 constexpr int kMediumWindowHeight = 200;
68 constexpr int kLargeWindowWidth = 400;
69 constexpr int kLargeWindowHeight = 500;
70 
71 // The size of the image we capture is slightly smaller than the actual size of
72 // the window.
73 constexpr int kWindowWidthSubtrahend = 14;
74 constexpr int kWindowHeightSubtrahend = 7;
75 
76 // Custom message constants so we can direct our thread to close windows and
77 // quit running.
78 constexpr UINT kDestroyWindow = WM_APP;
79 constexpr UINT kQuitRunning = WM_APP + 1;
80 
81 // When testing changes to real windows, sometimes the effects (close or resize)
82 // don't happen immediately, we want to keep trying until we see the effect but
83 // only for a reasonable amount of time.
84 constexpr int kMaxTries = 50;
85 
86 }  // namespace
87 
88 class WgcCapturerWinTest : public ::testing::TestWithParam<CaptureType>,
89                            public DesktopCapturer::Callback {
90  public:
SetUp()91   void SetUp() override {
92     com_initializer_ =
93         std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
94     EXPECT_TRUE(com_initializer_->Succeeded());
95 
96     if (!IsWgcSupported(GetParam())) {
97       RTC_LOG(LS_INFO)
98           << "Skipping WgcCapturerWinTests on unsupported platforms.";
99       GTEST_SKIP();
100     }
101   }
102 
SetUpForWindowCapture(int window_width=kMediumWindowWidth,int window_height=kMediumWindowHeight)103   void SetUpForWindowCapture(int window_width = kMediumWindowWidth,
104                              int window_height = kMediumWindowHeight) {
105     capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
106         DesktopCaptureOptions::CreateDefault());
107     CreateWindowOnSeparateThread(window_width, window_height);
108     StartWindowThreadMessageLoop();
109     source_id_ = GetTestWindowIdFromSourceList();
110   }
111 
SetUpForScreenCapture()112   void SetUpForScreenCapture() {
113     capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
114         DesktopCaptureOptions::CreateDefault());
115     source_id_ = GetScreenIdFromSourceList();
116   }
117 
TearDown()118   void TearDown() override {
119     if (window_open_) {
120       CloseTestWindow();
121     }
122   }
123 
124   // The window must live on a separate thread so that we can run a message pump
125   // without blocking the test thread. This is necessary if we are interested in
126   // having GraphicsCaptureItem events (i.e. the Closed event) fire, and it more
127   // closely resembles how capture works in the wild.
CreateWindowOnSeparateThread(int window_width,int window_height)128   void CreateWindowOnSeparateThread(int window_width, int window_height) {
129     window_thread_ = rtc::Thread::Create();
130     window_thread_->SetName(kWindowThreadName, nullptr);
131     window_thread_->Start();
132     SendTask(window_thread_.get(), [this, window_width, window_height]() {
133       window_thread_id_ = GetCurrentThreadId();
134       window_info_ =
135           CreateTestWindow(kWindowTitle, window_height, window_width);
136       window_open_ = true;
137 
138       while (!IsWindowResponding(window_info_.hwnd)) {
139         RTC_LOG(LS_INFO) << "Waiting for test window to become responsive in "
140                             "WgcWindowCaptureTest.";
141       }
142 
143       while (!IsWindowValidAndVisible(window_info_.hwnd)) {
144         RTC_LOG(LS_INFO) << "Waiting for test window to be visible in "
145                             "WgcWindowCaptureTest.";
146       }
147     });
148 
149     ASSERT_TRUE(window_thread_->RunningForTest());
150     ASSERT_FALSE(window_thread_->IsCurrent());
151   }
152 
StartWindowThreadMessageLoop()153   void StartWindowThreadMessageLoop() {
154     window_thread_->PostTask([this]() {
155       MSG msg;
156       BOOL gm;
157       while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
158         ::DispatchMessage(&msg);
159         if (msg.message == kDestroyWindow) {
160           DestroyTestWindow(window_info_);
161         }
162         if (msg.message == kQuitRunning) {
163           PostQuitMessage(0);
164         }
165       }
166     });
167   }
168 
CloseTestWindow()169   void CloseTestWindow() {
170     ::PostThreadMessage(window_thread_id_, kDestroyWindow, 0, 0);
171     ::PostThreadMessage(window_thread_id_, kQuitRunning, 0, 0);
172     window_thread_->Stop();
173     window_open_ = false;
174   }
175 
GetTestWindowIdFromSourceList()176   DesktopCapturer::SourceId GetTestWindowIdFromSourceList() {
177     // Frequently, the test window will not show up in GetSourceList because it
178     // was created too recently. Since we are confident the window will be found
179     // eventually we loop here until we find it.
180     intptr_t src_id = 0;
181     do {
182       DesktopCapturer::SourceList sources;
183       EXPECT_TRUE(capturer_->GetSourceList(&sources));
184       auto it = std::find_if(
185           sources.begin(), sources.end(),
186           [&](const DesktopCapturer::Source& src) {
187             return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
188           });
189 
190       if (it != sources.end())
191         src_id = it->id;
192     } while (src_id != reinterpret_cast<intptr_t>(window_info_.hwnd));
193 
194     return src_id;
195   }
196 
GetScreenIdFromSourceList()197   DesktopCapturer::SourceId GetScreenIdFromSourceList() {
198     DesktopCapturer::SourceList sources;
199     EXPECT_TRUE(capturer_->GetSourceList(&sources));
200     EXPECT_GT(sources.size(), 0ULL);
201     return sources[0].id;
202   }
203 
DoCapture(int num_captures=1)204   void DoCapture(int num_captures = 1) {
205     // Capture the requested number of frames. We expect the first capture to
206     // always succeed. If we're asked for multiple frames, we do expect to see a
207     // a couple dropped frames due to resizing the window.
208     const int max_tries = num_captures == 1 ? 1 : kMaxTries;
209     int success_count = 0;
210     for (int i = 0; success_count < num_captures && i < max_tries; i++) {
211       capturer_->CaptureFrame();
212       if (result_ == DesktopCapturer::Result::ERROR_PERMANENT)
213         break;
214       if (result_ == DesktopCapturer::Result::SUCCESS)
215         success_count++;
216     }
217 
218     total_successful_captures_ += success_count;
219     EXPECT_EQ(success_count, num_captures);
220     EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS);
221     EXPECT_TRUE(frame_);
222     EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSuccess),
223               total_successful_captures_);
224   }
225 
ValidateFrame(int expected_width,int expected_height)226   void ValidateFrame(int expected_width, int expected_height) {
227     EXPECT_EQ(frame_->size().width(), expected_width - kWindowWidthSubtrahend);
228     EXPECT_EQ(frame_->size().height(),
229               expected_height - kWindowHeightSubtrahend);
230 
231     // Verify the buffer contains as much data as it should.
232     int data_length = frame_->stride() * frame_->size().height();
233 
234     // The first and last pixel should have the same color because they will be
235     // from the border of the window.
236     // Pixels have 4 bytes of data so the whole pixel needs a uint32_t to fit.
237     uint32_t first_pixel = static_cast<uint32_t>(*frame_->data());
238     uint32_t last_pixel = static_cast<uint32_t>(
239         *(frame_->data() + data_length - DesktopFrame::kBytesPerPixel));
240     EXPECT_EQ(first_pixel, last_pixel);
241 
242     // Let's also check a pixel from the middle of the content area, which the
243     // test window will paint a consistent color for us to verify.
244     uint8_t* middle_pixel = frame_->data() + (data_length / 2);
245 
246     int sub_pixel_offset = DesktopFrame::kBytesPerPixel / 4;
247     EXPECT_EQ(*middle_pixel, kTestWindowBValue);
248     middle_pixel += sub_pixel_offset;
249     EXPECT_EQ(*middle_pixel, kTestWindowGValue);
250     middle_pixel += sub_pixel_offset;
251     EXPECT_EQ(*middle_pixel, kTestWindowRValue);
252     middle_pixel += sub_pixel_offset;
253 
254     // The window is opaque so we expect 0xFF for the Alpha channel.
255     EXPECT_EQ(*middle_pixel, 0xFF);
256   }
257 
258   // DesktopCapturer::Callback interface
259   // The capturer synchronously invokes this method before `CaptureFrame()`
260   // returns.
OnCaptureResult(DesktopCapturer::Result result,std::unique_ptr<DesktopFrame> frame)261   void OnCaptureResult(DesktopCapturer::Result result,
262                        std::unique_ptr<DesktopFrame> frame) override {
263     result_ = result;
264     frame_ = std::move(frame);
265   }
266 
267  protected:
268   std::unique_ptr<ScopedCOMInitializer> com_initializer_;
269   DWORD window_thread_id_;
270   std::unique_ptr<rtc::Thread> window_thread_;
271   WindowInfo window_info_;
272   intptr_t source_id_;
273   bool window_open_ = false;
274   DesktopCapturer::Result result_;
275   int total_successful_captures_ = 0;
276   std::unique_ptr<DesktopFrame> frame_;
277   std::unique_ptr<DesktopCapturer> capturer_;
278 };
279 
TEST_P(WgcCapturerWinTest,SelectValidSource)280 TEST_P(WgcCapturerWinTest, SelectValidSource) {
281   if (GetParam() == CaptureType::kWindow) {
282     SetUpForWindowCapture();
283   } else {
284     SetUpForScreenCapture();
285   }
286 
287   EXPECT_TRUE(capturer_->SelectSource(source_id_));
288 }
289 
TEST_P(WgcCapturerWinTest,SelectInvalidSource)290 TEST_P(WgcCapturerWinTest, SelectInvalidSource) {
291   if (GetParam() == CaptureType::kWindow) {
292     capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
293         DesktopCaptureOptions::CreateDefault());
294     source_id_ = kNullWindowId;
295   } else {
296     capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
297         DesktopCaptureOptions::CreateDefault());
298     source_id_ = kInvalidScreenId;
299   }
300 
301   EXPECT_FALSE(capturer_->SelectSource(source_id_));
302 }
303 
TEST_P(WgcCapturerWinTest,Capture)304 TEST_P(WgcCapturerWinTest, Capture) {
305   if (GetParam() == CaptureType::kWindow) {
306     SetUpForWindowCapture();
307   } else {
308     SetUpForScreenCapture();
309   }
310 
311   EXPECT_TRUE(capturer_->SelectSource(source_id_));
312 
313   capturer_->Start(this);
314   EXPECT_GE(metrics::NumEvents(kCapturerImplHistogram,
315                                DesktopCapturerId::kWgcCapturerWin),
316             1);
317 
318   DoCapture();
319   EXPECT_GT(frame_->size().width(), 0);
320   EXPECT_GT(frame_->size().height(), 0);
321 }
322 
TEST_P(WgcCapturerWinTest,CaptureTime)323 TEST_P(WgcCapturerWinTest, CaptureTime) {
324   if (GetParam() == CaptureType::kWindow) {
325     SetUpForWindowCapture();
326   } else {
327     SetUpForScreenCapture();
328   }
329 
330   EXPECT_TRUE(capturer_->SelectSource(source_id_));
331   capturer_->Start(this);
332 
333   int64_t start_time;
334   start_time = rtc::TimeNanos();
335   capturer_->CaptureFrame();
336 
337   int capture_time_ms =
338       (rtc::TimeNanos() - start_time) / rtc::kNumNanosecsPerMillisec;
339   EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS);
340   EXPECT_TRUE(frame_);
341 
342   // The test may measure the time slightly differently than the capturer. So we
343   // just check if it's within 5 ms.
344   EXPECT_NEAR(frame_->capture_time_ms(), capture_time_ms, 5);
345   EXPECT_GE(
346       metrics::NumEvents(kCaptureTimeHistogram, frame_->capture_time_ms()), 1);
347 }
348 
349 INSTANTIATE_TEST_SUITE_P(SourceAgnostic,
350                          WgcCapturerWinTest,
351                          ::testing::Values(CaptureType::kWindow,
352                                            CaptureType::kScreen));
353 
TEST(WgcCapturerNoMonitorTest,NoMonitors)354 TEST(WgcCapturerNoMonitorTest, NoMonitors) {
355   ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA);
356   EXPECT_TRUE(com_initializer.Succeeded());
357   if (HasActiveDisplay()) {
358     RTC_LOG(LS_INFO) << "Skip WgcCapturerWinTest designed specifically for "
359                         "systems with no monitors";
360     GTEST_SKIP();
361   }
362 
363   // A bug in `CreateForMonitor` prevents screen capture when no displays are
364   // attached.
365   EXPECT_FALSE(IsWgcSupported(CaptureType::kScreen));
366 
367   // A bug in the DWM (Desktop Window Manager) prevents it from providing image
368   // data if there are no displays attached. This was fixed in Windows 11.
369   if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN11)
370     EXPECT_FALSE(IsWgcSupported(CaptureType::kWindow));
371   else
372     EXPECT_TRUE(IsWgcSupported(CaptureType::kWindow));
373 }
374 
375 class WgcCapturerMonitorTest : public WgcCapturerWinTest {
376  public:
SetUp()377   void SetUp() {
378     com_initializer_ =
379         std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
380     EXPECT_TRUE(com_initializer_->Succeeded());
381 
382     if (!IsWgcSupported(CaptureType::kScreen)) {
383       RTC_LOG(LS_INFO)
384           << "Skipping WgcCapturerWinTests on unsupported platforms.";
385       GTEST_SKIP();
386     }
387   }
388 };
389 
TEST_F(WgcCapturerMonitorTest,FocusOnMonitor)390 TEST_F(WgcCapturerMonitorTest, FocusOnMonitor) {
391   SetUpForScreenCapture();
392   EXPECT_TRUE(capturer_->SelectSource(0));
393 
394   // You can't set focus on a monitor.
395   EXPECT_FALSE(capturer_->FocusOnSelectedSource());
396 }
397 
TEST_F(WgcCapturerMonitorTest,CaptureAllMonitors)398 TEST_F(WgcCapturerMonitorTest, CaptureAllMonitors) {
399   SetUpForScreenCapture();
400   EXPECT_TRUE(capturer_->SelectSource(kFullDesktopScreenId));
401 
402   capturer_->Start(this);
403   DoCapture();
404   EXPECT_GT(frame_->size().width(), 0);
405   EXPECT_GT(frame_->size().height(), 0);
406 }
407 
408 class WgcCapturerWindowTest : public WgcCapturerWinTest {
409  public:
SetUp()410   void SetUp() {
411     com_initializer_ =
412         std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
413     EXPECT_TRUE(com_initializer_->Succeeded());
414 
415     if (!IsWgcSupported(CaptureType::kWindow)) {
416       RTC_LOG(LS_INFO)
417           << "Skipping WgcCapturerWinTests on unsupported platforms.";
418       GTEST_SKIP();
419     }
420   }
421 };
422 
TEST_F(WgcCapturerWindowTest,FocusOnWindow)423 TEST_F(WgcCapturerWindowTest, FocusOnWindow) {
424   capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
425       DesktopCaptureOptions::CreateDefault());
426   window_info_ = CreateTestWindow(kWindowTitle);
427   source_id_ = GetScreenIdFromSourceList();
428 
429   EXPECT_TRUE(capturer_->SelectSource(source_id_));
430   EXPECT_TRUE(capturer_->FocusOnSelectedSource());
431 
432   HWND hwnd = reinterpret_cast<HWND>(source_id_);
433   EXPECT_EQ(hwnd, ::GetActiveWindow());
434   EXPECT_EQ(hwnd, ::GetForegroundWindow());
435   EXPECT_EQ(hwnd, ::GetFocus());
436   DestroyTestWindow(window_info_);
437 }
438 
TEST_F(WgcCapturerWindowTest,SelectMinimizedWindow)439 TEST_F(WgcCapturerWindowTest, SelectMinimizedWindow) {
440   SetUpForWindowCapture();
441   MinimizeTestWindow(reinterpret_cast<HWND>(source_id_));
442   EXPECT_FALSE(capturer_->SelectSource(source_id_));
443 
444   UnminimizeTestWindow(reinterpret_cast<HWND>(source_id_));
445   EXPECT_TRUE(capturer_->SelectSource(source_id_));
446 }
447 
TEST_F(WgcCapturerWindowTest,SelectClosedWindow)448 TEST_F(WgcCapturerWindowTest, SelectClosedWindow) {
449   SetUpForWindowCapture();
450   EXPECT_TRUE(capturer_->SelectSource(source_id_));
451 
452   CloseTestWindow();
453   EXPECT_FALSE(capturer_->SelectSource(source_id_));
454 }
455 
TEST_F(WgcCapturerWindowTest,UnsupportedWindowStyle)456 TEST_F(WgcCapturerWindowTest, UnsupportedWindowStyle) {
457   // Create a window with the WS_EX_TOOLWINDOW style, which WGC does not
458   // support.
459   window_info_ = CreateTestWindow(kWindowTitle, kMediumWindowWidth,
460                                   kMediumWindowHeight, WS_EX_TOOLWINDOW);
461   capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
462       DesktopCaptureOptions::CreateDefault());
463   DesktopCapturer::SourceList sources;
464   EXPECT_TRUE(capturer_->GetSourceList(&sources));
465   auto it = std::find_if(
466       sources.begin(), sources.end(), [&](const DesktopCapturer::Source& src) {
467         return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
468       });
469 
470   // We should not find the window, since we filter for unsupported styles.
471   EXPECT_EQ(it, sources.end());
472   DestroyTestWindow(window_info_);
473 }
474 
TEST_F(WgcCapturerWindowTest,IncreaseWindowSizeMidCapture)475 TEST_F(WgcCapturerWindowTest, IncreaseWindowSizeMidCapture) {
476   SetUpForWindowCapture(kSmallWindowWidth, kSmallWindowHeight);
477   EXPECT_TRUE(capturer_->SelectSource(source_id_));
478 
479   capturer_->Start(this);
480   DoCapture();
481   ValidateFrame(kSmallWindowWidth, kSmallWindowHeight);
482 
483   ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
484   DoCapture(kNumCapturesToFlushBuffers);
485   ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
486 
487   ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
488   DoCapture(kNumCapturesToFlushBuffers);
489   ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
490 }
491 
TEST_F(WgcCapturerWindowTest,ReduceWindowSizeMidCapture)492 TEST_F(WgcCapturerWindowTest, ReduceWindowSizeMidCapture) {
493   SetUpForWindowCapture(kLargeWindowWidth, kLargeWindowHeight);
494   EXPECT_TRUE(capturer_->SelectSource(source_id_));
495 
496   capturer_->Start(this);
497   DoCapture();
498   ValidateFrame(kLargeWindowWidth, kLargeWindowHeight);
499 
500   ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
501   DoCapture(kNumCapturesToFlushBuffers);
502   ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
503 
504   ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
505   DoCapture(kNumCapturesToFlushBuffers);
506   ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
507 }
508 
TEST_F(WgcCapturerWindowTest,MinimizeWindowMidCapture)509 TEST_F(WgcCapturerWindowTest, MinimizeWindowMidCapture) {
510   SetUpForWindowCapture();
511   EXPECT_TRUE(capturer_->SelectSource(source_id_));
512 
513   capturer_->Start(this);
514 
515   // Minmize the window and capture should continue but return temporary errors.
516   MinimizeTestWindow(window_info_.hwnd);
517   for (int i = 0; i < 5; ++i) {
518     capturer_->CaptureFrame();
519     EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_TEMPORARY);
520   }
521 
522   // Reopen the window and the capture should continue normally.
523   UnminimizeTestWindow(window_info_.hwnd);
524   DoCapture();
525   // We can't verify the window size here because the test window does not
526   // repaint itself after it is unminimized, but capturing successfully is still
527   // a good test.
528 }
529 
TEST_F(WgcCapturerWindowTest,CloseWindowMidCapture)530 TEST_F(WgcCapturerWindowTest, CloseWindowMidCapture) {
531   SetUpForWindowCapture();
532   EXPECT_TRUE(capturer_->SelectSource(source_id_));
533 
534   capturer_->Start(this);
535   DoCapture();
536   ValidateFrame(kMediumWindowWidth, kMediumWindowHeight);
537 
538   CloseTestWindow();
539 
540   // We need to pump our message queue so the Closed event will be delivered to
541   // the capturer's event handler. If we are too early and the Closed event
542   // hasn't arrived yet we should keep trying until the capturer receives it and
543   // stops.
544   auto* wgc_capturer = static_cast<WgcCapturerWin*>(capturer_.get());
545   MSG msg;
546   for (int i = 0;
547        wgc_capturer->IsSourceBeingCaptured(source_id_) && i < kMaxTries; ++i) {
548     // Unlike GetMessage, PeekMessage will not hang if there are no messages in
549     // the queue.
550     PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
551     SleepMs(1);
552   }
553 
554   EXPECT_FALSE(wgc_capturer->IsSourceBeingCaptured(source_id_));
555 
556   // The frame pool can buffer `kNumBuffers` frames. We must consume these
557   // and then make one more call to CaptureFrame before we expect to see the
558   // failure.
559   int num_tries = 0;
560   do {
561     capturer_->CaptureFrame();
562   } while (result_ == DesktopCapturer::Result::SUCCESS &&
563            ++num_tries <= WgcCaptureSession::kNumBuffers);
564 
565   EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSessionStartFailure),
566             1);
567   EXPECT_GE(metrics::NumEvents(kCaptureSessionResultHistogram, kSourceClosed),
568             1);
569   EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_PERMANENT);
570 }
571 
572 }  // namespace webrtc
573