diff --git a/apps/desktop/electron/link-title-window.cjs b/apps/desktop/electron/link-title-window.cjs new file mode 100644 index 00000000000..80b3af3976e --- /dev/null +++ b/apps/desktop/electron/link-title-window.cjs @@ -0,0 +1,42 @@ +'use strict' + +// Hidden BrowserWindow used by tier-2 link-title resolution: when curl can't +// read a page (bot walls, JS-rendered pages), we briefly load the URL +// in an offscreen window and read its title. That window loads arbitrary +// user-linked pages — including YouTube/`watch` URLs that autoplay — so it must +// never be allowed to emit sound. + +function linkTitleWindowOptions(partitionSession) { + return { + show: false, + width: 1280, + height: 800, + webPreferences: { + backgroundThrottling: false, + contextIsolation: true, + javascript: true, + nodeIntegration: false, + sandbox: true, + session: partitionSession, + webSecurity: true + } + } +} + +// Create the offscreen title-fetch window and immediately mute it. Without the +// mute, autoplaying media on the loaded page (e.g. a YouTube link) leaks ~2s of +// audio every time a session containing such links is re-rendered. See #49505. +function createLinkTitleWindow(BrowserWindow, partitionSession) { + const window = new BrowserWindow(linkTitleWindowOptions(partitionSession)) + + try { + window.webContents.setAudioMuted(true) + } catch { + // webContents may be unavailable in degraded/headless environments; muting + // is best-effort and the window is destroyed within a few seconds anyway. + } + + return window +} + +module.exports = { createLinkTitleWindow, linkTitleWindowOptions } diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index 0a4f8eec8ad..b4ba88a243c 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -34,6 +34,7 @@ const { SESSION_WINDOW_MIN_WIDTH } = require('./session-windows.cjs') const { canImportHermesCli, verifyHermesCli } = require('./backend-probes.cjs') +const { createLinkTitleWindow } = require('./link-title-window.cjs') const { probeGatewayWebSocket } = require('./gateway-ws-probe.cjs') const { adoptServedDashboardToken } = require('./dashboard-token.cjs') const { waitForDashboardPort } = require('./backend-ready.cjs') @@ -2980,20 +2981,7 @@ function runRenderTitleJob(rawUrl) { } try { - window = new BrowserWindow({ - show: false, - width: 1280, - height: 800, - webPreferences: { - backgroundThrottling: false, - contextIsolation: true, - javascript: true, - nodeIntegration: false, - sandbox: true, - session: partitionSession, - webSecurity: true - } - }) + window = createLinkTitleWindow(BrowserWindow, partitionSession) } catch { return finish('') }