test: add desktopCapturer icon validation (#50261)

* chore: testing of desktopCapturer can run on arm

* fix: DesktopMediaListCaptureThread crash

Fixed a crash when Windows calls ::CoCreateInstance() in the
DesktopMediaListCaptureThread before COM is initialized.

* test: added test for desktopCapturer fetchWindowIcons

* chore: updating Chromium patch hash

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
Kanishk Ranjan
2026-04-09 00:26:27 +05:30
committed by GitHub
parent c3e3958668
commit df81a1d4ac
3 changed files with 91 additions and 1 deletions

View File

@@ -150,3 +150,4 @@ fix_use_fresh_lazynow_for_onendworkitemimpl_after_didruntask.patch
fix_pulseaudio_stream_and_icon_names.patch
fix_fire_menu_popup_start_for_dynamically_created_aria_menus.patch
feat_allow_enabling_extensions_on_custom_protocols.patch
fix_initialize_com_on_desktopmedialistcapturethread_on_windows.patch

View File

@@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Charles Kerr <charles@charleskerr.com>
Date: Tue, 24 Mar 2026 16:33:20 -0500
Subject: fix: initialize COM on DesktopMediaListCaptureThread on Windows
On Windows, the DesktopMediaListCaptureThread runs with a UI message
pump that doesn't initialize COM. When WebRTC's window capturer
enumerates windows, interacting with UWP/Metro windows causes twinapi.dll
to internally call ::CoCreateInstance(), which hits Chromium's
DCheckedCoCreateInstance hook and triggers a crash.
This patch fixes the crash by ensuring COM is initialized on the
capture thread by calling `init_com_with_mta(false)`.
diff --git a/chrome/browser/media/webrtc/native_desktop_media_list.cc b/chrome/browser/media/webrtc/native_desktop_media_list.cc
index 9a8ebb4edfb92d9fe28ae4b87463a68547ea1ab3..13446d9849c54f1bfe515c3db4d69dd181ec6d39 100644
--- a/chrome/browser/media/webrtc/native_desktop_media_list.cc
+++ b/chrome/browser/media/webrtc/native_desktop_media_list.cc
@@ -786,6 +786,13 @@ NativeDesktopMediaList::NativeDesktopMediaList(
base::MessagePumpType thread_type = base::MessagePumpType::UI;
#else
base::MessagePumpType thread_type = base::MessagePumpType::DEFAULT;
+#endif
+#if BUILDFLAG(IS_WIN)
+ // On Windows, window enumeration via webrtc::DesktopCapturer may interact
+ // with UWP/Metro windows through twinapi.dll, which internally calls
+ // CoCreateInstance. Initialize COM on this thread to prevent crashes in
+ // Chromium's DCheckedCoCreateInstance hook.
+ thread_.init_com_with_mta(false);
#endif
thread_.StartWithOptions(base::Thread::Options(thread_type, 0));

View File

@@ -15,7 +15,7 @@ function getSourceTypes (): ('window' | 'screen')[] {
return ['window', 'screen'];
}
ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('desktopCapturer', () => {
describe('desktopCapturer', () => {
it('should return a non-empty array of sources', async () => {
const sources = await desktopCapturer.getSources({ types: getSourceTypes() });
expect(sources).to.be.an('array').that.is.not.empty();
@@ -254,4 +254,61 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
destroyWindows();
}
});
// Linux doesn't return any window sources.
ifdescribe(process.platform !== 'linux')('fetchWindowIcons', function () {
// Tests are sequentially dependent
this.bail(true);
let w: BrowserWindow;
let testSource: Electron.DesktopCapturerSource | undefined;
let appIcon: Electron.NativeImage | undefined;
before(async () => {
w = new BrowserWindow({
width: 200,
height: 200,
show: true,
title: 'desktop-capturer-test-window'
});
await w.loadURL('about:blank');
const sources = await desktopCapturer.getSources({
types: ['window'],
fetchWindowIcons: true
});
testSource = sources.find(
s => s.name === 'desktop-capturer-test-window'
);
appIcon = testSource?.appIcon;
});
after(() => {
if (w) w.destroy();
});
it('should find the test window in the list of captured sources', () => {
expect(testSource, `The ${w.getTitle()} window was not found by desktopCapturer`).to.exist();
});
it('should return a non-null appIcon for the captured window', () => {
expect(appIcon, 'appIcon property is null or undefined').to.exist();
});
it('should return an appIcon that is not an empty image', () => {
expect(appIcon?.isEmpty()).to.be.false();
});
it('should return an appIcon that encodes to a valid PNG data URL', () => {
const url = appIcon?.toDataURL();
expect(url).to.be.a('string');
// This is header 'data:image/png;base64,' length;
expect(url?.length).to.be.greaterThan(22);
expect(url?.startsWith('data:image/png;base64,')).to.be.true();
});
it('should return an appIcon with dimensions greater than 0x0 pixels', () => {
const { width, height } = appIcon?.getSize() || { width: 0, height: 0 };
expect(width).to.be.greaterThan(0);
expect(height).to.be.greaterThan(0);
});
});
});