Repository: electron/electron
Pull request: fix: crash on `getUserMedia` with invalid `chromeMediaSourceId`

#### Description of Change

Fixes https://github.com/electron/electron/issues/45713.

When a an id from `chromeMediaSourceId` can't be parsed via `content::DesktopMediaID::Parse`, a `null` `DesktopMediaID` is returned with `Type::NONE`. Calling `ToString` on a null `DesktopMediaID` results in a `NOTREACHED` crash. This also fails in upstream - so we should error on this as well.

<details><summary>Upstream</summary>
<p>

<img width="335" alt="Screenshot 2025-02-20 at 12 30 17 PM" src="https://github.com/user-attachments/assets/6ebf5067-8501-4b2c-a73d-e3eae80686e0" />

</p>
</details> 

<details><summary>After Fix</summary>
<p>

<img width="565" alt="Screenshot 2025-02-20 at 7 58 51 PM" src="https://github.com/user-attachments/assets/f3fcc089-5768-47d1-9890-79ce860a10f8" />

</p>
</details> 

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed a potential crash when calling legacy `getUserMedia` with an invalid  `chromeMediaSourceId`.


Changed files:
filename: shell/browser/web_contents_permission_helper.cc
patch: @@ -101,16 +101,20 @@ void HandleUserMediaRequest(const content::MediaStreamRequest& request,
             : "");
   }
 
-  if (request.video_type == MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
-      request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
+  if (request.video_type == MediaStreamType::GUM_TAB_VIDEO_CAPTURE) {
+    devices_ref.video_device =
+        blink::MediaStreamDevice(request.video_type, "", "");
+  } else if (request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
+    // If the DesktopMediaID can't be successfully parsed, throw an
+    // Invalid state error to match upstream.
+    auto dm_id = GetScreenId(request.requested_video_device_ids);
+    if (dm_id.is_null()) {
+      std::move(callback).Run(blink::mojom::StreamDevicesSet(),
+                              MediaStreamRequestResult::INVALID_STATE, nullptr);
+      return;
+    }
     devices_ref.video_device = blink::MediaStreamDevice(
-        request.video_type,
-        request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE
-            ? GetScreenId(request.requested_video_device_ids).ToString()
-            : "",
-        request.video_type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE
-            ? "Screen"
-            : "");
+        request.video_type, dm_id.ToString(), "Screen");
   }
 
   bool empty = !devices_ref.audio_device.has_value() &&

filename: spec/api-media-handler-spec.ts
patch: @@ -400,6 +400,23 @@ describe('setDisplayMediaRequestHandler', () => {
     expect(ok).to.be.true(message);
   });
 
+  it('throws an error when calling legacy getUserMedia with invalid chromeMediaSourceId', async () => {
+    const w = new BrowserWindow({ show: false });
+    await w.loadURL(serverUrl);
+    const { ok, message } = await w.webContents.executeJavaScript(`
+      new Promise((resolve, reject) => navigator.getUserMedia({
+         video: {
+          mandatory: {
+            chromeMediaSource: 'desktop',
+            chromeMediaSourceId: undefined,
+          },
+        },
+      }, x => resolve({ok: x instanceof MediaStream}), e => resolve({ ok: false, message: e.message })))
+    `);
+    expect(ok).to.be.false();
+    expect(message).to.equal('Invalid state');
+  });
+
   it('can remove a displayMediaRequestHandler', async () => {
     const ses = session.fromPartition('' + Math.random());