feat: deno desktop subcommand#33441
Open
crowlKats wants to merge 155 commits into
Open
Conversation
# Conflicts: # Cargo.lock # Cargo.toml
Unstripped Linux release binaries (deno, denort, test_server) are several GB; building the denort_desktop cdylib on top exhausts the ~14 GB runner disk. Strip between the two cargo build invocations to free ~80% of that space.
The denort_desktop cdylib (libdenort.so) links the entire Deno+V8 runtime as a shared library. Building it after the first deno/denort/test_server release build exceeds the ~7 GB RAM available on GitHub-hosted linux runners, OOM-killing the runner agent and hanging the CI job indefinitely. Gate the Build denort_desktop step on main/tags or ci-full PRs only. PR builds only need the deno binary (for WPT tests); they don't need the cdylib artifact. Also guard the Pre-release (linux) libdenort.so zip step with an existence check so it skips gracefully when the cdylib wasn't built.
Remove large pre-installed tool suites (Android SDK, PowerShell, dotnet) and prune Docker images before restoring the Cargo cache and building. Frees ~10-20 GB on ubuntu-24.04 runners, preventing OOM/disk exhaustion during ThinLTO V8 linking for the release build.
Deno's JSON.stringify writes matched surrogate pairs as raw UTF-8 (e.g. 🔥 instead of 🔥) and writes U+007F as a raw byte rather than the � escape sequence. Update url.json, urlpattern.json, and xhr.json to match the format the WPT runner actually produces, so the file-diff check passes. Lone surrogates (e.g. \ud83d \udeb2 separated by a space) remain as \uXXXX escapes since they cannot be encoded as UTF-8.
- mimesniff.json: replace escaped � with raw DEL byte; JS JSON.stringify does not escape U+007F so the WPT tool writes raw bytes - url.json: fix sort order for non-BMP chars; JS sorts by UTF-16 code units so U+1FFFE (high surrogate 0xD83F=55359) sorts before U+FEFF (65279) and U+FFFA (65530), opposite of Python codepoint order
The test consistently fails with 'Blob for the given URL not found' when the worker tries to load its script from a revoked blob URL. Deno does not retain blob URL references after Worker creation, which is the behavior that test 2 relies on. This matches the main branch expectation.
spawn_blocking+Command::output() left OS threads alive when the 10-second timeout fired on a hung compiled binary. kill_on_drop(true) sends SIGKILL when the future is cancelled, preventing thread accumulation and test-binary-exit deadlock on release linux-x86_64.
Prior fix (43ce6ed) only added #[nofast] to methods that previously had explicit #[fast]. Remaining cppgc methods (getters, tuple-return methods, serde methods) were left without #[nofast], allowing op2 to generate fast calls for them. Under fat LTO on release linux-x86_64 this causes upgrade_snapshotted_ops_with_fast_calls to hang. Add #[nofast] to all instance methods and getters in BrowserWindow, Dock, Tray, and Notification cppgc impl blocks.
This reverts commit af42c62.
build_fast on release linux-x86_64 with fat LTO hangs for new desktop ops. Mark all desktop ops nofast — they call heavyweight OS APIs, so the JS-to-Rust fast-call overhead is irrelevant.
…t creation op_ctx_plain_function was introduced to avoid managed-resource serialization issues in V8 14.9, but op_ctx_function with will_snapshot=true already prevents build_fast from being called (gated by !will_snapshot in op_ctx_template), so no managed resources are ever created during snapshot building. Using op_ctx_plain_function for non-constructable ops during snapshot creation produces a V8 internal state that causes build_fast to hang at runtime in upgrade_snapshotted_ops_with_fast_calls on release linux-x86_64 with fat LTO. Reverting to the same op_ctx_function path used on main restores the snapshot structure that build_fast expects, fixing the compile_watch_test hang.
…d_ops Changing ConstructorBehavior from Allow to Throw for the top-level ops loop in upgrade_snapshotted_ops_with_fast_calls causes build_fast to deadlock on release linux-x86_64 with fat LTO. This affected all compiled binaries, not just desktop ones: every deno compile test hung before producing output. Keep ConstructorBehavior::Allow in the upgrade path (matching main), which is safe since ops are not constructors at runtime and this path is only hit once during startup. ConstructorBehavior::Throw is still used correctly in initialize_deno_core_ops_bindings via op_ctx_constructor_behavior.
…shot build_fast() crashes (SIGABRT/__cxa_guard_acquire mutex) on release linux-x86_64 with fat LTO when called after deserializing a snapshot that contains cppgc objects. Skip upgrade_snapshotted_ops_with_fast_calls entirely when op_method_decls is non-empty (indicating cppgc types are registered). Desktop ops are all nofast anyway so no functionality is lost. Also fixes dprint formatting for the op_ctx_function call site.
…tion warning Deno.serve now emits a deprecation warning when request.signal is used with legacy abort behavior. The test expectation must include this warning.
Compiled binaries (denort) were using skip_op_registration=true which skips initialize_deno_core_ops_bindings. The CLI snapshot now includes the desktop extension with cppgc class types (BrowserWindow, Dock, Tray, Notification). With skip_op_registration=true these cppgc templates are deserialized from the snapshot but their GCInfo is never pre-initialized, which triggers a __cxa_guard_acquire recursive-initialization SIGABRT on release linux-x86_64 with fat LTO at first startup. Setting skip_op_registration=false forces initialize_deno_core_ops_bindings to run, matching the deno run code path which does not crash.
# Conflicts: # tests/specs/serve/request_signal_streaming/main.out
… with non-empty op_method_decls With the crypto cppgc rewrite (f93c548) now in the branch, op_method_decls is non-empty, causing the upgrade_snapshotted_ops_with_fast_calls early-return to actually fire and prevent build_fast() calls that were triggering recursive C++ static-init (SIGABRT) on release linux-x86_64 with fat LTO. The skip_op_registration=false change made initialize_deno_core_ops_bindings run instead, which also called build_fast() for crypto cppgc ops causing hangs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the
deno desktopsubcommand for building desktop apps from Deno projects.Core:
deno desktop <entry>compiles a project into a self-contained desktop app, built on WEF (prebuilt backends downloaded fromgithub.com/denoland/wef/releases, pinned via Cargo.lock, SHA256-verified, cached under<deno_dir>/wef/<version>/).WEF_DEV_DIRpoints at a local wef checkout for development.cef(default, bundled Chromium),webview(OS webview),raw(winit, no engine).Deno.serve()in the entry auto-binds to the port the webview navigates to (viaDENO_SERVE_ADDRESS).Framework auto-detection (
cli/tools/framework.rs): Next.js, Astro, Fresh, Remix, Nuxt, SvelteKit, SolidStart, TanStack Start, Vite SSR. Prod server runs by default; dev server runs under--hmr.HMR (
--hmr): framework projects use the framework's dev server; non-framework apps use file-watch +Debugger.setScriptSourcehot-swap with the runtime and CEF staying alive.Deno.BrowserWindowAPI (cli/tsc/dts/lib.deno.desktop.d.ts): window lifecycle (show/hide/focus/close/reload), size/position, always-on-top, navigation,bind/unbindRPC to webview JS viabindings.<name>(),executeJs, app/context menus, native window handle, keyboard/mouse/wheel/resize/focus events.Runtime integration:
prompt()/alert()/confirm()become native popups; uncaught errors show a native alert and optionallyPOSTtodesktop.errorReporting.url.Auto-updater:
Deno.desktopVersion+Deno.autoUpdate({ url, interval, onUpdateReady, onRollback }). Polls<url>/latest.json, applies bsdiff patches (qbsdiff) to the dylib, stages for next launch, rolls back on failed launch.Unified DevTools (
--inspect/--inspect-brk/--inspect-wait): single DevTools session showing both the Deno runtime V8 and the CEF renderer V8 as attached targets — one Console dropdown (Renderer / Deno), one Sources panel with both threads. Implemented as a CDP multiplexer incli/tools/desktop_devtools.rs.--inspect-brkpauses both isolates (Deno via its own mechanism, CEF via injectedDebugger.enable+Debugger.pausebefore navigation).Dock / Tray:
Deno.dock(macOS) andDeno.Tray(cross-platform) for status-area icons with tooltips, dark-mode icons, and context menus.Distribution / cross-compile:
--targetand--all-targetsdownload prebuilt denort + WEF backends for the target triple. Outputs:.appbundle (framework underContents/Frameworks/),.dmgviahdiutil.exe+ DLLs directory.AppImageviaappimagetoolOS-specific limitations
Several features are currently wired up for a subset of platforms. They degrade gracefully (the rest of the app still works) but are worth calling out:
apply_pending_update,get_dylib_path, andAutoUpdateStateare all#[cfg(unix)]. On non-unix (Windows)update_rolled_backis hardcoded tofalseandauto_update_stateisNone, so the staged-update / patch-apply / rollback path is a no-op there. Bringing it to Windows means implementing the dylib-swap + pending-update application for that platform (likely behind a small platform module so the#[cfg]branches inrun_desktop/laufey::main!collapse).promote_dylib_symbols_to_global(re-dlopens the runtime dylib withRTLD_GLOBALso NAPI symbols are visible to addons like next-swc) is#[cfg(unix)]. On Windows, frameworks that pull native addons may fail to load those addons.disclaim_spawn(posix_spawn with TCC responsibility disclaimed) so it becomes its own permission principal; without itUNUserNotificationCenter.requestAuthorizationfails. The non-macOS path is a plaintokiospawn withkill_on_drop.--ozone-platform=x11+GDK_BACKEND=x11, because the LAUFEY mouse/focus/resize event monitor uses XI2 on X11. On Wayland sessions it runs through XWayland; native Wayland is unsupported.Deno.dockis macOS-only (no-op / unavailable elsewhere).--hmrmode (any platform).IconConfig::Seterrors with "icon sets are not supported in --hmr mode yet"; only a single icon path works under--hmr.Not yet implemented
macos.codesignIdentityindeno.json, with an ad-hoc signature fallback for unsigned builds), but there's nonotarytoolsubmission /staplerstep yet..deb/.rpminstallersCloses #3234