Skip to content

Open links in the browser for every click gesture, not just a plain click - #358

Open
amitraj2203 wants to merge 4 commits into
WordPress:trunkfrom
amitraj2203:fix/284-links-open-in-browser
Open

Open links in the browser for every click gesture, not just a plain click#358
amitraj2203 wants to merge 4 commits into
WordPress:trunkfrom
amitraj2203:fix/284-links-open-in-browser

Conversation

@amitraj2203

@amitraj2203 amitraj2203 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Why

Middle-clicking the site link, wp-admin or Adminer opens the site inside an app window with no address bar and no back button. Only closing it gets you out. See #284.

Each link cancels its own navigation in onClick. A middle click fires auxclick, which a click handler never sees, so Chromium's "open in a new window" default runs.

What changes

src/window-links.js denies window creation and http/https navigation on the app window, and sends the address to the system browser instead. One place, so every link is covered including new ones.

file: navigation is left alone on purpose. The app's own page is a file: URL and blocking it stops location.reload().

The links keep their onClick handlers. They're redundant now, but removing seven of them is a separate change.

How to test this

Platforms: any. Needs a mouse with a clickable scroll wheel; macOS trackpads have no middle button.

Starting state: a site that has finished setting up.

  1. Start the app, select the site, click Start dev server.
  2. Wait for the site URL, wp-admin and DB inspect (Adminer) links. The app opens the site in your browser on its own here, so close that tab.
  3. Middle-click the site URL. It opens in your browser.
  4. Middle-click wp-admin, then DB inspect (Adminer). Both open in the browser. Adminer's address should still have its full query string, or it lands on a broken page.
  5. Plain-click and Cmd/Ctrl-click all three. Same as before, they open in the browser.

What must not have happened:

  • No window of the app shows the WordPress site. That's the bug.
  • The app window still shows the site page, not WordPress.
  • No link does nothing at all.

For the old behaviour, check out trunk and repeat steps 1-3.

Risks and limitations

Cmd/Ctrl+click is not affected by this bug, though #284 says it is. Chromium sends it as a normal click, so the existing preventDefault() catches it. I tested all four gestures on trunk: only middle click reproduces. Worth knowing before testing, or the gesture the issue names looks like it disproves the fix.

The review found three separate bugs sitting next to this code. They're listed below and left alone.

Related

Fixes #284


Review outcome (required — see AGENTS.md)

Lint clean. npm test and npm run test:electron both 1034 pass.

Fixed: an empty catch in the new module swallowed a failed openExternal.

Not fixed, all pre-existing and none caused by this change:

  • The app window can be navigated off its own page by a file: or page-relative link. One route is the email body at index.jsx:5551, rendered with dangerouslySetInnerHTML. Trunk has no guard on this window at all. If you fix it, note that blocking every navigation breaks location.reload(), so the exemption has to be the current URL.
  • openExternalUrl has no way to report a failed open, and lets the rejection propagate into a renderer that catches nothing at 23 call sites. So a click on an address the OS can't open is a link that did nothing, with nothing in the log.
  • The wire isn't tested. Delete the openLinksExternally call and the suite stays green. test/ipc-wiring.test.cjs can't reach createWindow: app.whenReady() is stubbed as a promise that never settles and main.js has no exports.

I had fixed the first two, then reverted them as out of scope, which is why there's a revert in the history. The code matches the first commit.

Screenshots

Nothing new appears on screen; the fix is a window that stops appearing. I have a screenshot of the bug on trunk if it's useful.

The site links each cancel their own navigation in an onClick handler. A
middle click fires auxclick, which a click handler never sees, so Chromium's
"open in a new window" default ran and the site loaded inside a bare app
window with no address bar and no way back.

Refuse navigation and window creation once for the app window instead, and
hand the address to the system browser. Every link is covered, including ones
added later, and the address goes out through the same external-url.js gate
the renderer already uses.

Fixes WordPress#284
Two findings from the review pass on this branch.

The window guard only intercepted http and https, so anything else was left
to navigate the main window. That window is the one with the preload bridge
attached, and it renders content the app does not author — captured email
bodies among it — where a link can point at a local path or at one relative
to the app's own file: origin. It now refuses every navigation except a
reload, which is the one case that has to keep working; a plain deny breaks
reloading the window.

The onFailed reporter also only covered the link path. openExternalUrl
ignored it and let a rejecting openExternal propagate out of the url:open
handler, where none of the renderer's 23 call sites catches anything — so an
ordinary click on an address the OS cannot open was still a link that did
nothing with nothing in the log. Reporting moved into openExternalUrl, which
is where both paths meet.

Tests for both fail on the previous code.
Both were out of scope for WordPress#284. The navigation hole they closed — the app
window following a file: or relative link out of its own page — is on trunk
already and is not something this fix introduced, and the missing failure log
on the url:open path is a gap in external-url.js rather than in the link
handling. Neither belongs in a PR that fixes one reported bug.

Both are worth doing on their own; noted in the PR description so they are not
lost.
Trunk moved every test into `tests/unit/` in WordPress#377, and this branch predates
that, so the merge raises a `CONFLICT (file location)` for the new
`test/window-links.test.cjs`.

Git relocates the file on its own; it does not rewrite what is inside it.
Accepting the move alone leaves `require('../src/window-links.js')` pointing
one directory too high, and the test throws `MODULE_NOT_FOUND` the first time
it runs.
This resolution accepts the relocation and takes the require to
`../../src/window-links.js`, the depth every other file in `tests/unit/`
already uses.

---

Generated with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mokagio

mokagio commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

@amitraj2203 thank you for this PR. You'll notice I just merged trunk into your branch. It's to sync with a few structural and security changes that landed on trunk since you opened this. Cheers.

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.

Links can open inside the app instead of the browser

2 participants