fix: guard against null popup window in PhraseMaker export#7721
fix: guard against null popup window in PhraseMaker export#7721rish106-hub wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #7721 +/- ##
==========================================
+ Coverage 54.60% 54.66% +0.06%
==========================================
Files 172 172
Lines 57270 57271 +1
==========================================
+ Hits 31272 31309 +37
+ Misses 25998 25962 -36 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
A guard makes sense and also it is good to have a user-facing error message.
|
|
Thanks for the review @walterbender sir !
Want me to file/reference that specifically, or fold a guard for this callsite into that issue's scope? Either way it's separate from this PR, which only handles the crash once the button is clicked and the popup gets blocked.
|
|
I misspoke when I said "ad blocker". I meant "pop-up blocker". But the point is the same -- it is a likely cause of the failure, but not the exclusive cause of the failure, so some rewording is needed. Re #7074 that is a good point. We should prob. fix that sooner than later. There is an effort to migrate to indexDB, so maybe #7074 can be handled in that context: #7678 |
|
Reworded @walterbender sir . it no longer states pop-up blocking as the definite cause, just flags it as a likely one and points the user to check settings. Pushed. Will sit ready for whenever the freeze lifts. |
Description
PhraseMaker._export()(js/widgets/phrasemaker.js) opens a new browser window/tab and renders the current Music Matrix into it as a formatted, downloadable HTML table. It does this with:window.open()returnsnullwhenever the browser's popup blocker prevents the window from opening — this is standard, spec-defined behavior, not an edge case. When that happens,exportWindow.documentthrows immediately:There was already an attempt at a guard for this:
but it's dead code: it checks the wrong variable (
exportDocument, which is a derived property of the thing that's actuallynull) against the wrong falsy value (undefinedinstead ofnull), and the check runs one statement after the line that already threw. In practice the guard could never fire before the crash happened.Why it matters: any user with popup blocking enabled (the browser default in most cases, and standard in locked-down school/classroom Chromebook profiles that Music Blocks is commonly deployed on) hits an uncaught exception the moment they try to export their Music Matrix, with no explanation and no way to recover short of reloading.
Related Issue
This PR fixes #7082
PR Category
Changes Made
js/widgets/phrasemaker.js—_export():window.open(""), before any property is read off the resultif (!exportWindow)) instead of a property already derived from itexportDocument === undefinedcheck — it's unreachable/redundant onceexportWindowis confirmed truthy (a realWindowobject always has a.document)this.activity.errorMsg(...)when the popup is blocked, so the user gets told why the export didn't happen instead of it failing silently (previously it only logged toconsole.debug, invisible to anyone but a developer with devtools open)this._(...)for i18n, matching every other user-facing string already in this widget (this._("Play"),this._("Export"), etc. — see line 84 wherethis._is bound from the injecteddeps._)this.activity.errorMsgis the established notification pattern already used elsewhere in this same file (passed as a callback at lines 773/785) and across other widgets (reflection.js,legobricks.js) — no new utility introduced, just reusing what's already thereBefore:
After:
Nothing else in the 188-line method was touched — the table/row/cell generation and download-link logic downstream of the guard is unrelated to this bug and works correctly once the guard fires (or doesn't) at the right time.
js/widgets/__tests__/phrasemaker.test.js— newdescribe("_export", ...)block (this method had no prior test coverage):window.opento returnnull, asserts_export()does not throw and thatactivity.errorMsgis called exactly once with a message mentioning the export windowwindow.opento return a mock window/document, asserts_export()runs to completion (including building the table and closing the document) without ever callingerrorMsgReproduction (before the fix)
window.opencalls not tied to a direct user gesture, and standard on locked-down classroom Chromebook profiles)TypeErrorin the console, export silently fails, no feedback to the userAfter the fix, step 4 instead shows a visible warning message telling the user to allow pop-ups, and the app continues to function normally.
Testing Performed
npx jest js/widgets/__tests__/phrasemaker.test.js— 71/71 pass (69 pre-existing + 2 new)npx jest(full repo suite) — 6077/6077 pass, zero regressionsnpx eslint js/widgets/phrasemaker.js js/widgets/__tests__/phrasemaker.test.js— clean, zero warnings/errorsChecklist
pnpm run lintandpnpm exec prettier --check .with no errors.Additional Notes for Reviewers
Kept this surgical and single-topic per the contribution guidelines — only the guard clause and the new warning call changed, no drive-by refactoring of the rest of
_export()or adjacent methods.window.open()isn't used anywhere else in the codebase, so there was no existing null-check convention to match; theerrorMsg/this._()pattern chosen here is the one already used consistently throughout this file and the wider widget codebase.