Skip to content

feat: Electron-compat shim + perry electron runner (packaged apps run natively) - #5811

Draft
proggeramlug wants to merge 10 commits into
PerryTS:mainfrom
proggeramlug:feat/electron-compat-servo
Draft

proggeramlug wants to merge 10 commits into
PerryTS:mainfrom
proggeramlug:feat/electron-compat-servo

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Run existing Electron apps natively on Perry (macOS). The app's main process
compiles to a native binary with electron imports redirected to an in-repo
compat shim; the view renders in the OS-native WKWebView. Tauri-model internals,
Electron's API surface, no Chromium, no Rust to write.

The experimental Servo web-engine backend from the original version of this PR
is removed
— widget, --webview flag, the Cargo [patch], and docs/servo/.
This PR is the Electron shim plus the runner.

What's here

  • perry electron <path> (crates/perry/src/commands/electron.rs) — runs an
    app as-is. Accepts a package.json directory, a packaged macOS .app
    (Contents/Resources/app.asar, then Contents/Resources/app/), an .asar
    archive, or a main-process file. ESM and CommonJS entries both work.
  • asar reader (electron/asar.rs) — minimal, bounds-checked (entry names
    validated as single safe path components; payload offset+size checked against
    the archive before any write), with a per-user extraction cache keyed on
    path+size+mtime. Nothing is written beside the archive, so signed read-only
    bundles work.
  • --package-alias FROM=TO — repeatable compile flag; TO is a package name
    or an absolute source path. Merged after perry.packageAliases.
  • packages/electron (@perryts/electron) — app, BrowserWindow,
    webContents, ipcMain, Menu (native menubar), dialog (NSOpenPanel /
    NSSavePanel), Tray, clipboard, shell, nativeImage, plus the renderer
    bridge (ipcRenderer, contextBridge, preload require).
  • Native plumbing in perry-ui-macos — WKWebView renderer→main message
    bridge, document-start user scripts, a body-less app loop entered from the true
    top level of generated main so windows created in
    app.whenReady().then(...) composite, appQuit, full-window pinning.
  • packages/perry-react (@perryts/react) — types for Perry's createRoot
    native-window overload.
  • perry run <dir> now errors on a missing/malformed perry.toml entry instead
    of silently falling back to src/main.ts; perry init always syncs the
    perry/* tsconfig path and rebases generated paths onto an existing baseUrl.

Verification (macOS, aarch64)

E2E with a CommonJS fixture app (package.json main = main.cjs) through every
input shape — unpacked directory, .asar, packaged .app with app.asar, and a
read-only .app with an unpacked Contents/Resources/app/ — plus the in-repo
examples/system-explorer. Each one: window created, bridge reports
channel=true, ipcRenderer.invoke ↔ ipcMain.handle round-trips, page globals
isolated (require/electron/the preload runner are all undefined in the
page), did-finish-load fires and loadFile() resolves for file://, clean quit
with exit 0. Also verified once with auto-optimize on (the default path, which
rebuilds runtime+stdlib from source) — the new js_ui_loop_take_over call in
generated main survives that link.

  • scripts/run_lint_gates.sh (81 script gates + compile tier: workspace
    -D warnings check, clippy, API-docs regen + drift).
  • cargo test -p perry --bin perry: 1169 tests, all electron/init/run::entry
    tests included.
  • cargo test -p perry-codegen --lib: 1655 tests.

Known-red on main and inherited, not from this PR: lint's Public benchmark
evidence freshness
step (red on main since 2026-09-01; regenerating rewrites
published perf numbers, which is the owner's call).

Known limitations (honest)

  • macOS only — the WebView2 / WebKitGTK bridge is not wired.
  • npm long-tail — a packaged app's production node_modules are compiled
    ahead of time like the app; there is no JS runtime to fall back to, so a
    dependency Perry can't compile fails the build.
  • dialog is single-selection, no filters; showMessageBox is a stub.
    Menu.popup is a no-op.
  • IPC is JSON — no transferables/Buffer/MessagePort. A non-serializable
    handler result rejects the renderer's invoke.
  • No nodeIntegration in pages — only the preload gets require.
  • Single process — no Chromium process-isolation boundary.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Electron compatibility layers, a Servo-backed macOS WebView path, CLI updates for entry resolution and tsconfig syncing, an @perryts/react type package, a servo-script ML-KEM patch, and one additional security-audit workflow ignore.

Changes

Electron Compat and Servo Native Plumbing

Layer / File(s) Summary
UI loop takeover runtime and codegen
crates/perry-runtime/src/lib.rs, crates/perry-runtime/src/ui_loop.rs, crates/perry-codegen/src/runtime_decls/strings_part2.rs, crates/perry-codegen/src/codegen/entry.rs
Adds the runtime registration and takeover entry points, exports the runtime module, declares the takeover function in codegen support, and inserts the takeover call into compiled entry startup flow.
WebView IPC and user script bridge
crates/perry-ui-macos/src/widgets/webview.rs, crates/perry-ui-macos/src/lib_ffi/interactivity.rs, types/perry/ui/index.d.ts
Adds renderer-to-native message delivery, user script injection, Servo-aware WebView routing, file URL loading, and the matching FFI and type declarations.
Servo WebView backend
crates/perry-ui-macos/Cargo.toml, crates/perry-ui-macos/src/widgets/mod.rs, crates/perry-ui-macos/src/widgets/servo_webview.rs
Adds the feature-gated Servo WebView backend, its timer-driven rendering loop, input handling, navigation, JS evaluation, load callbacks, and supporting selection helpers.
macOS app loop and window behavior
crates/perry-ui-macos/src/app.rs, crates/perry-ui-macos/src/lib_ffi/core_widgets.rs, types/perry/ui/index.d.ts
Adds the blocking app loop, loop request plumbing, quit API, window body pinning, and window presentation updates.
Dispatch table, CLI webview flag, and library selection
crates/perry-dispatch/src/ui_table/part_b.rs, crates/perry/src/commands/compile/types.rs, crates/perry/src/commands/compile/run_pipeline.rs, crates/perry/src/commands/compile/library_search.rs, crates/perry/src/commands/dev.rs, crates/perry/src/commands/run/mod.rs
Adds the Electron-compat dispatch rows, the compile-time --webview option, the Servo selection flag, and the macOS UI library fallback logic.
Electron preload runtime
packages/electron/src/preload-runtime.ts, packages/electron/bridge/preload-runtime.js
Adds the injected document-start runtime, native transport, IPC bridge APIs, context bridge exposure, CommonJS-style loader, and heartbeat message.
Electron main-process API and package metadata
packages/electron/src/index.ts, packages/electron/package.json, packages/electron/README.md, packages/electron/DESIGN.md, packages/electron/.gitignore, package.json, packages/electron/examples/system-explorer/...
Implements the Electron-shaped main-process surface, renderer-facing placeholders, package metadata, and Electron compatibility documentation and example packaging files.

CLI Improvements

Layer / File(s) Summary
init command tsconfig sync
crates/perry/src/commands/init.rs
Updates the default tsconfig template, adds helpers to derive paths from package aliases, and merges generated paths into existing tsconfig.json files.
run command entry resolution
crates/perry/src/commands/run/entry.rs
Rewrites entry resolution to accept directory inputs and project-root resolution through perry.toml, src/main.ts, and main.ts.

@perryts/react Type Package

Layer / File(s) Summary
PerryRootOptions and createRoot augmentation
packages/perry-react/index.d.ts, packages/perry-react/package.json, packages/perry-react/tsconfig.json, packages/perry-react/README.md, packages/perry-react/LICENSE
Defines the Perry window options interface and augments react-dom/client with a `createRoot(null

Servo ML-KEM Migration and Docs

Layer / File(s) Summary
servo-script ML-KEM patch and docs
Cargo.toml, docs/servo/servo-script-ml-kem-0.3.2.patch, docs/servo/README.md
Updates servo-script to ml-kem 0.3.2, changes the related key and ciphertext handling code, and documents the Servo integration and patch workflow.

CI audit ignore

Layer / File(s) Summary
security audit workflow
.github/workflows/security-audit.yml
Adds the new cargo audit ignore entry and its explanatory comments.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • PerryTS/perry#5707: Both PRs adjust .github/workflows/security-audit.yml by changing the cargo audit ignore list.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description gives detailed Electron functionality and verification steps, but it incorrectly states that the Servo backend was removed even though the changes include substantial Servo, ML-KEM, an… Update the description to accurately include the Servo backend and ML-KEM dependency changes. Remove the claim that Servo was removed. Add the required Related issue and Checklist sections, and map the verification details to the template’s…
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary Electron compatibility shim and native runner changes. It is concise and specific.
Full details: Description check

Explanation

The description gives detailed Electron functionality and verification steps, but it incorrectly states that the Servo backend was removed even though the changes include substantial Servo, ML-KEM, and dependency updates. It also omits the required Related issue and Checklist sections.

Resolution

Update the description to accurately include the Servo backend and ML-KEM dependency changes. Remove the claim that Servo was removed. Add the required Related issue and Checklist sections, and map the verification details to the template’s Test plan section.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/perry/src/commands/run/entry.rs (1)

108-115: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

This entry extraction is too loose to parse TOML safely.

starts_with("entry") will also match keys like entrypoint, and the string slicing ignores valid TOML constructs such as inline comments or single-quoted strings. A valid perry.toml can therefore resolve to a bogus path here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/run/entry.rs` around lines 108 - 115, The
`read_perry_toml_entry_in` parser is too permissive and can misread non-`entry`
keys or TOML values. Replace the manual line scanning and string slicing with
real TOML parsing for `perry.toml`, then extract only the exact `entry` field
from the parsed structure. Update the logic in `read_perry_toml_entry_in` so it
properly handles TOML syntax like comments and quoted strings, and does not
match keys such as `entrypoint`.
🧹 Nitpick comments (1)
crates/perry-ui-macos/src/widgets/servo_webview.rs (1)

80-81: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use dirty to skip static-frame readbacks.

dirty is maintained but tick() still paints, presents, allocates, and blits every engine every 16ms. Gate the expensive readback/blit path on dirty so static pages do not burn CPU continuously.

Suggested shape
     fn tick(&self) {
         self.servo.spin_event_loop();
+        if !self.state.dirty.replace(false) {
+            return;
+        }
         self.webview.paint();
         self.rc.present();
         let (w, h) = self.size.get();
         let rect = DeviceIntRect::from_size(DeviceIntSize::new(w as i32, h as i32));
         if let Some(img) = self.rc.read_to_image(rect) {
             blit_rgba_to_image_view(&self.view, img.as_raw(), img.width(), img.height());
         }
-        self.state.dirty.set(false);
     }

Also applies to: 112-123, 409-429

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-ui-macos/src/widgets/servo_webview.rs` around lines 80 - 81,
`dirty` is currently tracked on the webview state but `tick()` still performs
the full paint/present/readback/blit path every frame. Update
`ServoWebView::tick` (and the related blit/readback helpers it calls) to check
`dirty` first and skip the expensive static-frame work when no new frame has
been reported, clearing `dirty` only after a successful blit so static pages
stop consuming CPU continuously.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-ui-macos/src/app.rs`:
- Around line 696-719: The deferred `on_ready` callback is only stored in
`PENDING_APP_LOOP_ON_READY`, so it can be collected before `ui_loop_entry` calls
`app_run_loop`. Update `request_app_loop` and `ui_loop_entry` in `app.rs` to
move the callback into a GC-rooted/runtime-managed handle instead of a
`thread_local Cell<f64>`, and ensure it stays pinned until `js_closure_call0`
runs. Use the existing `perry_runtime_register_ui_loop` / `app_run_loop` flow as
the place to wire the rooted handle through.

In `@crates/perry-ui-macos/src/widgets/servo_webview.rs`:
- Around line 54-57: The `is_servo_requested` check only reads `PERRY_WEBVIEW`,
so the compile-time Servo path selected by `--webview servo` can still fall back
to WKWebView unless the env var is set. Update `is_servo_requested` in
`servo_webview.rs` to also honor a baked-in compile-time preference or a UI
runtime setter that the codegen/selection path can call, and wire that setter
into the Servo selection flow so the UI library choice is preserved without
requiring `PERRY_WEBVIEW=servo`.
- Around line 439-468: The RGBA size checks in blit_rgba_to_image_view can
overflow because width * height * 4 is computed before casting, so large
dimensions may wrap and bypass the guard. Update the function to use checked
arithmetic or usize-based size computation for the buffer length, bytes-per-row,
and copy length, and then use the resulting safe size in both the early return
condition and the std::ptr::copy_nonoverlapping call.

In `@crates/perry-ui-macos/src/widgets/webview.rs`:
- Around line 560-568: The Servo-backed path in webview creation can return a
handle, but the Electron-facing API wiring still only looks up WKWebView state
through HANDLE_TO_KEY and webview_for_handle. Update the handle routing in the
relevant webview methods (set_on_message, add_user_script, and related
IPC/preload setup) so Servo handles either get equivalent message/script support
or are explicitly rejected/disabled for Electron windows until those contracts
exist. Ensure the Servo branch in create and the handle-to-webview lookup logic
use the same backend-specific path.
- Around line 620-636: Remove the global `allowUniversalAccessFromFileURLs`
toggle in `webview.rs` and scope file-origin relaxation to the existing
`loadFileURL:allowingReadAccessToURL:` path instead. Keep
`allowFileAccessFromFileURLs` only if needed for local subresources, and update
the WebView configuration setup around `cfg`, `prefs`, and the `NSNumber` KVC
writes so universal file access is not enabled for every `WKWebView`.

In `@crates/perry/src/commands/compile/library_search.rs`:
- Around line 998-1007: The fallback in library_search::find_library for the
macos_servo path should not silently return the WKWebView archive when --webview
servo was explicitly requested. Update the logic around the macOS Servo lookup
to treat missing libperry_ui_macos_servo.a as a build failure instead of
emitting a warning and falling back, so the explicit Servo selection in
find_library is enforced consistently.

In `@crates/perry/src/commands/compile/types.rs`:
- Around line 124-131: The `CompileArgs::webview` option currently accepts any
string and unknown values silently behave like `system` because callers only
compare `args.webview == "servo"`. Update the `webview` argument in `types.rs`
to restrict accepted values to `system` and `servo` (for example by using a
value parser or enum-backed CLI option), and make sure any parsing/validation
path rejects typos instead of falling through. Keep the existing
`CompileArgs`/`webview` symbol names as the place to apply the fix so the
runtime selection logic only sees valid values.

In `@crates/perry/src/commands/init.rs`:
- Around line 305-309: The fast path in init/tsconfig sync is incorrectly
skipping updates when packageAliases is empty, which prevents existing
tsconfig.json files from getting the always-present perry/* path mapping from
tsconfig_paths(). Update the logic in the init command so tsconfig
synchronization still runs even when aliases are empty, and only preserve the
“already exists” skip behavior when there is truly nothing to merge; use the
existing tsconfig_paths() and aliases handling in the init flow to ensure the
default Perry mapping is written for existing projects.
- Around line 161-194: The `merge_paths_into_existing` helper is preserving any
preexisting `compilerOptions.baseUrl`, which breaks the injected `paths` aliases
when a project already sets a different base directory. Update the merge logic
so the generated `paths` entries are only written when the config will resolve
them from the repo root, either by forcing `compilerOptions.baseUrl` to `"."` or
by rejecting/rewriting configs with a conflicting `baseUrl`. Keep the fix
localized in `merge_paths_into_existing` and use the existing
`render_paths_block` fallback behavior for unsupported shapes.

In `@crates/perry/src/commands/run/entry.rs`:
- Around line 86-95: The entry resolution in read_perry_toml_entry_in should not
silently fall back to src/main.ts or main.ts when perry.toml explicitly declares
an entry that cannot be resolved. Distinguish the “no entry configured” case
from an invalid configured entry, and return or propagate an error for the
broken entry path instead of continuing. Update the logic around
read_perry_toml_entry_in and its caller in the run entry flow to surface this
failure clearly.

In `@docs/servo/README.md`:
- Around line 51-59: Update the Servo README wording so it matches the actual
selection behavior in servo_webview and webview: `--webview servo` should be
described as a build-time step that links the Servo-enabled UI library, but
runtime selection still depends on `PERRY_WEBVIEW=servo` with WKWebView as the
fallback. Adjust the “At build time” and “At runtime” bullets to make it clear
that compiling with `perry compile ... --webview servo` does not by itself force
Servo at launch unless the environment variable is also set.

In `@docs/servo/servo-script-ml-kem-0.3.2.patch`:
- Around line 1-2: The patch headers currently use absolute local paths, which
makes the checked-in patch non-portable. Regenerate the patch for
servo-script-ml-kem-0.3.2.patch using repository-relative paths in the diff
headers (for example stable a/... and b/... prefixes) so the apply flow works
consistently in README, CI, and for other contributors. Update all affected diff
hunks in the patch file to use the same relative-path format.

In `@packages/electron/bridge/preload-runtime.js`:
- Around line 150-197: The preload runtime is leaking privileged APIs into the
page global scope by assigning require, electron, and __perryIpcRenderer
directly on window, which exposes ipcRenderer and the CommonJS loader to any
loaded content. Update the preload bridge in makeRequire and the window.*
exports so these capabilities are only exposed through a controlled
preload/bridge API, not as page-accessible globals. Keep the Electron shim
private to the preload context and remove direct global publication unless it is
explicitly mediated through contextBridge.

In `@packages/electron/examples/system-explorer/renderer/renderer.js`:
- Around line 23-25: The sysinfo rendering in renderer.js is building HTML
strings from untrusted IPC/filesystem values, which can lead to injection;
update the row-building logic in the sysinfo display code and the related list
rendering around the same area to use DOM APIs instead of innerHTML. In the
functions that populate `#sysinfo` and the affected rows in the 35-55 range,
create elements with `document.createElement`, assign values with `textContent`,
and use `dataset` or attributes for metadata rather than interpolating `info.*`,
`e.name`, or other dynamic fields into HTML.

In `@packages/electron/package.json`:
- Around line 2-3: The package name is conflicting with the existing public npm
package, so update the package metadata for the electron package to avoid
publishing under the plain electron name. In package.json for the electron
package, either mark it private if it is workspace-only or rename it to a
scoped/non-conflicting name, and keep any require("electron") compatibility
handled separately from the published package name.

In `@packages/electron/src/index.ts`:
- Around line 304-344: The load methods in the BrowserWindow implementation
resolve too early, so callers awaiting loadURL and loadFile can proceed before
navigation is complete. Update the BrowserWindow methods loadURL and loadFile to
return a promise that settles only after the page finishes loading, and wire
that resolution through the existing webContents/webview load lifecycle instead
of resolving immediately. Also address the file:// onLoaded gap noted in the
create-window setup so loadFile can honor the same completion contract as
loadURL.
- Around line 63-76: Serialization in resolveJs(), deliverJs(), and the
webContents.send() path can throw for non-JSON-serializable values like cycles
or BigInt, which leaves ipcRenderer.invoke() and send-based flows hanging.
Update the IPC handling in packages/electron/src/index.ts so JSON.stringify
failures are caught before building the script payload, and route those failures
through the existing error reply path used for rejected IPC calls instead of
allowing the Promise fulfillment or send path to throw. Use the
resolveJs/deliverJs helpers and the invoke/send handler code that calls them to
locate and wrap the serialization step.

In `@packages/perry-react/package.json`:
- Around line 5-10: The package export for "." in
packages/perry-react/package.json only provides types, so a bare import like
import '`@perryts/react`' has no runtime target. Update the package entrypoint so
the existing activation path resolves at runtime by adding a real runtime
export/stub for the main symbol, or remove the bare-import guidance and make the
type-only reference path the supported mechanism. Check the package.json exports
map and the related index.d.ts/package entrypoint wiring to keep runtime and
type resolution aligned.
- Around line 48-50: The React type dependencies in the package manifest are
using a shared union range, which can resolve to the wrong major for consumers.
Update the `@types/react` and `@types/react-dom` entries in the package.json
dependencies to target a single supported major, or move them to
peerDependencies so the consuming app controls the matching typings. Use the
existing dependency keys to locate the entries and split the majors cleanly.

---

Outside diff comments:
In `@crates/perry/src/commands/run/entry.rs`:
- Around line 108-115: The `read_perry_toml_entry_in` parser is too permissive
and can misread non-`entry` keys or TOML values. Replace the manual line
scanning and string slicing with real TOML parsing for `perry.toml`, then
extract only the exact `entry` field from the parsed structure. Update the logic
in `read_perry_toml_entry_in` so it properly handles TOML syntax like comments
and quoted strings, and does not match keys such as `entrypoint`.

---

Nitpick comments:
In `@crates/perry-ui-macos/src/widgets/servo_webview.rs`:
- Around line 80-81: `dirty` is currently tracked on the webview state but
`tick()` still performs the full paint/present/readback/blit path every frame.
Update `ServoWebView::tick` (and the related blit/readback helpers it calls) to
check `dirty` first and skip the expensive static-frame work when no new frame
has been reported, clearing `dirty` only after a successful blit so static pages
stop consuming CPU continuously.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c4dd21c-4061-49f9-aaac-49bb113b2f1f

📥 Commits

Reviewing files that changed from the base of the PR and between f7dfd8b and 7a97670.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (41)
  • Cargo.toml
  • crates/perry-codegen/src/codegen/entry.rs
  • crates/perry-codegen/src/runtime_decls/strings_part2.rs
  • crates/perry-dispatch/src/ui_table/part_b.rs
  • crates/perry-runtime/src/lib.rs
  • crates/perry-runtime/src/ui_loop.rs
  • crates/perry-ui-macos/Cargo.toml
  • crates/perry-ui-macos/src/app.rs
  • crates/perry-ui-macos/src/lib_ffi/core_widgets.rs
  • crates/perry-ui-macos/src/lib_ffi/interactivity.rs
  • crates/perry-ui-macos/src/widgets/mod.rs
  • crates/perry-ui-macos/src/widgets/servo_webview.rs
  • crates/perry-ui-macos/src/widgets/webview.rs
  • crates/perry/src/commands/compile/library_search.rs
  • crates/perry/src/commands/compile/run_pipeline.rs
  • crates/perry/src/commands/compile/types.rs
  • crates/perry/src/commands/dev.rs
  • crates/perry/src/commands/init.rs
  • crates/perry/src/commands/run/entry.rs
  • crates/perry/src/commands/run/mod.rs
  • docs/servo/README.md
  • docs/servo/servo-script-ml-kem-0.3.2.patch
  • package.json
  • packages/electron/.gitignore
  • packages/electron/DESIGN.md
  • packages/electron/README.md
  • packages/electron/bridge/preload-runtime.js
  • packages/electron/examples/system-explorer/main.ts
  • packages/electron/examples/system-explorer/package.json
  • packages/electron/examples/system-explorer/preload.js
  • packages/electron/examples/system-explorer/renderer/index.html
  • packages/electron/examples/system-explorer/renderer/renderer.js
  • packages/electron/package.json
  • packages/electron/src/index.ts
  • packages/electron/src/preload-runtime.ts
  • packages/perry-react/LICENSE
  • packages/perry-react/README.md
  • packages/perry-react/index.d.ts
  • packages/perry-react/package.json
  • packages/perry-react/tsconfig.json
  • types/perry/ui/index.d.ts

Comment thread crates/perry-ui-macos/src/app.rs Resolved
Comment thread packages/electron/package.json Resolved
Comment thread packages/electron/src/index.ts Resolved
Comment thread packages/electron/src/index.ts Resolved
Comment thread packages/perry-react/package.json Resolved
Comment thread packages/perry-react/package.json Resolved
proggeramlug pushed a commit to proggeramlug/perry that referenced this pull request Jul 5, 2026
…nel)

ml-dsa 0.0.4 is a transitive dep of servo-script, part of PerryTS#5811's
EXPERIMENTAL feature-gated Servo web-engine switch (servo-webview,
opt-in — not in the default build graph). Patched only in a pre-release
(>=0.1.0-rc.3) that servo-script does not yet pin; Perry does not
exercise ML-DSA signing. Documented alongside the existing acknowledged
transitive-dep advisories.
@proggeramlug
proggeramlug marked this pull request as draft July 8, 2026 06:03
@TheHypnoo TheHypnoo added the enhancement New capability or improvement label Jul 26, 2026

@jdalton jdalton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of 647d438d42529e9b4780210a4db504d485e92d8c (2026-09-07).

Concrete IPC dispatch bug: IpcMain.handlers is initialized to {}, and _dispatch reads this.handlers[msg.channel] without an own-property check. An unregistered "toString" channel therefore resolves to Object.prototype.toString and is invoked instead of taking the "No handler registered" rejection; "__proto__" also has special behavior on registration. Use Map or a null-prototype dictionary and test these channel names through invoke/handle/removeHandler. Also add a default-WKWebView invoke round trip with success and rejection before calling the shim fully functional; build success cannot establish bridge dispatch. This feedback is limited to the IPC path inspected, not a complete audit of the experimental Servo backend.

Validation scope: source/diff inspection; I have not run this PR's build or test suite locally.

@proggeramlug proggeramlug changed the title feat: Electron-compat shim + experimental Servo web-engine switch feat: Electron-compat shim + perry electron runner (packaged apps run natively) Sep 19, 2026
Ralph Küpper added 10 commits September 21, 2026 15:28
App-run-loop takeover (perry-runtime/ui_loop, appRunLoop/appRequestLoop/appQuit),
WKWebView renderer<->main IPC bridge (onMessage script handler + document-start
user scripts), dynamic multi-window support, and macOS window pinning/activation
fixes for Electron-style flows.
…ain/...)

npm-'electron'-named compat package: main-process API (app, BrowserWindow,
ipcMain, webContents, clipboard, Tray, nativeImage, Menu, dialog, shell) in
native TS on perry/ui + node:*, renderer-side ipcRenderer/contextBridge
preload runtime injected as document-start user script, and a
system-explorer example app.
New 'perry electron <path>' subcommand: accepts a package.json directory, a
packaged macOS .app bundle, or an .asar archive (minimal asar reader with
traversal/bounds checks, per-user extraction cache, unpacked-entry support),
redirects 'electron' imports to the compat shim via the new
--package-alias FROM=TO compile flag, compiles the main process natively
(CJS entries via cjs_wrap), and launches the app binary with the app root as
cwd.
…_modules

CompileArgs gained function_source on main; the runner's literal now sets it.
The asar extraction-cache root was swapped out under cfg(test), so the
production path (the one whose old node_modules/.cache location made an
extracted main.cjs an untrusted npm package named .cache) had no coverage.
Thread the cache root through resolution, test the real default root, and
cover a .cjs asar entry.
…file cap

The earlier rebase was cut from the PR branch's parent of its final commit
(647d438, "address electron and Servo review feedback"), silently dropping
that commit's non-Servo fixes. Restored:

- GC-root the pending app.whenReady() closure slot (js_gc_register_global_root
  + root write barrier) and re-read it before use, so a moving collection
  between appRequestLoop and the loop takeover cannot leave it dangling.
- Stop enabling allowUniversalAccessFromFileURLs on the webview.
- Run the app preload through a one-shot __perryRunPreload runner that is
  deleted before page scripts run; require/electron/ipcRenderer no longer
  leak onto page globals.
- JSON-serialization failures reject the renderer's invoke instead of
  throwing on the main side; loadURL/loadFile settle on did-finish-load /
  did-fail-load.
- perry run: a perry.toml entry that is missing or malformed is an error
  instead of silently falling back to src/main.ts (real TOML parsing).
- perry init: always sync the perry/* tsconfig path and rebase generated
  paths onto an existing baseUrl.
- packages/electron publishes as @perryts/electron; perry-react takes the
  React type packages as peers and ships a no-op index.js.
- system-explorer renderer builds DOM nodes instead of innerHTML.

The Servo backend selection in BrowserWindow stays out.

app.rs had grown to 2110 lines (cap 2000); the Electron shell pieces (the
body-less loop, loop request, quit, full-window pinning) move to
app/electron_shell.rs, re-exported so callers are unchanged.
clap derived --package-aliases from the field name, while the help text, docs
and changelog say --package-alias (matching --define / --embed).
…e into a .app

PERRY_ELECTRON_SHIM pointed at the shim directory (what the error message
asks for) joined packages/electron/src/index.ts onto it and never matched;
it now accepts the shim's entry file, the shim directory, or a checkout root.

A bundle's unpacked Contents/Resources/app/ is the app root, so the compile
wrote node_modules/.cache/perry inside the installed, signed application.
That case now builds under the per-user cache (PERRY_CACHE_DIR still wins).
…oesn't

README/DESIGN describe the perry electron runner (entry resolution, asar
cache and why it sits outside node_modules, the --package-alias compile, the
launch cwd), the @perryts/electron name, preload-only require, and a gap list
matching the shim as it is (macOS only, npm long-tail, dialog/Menu depth,
JSON-only IPC). Extend the changelog fragment to cover the whole PR.
…root slot

codegen/entry.rs was 1997 lines on main and this PR's top-level UI-loop
takeover call pushed it to 2007. Move the two GlobalDeclarationInstantiation
prelude emitters (Script function bindings, Annex B var bindings) to
codegen/entry/global_decls.rs — private to entry.rs, no behaviour change.

The Electron shell's pending-onReady slot is a new rule-F holder in the
gc_runtime_root_holders census. It is genuinely covered: request_app_loop
registers the slot with js_gc_register_global_root before writing it, so
visit_global_root_slots marks and rewrites it. Recorded as a researched
covered_elsewhere verdict naming that scanner.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New capability or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants