#53070: perf: trim GTK and FontConfig work off the linux startup path
Description of Change
Linux app launch to ready drops ~35 ms and launch to first contentful paint ~55 ms (release build, X11, medians of 25-30 interleaved cold launches per side), from two independent main-thread costs during toolkit initialization. Two commits:
| what | launch -> app ready |
launch -> first contentful paint | |
|---|---|---|---|
| 1 | skip GDK's OpenGL probe during gtk_init() |
395.7 -> 366.7 ms (-29 ms, p=9e-14) | 618.0 -> 573.7 ms (-44 ms) |
| 2 | start loading FontConfig before the browser thread pool runs | 393.9 -> 388.1 ms (-5.8 ms, p=7e-8) | 532.0 -> 520.5 ms (-11.5 ms) |
(Row 2 was measured on a lower-variance base than row 1, hence the different absolute numbers; new BrowserWindow and everything after first paint are unchanged in both.)
1. GDK GL probe. When GTK initializes inside ui::GetDefaultLinuxUi(), GDK probes the display for GLX/EGL support (gdk_x11_screen_init_gl -> epoxy -> glXQueryServerString), which dlopen()s the GL driver into the browser process on the main thread. Electron only uses GTK for theme colors (cairo) and native dialogs, none of which touch GdkGLContext, so the probe is pure cost: here it was 27 of the 68 ms between "JS modules loaded" and ready, and the driver mappings also stay resident in the browser for the life of the app. GDK reads GDK_GL exactly once during gtk_init(), so ToolkitInitialized() sets GDK_GL=disable around that call when the user has not set it and removes it again immediately after, before any app code can spawn children. The same change is proposed for Chromium's GtkUi (https://crrev.com/c/8265951); this is the Electron-side form until that lands and covers Electron's own call site either way.
2. FontConfig warm-up. content already tries to initialize FontConfig off the main thread (gfx::InitializeFonts() from BrowserMainRunnerImpl::Initialize()), but that task is posted after BrowserMainLoop raises the ThreadPool execution fence, so it can never run before the toolkit's first font lookup does FcInit + config parsing on the main thread. ElectronMainDelegate::PreBrowserMain() runs after the pool is created and before it is started and fenced, so calling gfx::InitializeGlobalFontConfigAsync() there queues the work to start with the pool and overlap toolkit init; if it ever loses the race it degrades to today's behaviour, and the later content call is a no-op on the NoDestructor singleton. Upstream review of the equivalent content change asked for exactly this placement in the embedder (https://crrev.com/c/8264864).
Both are BUILDFLAG(IS_LINUX) only; macOS and Windows are untouched.
Checklist
- PR description included and stakeholders cc'd
-
npm testpasses - PR release notes describe the change in a way relevant to app developers, and are capitalized, punctuated, and past tense.
- I have reviewed and verified the changes
Release Notes
Notes: Improved application startup time on Linux by skipping GDK's OpenGL probe and loading FontConfig off the main thread during toolkit initialization.
Backports
Semver Impact
Semantic Versioning helps users understand the impact of updates:
- Major (X.y.z): Breaking changes that may require code modifications
- Minor (x.Y.z): New features that maintain backward compatibility
- Patch (x.y.Z): Bug fixes that don't change the API
- None: Changes that don't affect using facing parts of Electron