fix(web): add alert when pdf export popup is blocked (#664)#676
fix(web): add alert when pdf export popup is blocked (#664)#676PratikRai0101 wants to merge 2 commits intonexu-io:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a17485e1a2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Hi @PratikRai0101! 🎉 Thanks for the contribution — the alert() approach gives immediate feedback and the early return prevents resource waste. I will run a deep review and get back to you within 24h. Thanks for making open-design better! |
lefarcen
left a comment
There was a problem hiding this comment.
Code review
What's working
The fix correctly detects popup blocking and prevents silent failure. The early return stops execution cleanly when window.open returns null, which is exactly what issue #664 called for.
Minor suggestions (non-blocking)
See inline comments below. These are opportunities to align with modern UX patterns and strengthen the fix, but the current implementation already resolves the reported bug.
Overall: the core fix is sound. The suggestions below would polish the user experience and test coverage.
b28b98d to
4ac52ac
Compare
|
hey @lefarcen I have updated the code to explicitly trigger URL.revokeObjectURL(url) immediately before we return early from the block. The fix is pushed. |
|
Perfect! The memory leak is now addressed — runs before the early return. I also see you added the The P3 concern is resolved. The core fix is solid and ready from my side. Deferring final approval to a maintainer. |
|
Good news @PratikRai0101! The TS1117 duplicate object key error blocking your CI has been fixed in PR #680 (just merged). The duplicate Ukrainian translation keys in If CI is still running, you might need to rebase/merge main into your branch to pick up the fix. |
|
✅ Blocker resolved: PR #680 (merged) fixed the TS1117 duplicate Ukrainian keys in uk.ts. Your CI should pass once it re-runs. If still running, try rebasing main. |
|
Thanks for the fix. One issue remains before merge: the new alert branch is currently unreachable for existing PDF exports. All current call sites use Please update |
|
Good catch, @alchemistklk! You're absolutely right — the current condition @PratikRai0101, the fix needs to handle the blocked-popup case regardless of if (!win) {
if (typeof alert !== 'undefined') {
alert('Popup blocked! Please allow popups for this site to export as PDF.');
}
URL.revokeObjectURL(url);
return;
}This way, the user gets feedback whether the export is sandboxed or not. The |
mrcfps
left a comment
There was a problem hiding this comment.
@PratikRai0101 thanks for iterating on the PDF export popup handling. I found one remaining blocker: the new blocked-popup alert is still bypassed by the default sandboxed export path used by the current UI, so the primary Share > Export as PDF flow can still fail silently.
Generated by Looper 0.6.1 · runner=reviewer · agent=opencode4ac52ac to
f30e279
Compare
|
@mrcfps @lefarcen @alchemistklk The PR has been updated with the empty-tab synchronous strategy!. Here is how it behaves now:
I also successfully rebased against the latest |
|
Perfect! The synchronous empty-tab strategy directly addresses the blocker — now reliably detects popup blocking regardless of the default, and you manually preserve the sandboxing with The P1 correctness issue is resolved. The approach is clean and handles the edge case @mrcfps flagged. One minor note (non-blocking): the blob URL is no longer revoked after use. This is a P3 memory hygiene nit at most — the OS will GC blob URLs when the page unloads, so it won't cause a practical leak. If you want to tidy it up, you could add a Core fix is solid now. Waiting for CI to confirm the rebase didn't introduce any regressions. |
lefarcen
left a comment
There was a problem hiding this comment.
Re-reviewed the new head (f30e279) — the P1 blocker is resolved.
What's fixed
The synchronous empty-tab strategy directly addresses the correctness issue @alchemistklk and @mrcfps identified:
- Opens empty tab first:
window.open('', '_blank')withoutnoopener→ can reliably detect ifwin === null(blocked popup) - Alert on block: If blocked → revoke blob URL → alert user → return early
- Manual sandboxing: If successful →
win.opener = null(preserves security) → navigate to blob URL
This works regardless of the sandboxedPreview default. The fix now covers the default Share > Export as PDF flow.
Minor note (non-blocking)
The blob URL is no longer revoked after use (the setTimeout(() => URL.revokeObjectURL(url), 60_000) is gone). This is a P3 memory hygiene nit at most — the OS will GC blob URLs when the page unloads, so it won't cause a practical leak. If you want to tidy it up later, you could add win.addEventListener('beforeunload', () => URL.revokeObjectURL(url)) or leave it as-is.
Verdict
Core fix is solid. The P1 correctness blocker is resolved. Waiting for CI to confirm the rebase didn't introduce regressions.
mrcfps
left a comment
There was a problem hiding this comment.
@PratikRai0101 thanks for the update — the empty-tab strategy resolves the previous popup-blocker detection issue for the main PDF export flow. I found one blocking follow-up around the existing web test coverage that needs to be updated with the new window.open contract before this can merge. 👍
| // Open an empty tab synchronously (without noopener) to reliably detect popup blocking. | ||
| // Since window.open with 'noopener' returns null on success by specification, | ||
| // this approach allows us to distinguish between a successful export and a blocked popup. | ||
| const win = window.open('', '_blank'); |
There was a problem hiding this comment.
This runtime change now opens a blank tab with window.open('', '_blank') and then navigates the returned handle, which is a reasonable fix for distinguishing a real popup block from noopener returning null. However, the existing web tests still encode the old contract: apps/web/tests/runtime/exports.test.ts stubs window.open to capture the third features argument and the default sandboxed PDF tests assert openedFeatures === 'noopener,noreferrer'. Because this PR only changes exports.ts, those assertions no longer match the changed line here and the test suite will fail when dependencies are installed and the web runtime tests run.
Please update the PDF export tests alongside this implementation: have the window.open stub return a small WindowProxy-like object with opener and location.href, assert the successful sandboxed path clears opener and navigates to the blob URL, and add/adjust the blocked-popup case where open returns null so it verifies alert and immediate URL.revokeObjectURL. That keeps CI aligned with the new behavior while preserving coverage for the bug this PR fixes.
f30e279 to
2d68a1d
Compare
|
hey @mrcfps rightly said the strict assertions on the old I've refactored
All 310 tests are green locally. Thanks for the help! |
2d68a1d to
1532027
Compare
mrcfps
left a comment
There was a problem hiding this comment.
@PratikRai0101 thanks for continuing to iterate on the popup-blocked PDF export flow — the main detection path and the updated tests now line up nicely. I found one small resource-lifecycle follow-up that should be safe to handle without blocking the primary fix.
Non-blocking follow-up in apps/web/src/runtime/exports.ts, exportAsPdf, around win.location.href = url: the successful PDF-export path now navigates the opened tab to the Blob URL, but the delayed URL.revokeObjectURL(url) that used to run after a successful open was removed in this same hunk. The blocked-popup branch correctly revokes on early exit, yet every successful export now leaves its Blob URL registered until the main app page unloads, which can accumulate memory in a long-running session when users export multiple large artifacts. Please restore a success-path cleanup after navigation, e.g. schedule setTimeout(() => URL.revokeObjectURL(url), 60_000) after win.location.href = url as the previous implementation did.
1532027 to
6f269f7
Compare
Summary
This PR resolves issue #664, where clicking Share > Export as PDF fails silently with no user feedback if the browser's aggressive popup blocker halts the
window.opencall.In the previous implementation, when a browser blocked the export window, the app would proceed without notifying the user, leaving the interface completely unresponsive to their action. This fix introduces robust error handling that immediately detects a blocked popup, alerts the user with actionable feedback, and cleanly halts execution.
Root Cause Analysis
The PDF export workflow in
apps/web/src/runtime/exports.tsutilizes the following sequence:blob:http...).window.open(url, '_blank', ...)to open the printable preview in a new tab.Under default security configurations in modern browsers (Chrome, Safari, Firefox, Brave), synchronous or unprompted
window.opencalls are flagged as popups and blocked. When blocked:window.openreturnsnull(orundefinedin some legacy engines).if (!win) { /* empty block */ }.Detailed Changes
apps/web/src/runtime/exports.ts
if (!win)block with an explicit check immediately following thewindow.opencall.returnstatement to stop any subsequent operations whenwinis null, preventing unnecessary resource overhead and potential orphan processes.