Skip to content

fix(electron): offer spelling suggestions on right-click - #144

Merged
PseudoSean merged 1 commit into
evilnet:developfrom
Josephur:spellcheck-fix
Nov 1, 2026
Merged

PseudoSean merged 1 commit into
evilnet:developfrom
Josephur:spellcheck-fix

Conversation

@Josephur

Copy link
Copy Markdown
Contributor

Problem

In the Electron shell, Chromium underlines misspelled words in the composer
(spellcheck: true), but right-clicking one offered no way to fix it:
Electron shows no context menu at all unless the app builds one, and the
shell never did.

Fix

  • New shells/electron/context-menu.js: builds the right-click menu for
    editable fields from the context-menu event — the dictionary's
    suggestions (clicking one calls replaceMisspelling), "Add to
    Dictionary", and undo/redo/cut/copy/paste/select-all, each gated on the
    field's editFlags. A read-only selection gets Copy alone; everywhere
    else no native menu opens, so the SPA's own context menus (messages,
    channels, users) keep owning the right-click there.
  • main.js installs it per window; the packaged file list includes the
    new module; the shell README documents the behaviour.

Kept strictly to the Electron shell — no client/SPA changes.

Tests

  • test/tests/electronContextMenu.ts (7 tests): suggestions and their
    wiring to replaceMisspelling / addWordToSpellCheckerDictionary,
    the no-suggestion case, edit-action gating, read-only Copy, and staying
    out of the way of the page's own menus. ESLint + Prettier clean.
  • Verified in a real Electron window (typed input, not scripted values):
    teh/tset underlined, menu returned the, eh, tech, tee, tea, and
    clicking the corrected the field. The shell's --smoke run passes.

@Josephur

Copy link
Copy Markdown
Contributor Author

Screenshot of the context menu it generates for reference.
image

@Josephur

Copy link
Copy Markdown
Contributor Author

Tell me to re-adapt this if you choose to switch from Electron to Tauri (a move I really like) and I can re-write it.

@PseudoSean
PseudoSean merged commit c09d8b7 into evilnet:develop Nov 1, 2026
5 checks passed
@PseudoSean

Copy link
Copy Markdown
Collaborator

Security review: approve

Reviewed 8bf09a89 (on develop d107c577), tested in a detached worktree. I found no security issues. It merges cleanly onto current develop (947a68c1), and nothing under shells/electron has changed there since the PR's base.

What the change can reach

The new handler runs in the main process and takes the context-menu params, which Chromium fills in from the renderer. The renderer shows untrusted IRC content. It uses five params, each in a harmless way:

Param Use Worst case if forged (compromised renderer)
isEditable, selectionText which items appear a Copy item appears or doesn't
editFlags enabled on the edit roles an enabled role does nothing the field doesn't allow
dictionarySuggestions menu labels (plain text) and the string passed to webContents.replaceMisspelling() arbitrary label text; the replacement is an edit in the page itself
misspelledWord session.addWordToSpellCheckerDictionary() after the user clicks, one odd word in the custom dictionary

None of these runs code, opens URLs, navigates, touches files outside Chromium's own dictionary store, or adds IPC. A renderer that could forge these params would already have more power than this menu gives it. Page script cannot forge them: a synthetic contextmenu event raises no native menu (verified below).

What it leaves out matters for security too: no Open/Copy Link, no Save Image, no Inspect Element. So no new shell.openExternal, navigation or devtools path. The window keeps contextIsolation, sandbox, nodeIntegration: false and its will-navigate/setWindowOpenHandler guards untouched. No dependency changes. The files entry in package.json is needed: without it a packaged build would throw on require("./context-menu").

Hardening note for later, not this PR: if a link item is ever added, route it through openExternalIfSafe(), not shell.openExternal(params.linkURL).

Tests

  • PR's own tests: 7/7. ESLint and Prettier clean.
  • Full mocha on the PR head (production build first): 3551 passing, 15 pending, 0 failing.
  • electron . --smoke (Electron 44.0.0 from the shell's lockfile, Xvfb): smoke: OK. The app loads app://seance/, the service worker registers, no renderer errors.
  • Real window, real input events (sendInputEvent keys and right-clicks; I wrapped Menu.buildFromTemplate to record each template and stub popup): 11/11
    • teh typed in a textarea → misspelledWord: "teh", suggestions the, eh, tech, tee, tea. The menu is suggestions, Add to Dictionary, then the edit roles. Clicking the corrected the field.
    • Read-only text with no selection: no native menu. A word selected by double-click: Copy alone.
    • An element whose page listener calls preventDefault() on contextmenu: Electron's event never fires and the page's handler runs. So the SPA's menus keep the right-click, as the PR says.
    • A link: no native menu.
    • A password field: no misspelled word, no suggestions. With its text selected, Cut and Copy come back disabled (selectionText: "••••").
    • A synthetic contextmenu dispatched from page script: no native menu.
  • The real composer: connected to a local nefarious2 and joined #seance, typed teh into #input, right-clicked → the same suggestions; clicking the made the input the . (The connect form's text fields all set spellcheck="false", so they get the edit roles only, which is correct. Nothing in client/js registers a global contextmenu listener that could swallow the event.)
  • Limits: Linux only, in Xvfb. The native popup was stubbed (it can grab the pointer in a virtual display), so I checked the menu's contents, not how it's drawn. A suggestion was chosen by calling its item's click handler, which then went through the real replaceMisspelling. macOS, Windows and a packaged build (AppImage/deb) were not run.

Nits (not security, take or leave)

  • "No suggestions" and "Add to Dictionary" are English literals. That matches the shell's existing app menu ("File", "Edit", …), so it's consistent; localizing the shell is a separate job.
  • test/tests/electronContextMenu.ts imports with @ts-ignore; a one-line context-menu.d.ts beside the module would type it.

This branch was successfully deployed

1 active deployment
github-pages — 8bf09a89 Deployed Nov 1, 2026 by PseudoSean via deploy #393
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants