Playground: exit loops which block the page instead of freezing the tab when running code - #3617
Closed
dinesh3018 wants to merge 1 commit into
Closed
Playground: exit loops which block the page instead of freezing the tab when running code#3617dinesh3018 wants to merge 1 commit into
dinesh3018 wants to merge 1 commit into
Conversation
Running code containing an infinite loop (e.g. `while (true) {}`) freezes
the browser tab, because the Run button evals the emitted JS directly on
the main thread with no way to stop it (microsoft#2541).
This adds loop protection in the same style as JSBin, CodePen and the
Babel REPL: before eval, a guard is inserted at the top of every
for/while/do loop body using the TypeScript AST. If user code blocks the
page for more than a second, every guarded loop exits and the Logs pane
shows which line looked infinite. Await-loops, timers and ordinary
sub-second loops are never affected, no newlines are added so error line
numbers still match, and a "Disable Loop Protection" setting turns it
off for intentionally long-running code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
|
Closing — this was opened from the wrong account by mistake. Apologies for the noise. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Open https://www.typescriptlang.org/play, type:
and press Run. The tab freezes permanently — the only way out is killing the tab, losing the code and any unsaved state. Same story for
for(;;), accidentali--instead ofi++, and every other runaway loop a learner writes. (Filing this as a PR rather than an issue per the issue-template guidance that non-critical reports should come as PRs; the underlying bug context is below.)The cause is that the Run button evals the emitted JS directly on the page's main thread (
packages/playground/src/sidebar/runtime.ts) — thetry/catchthere can handle a thrown error, but nothing can interrupt non-termination, which is what was concluded in #2541 ("Add ability to stop a running script", closed as not feasible without ShadowRealms). Related Run-UX discussion: #3155.The fix
The same approach JSBin (
loop-protect), CodePen, freeCodeCamp and the Babel REPL (babel/website#352) converged on for main-thread-eval playgrounds — instrument loops before eval, since a Worker/iframe runner would break DOM access and the live console capture:insertLoopProtection(inpackages/playground/src/sidebar/loopProtection.ts) parses the emitted JS with the already-loaded TypeScript AST and insertsif (__tsPlaygroundLoopProtection.hit(id, line)) break;at the top of everyfor/while/doloop body. Unbraced bodies are wrapped in a block; nothing is ever inserted before a loop, soif (x) while (y) z(),else-bodies and labelled loops stay valid. No newlines are added, so runtime error positions still match the JS the user can see.await-loops,setIntervalcallbacks, or ordinary sub-second loops.while (true) let x = 1;— a runtime SyntaxError which wrapping would legalize) are left byte-for-byte untouched so they behave exactly as today.en/playground.tsand entries in the playground handbook ("Settings Panel" and "Running Code").setIntervals from earlier runs (Playground: Add ability to stop a running script #2541's other half) are intentionally out of scope.Testing
pnpm --filter=@typescript/playground test— 18 tests, including: nested/unbraced/labelled loops,for-ofover an endless generator,try/finallybodies, loops in single-statement position, a finite loop in asetTimeoutcallback queued behind an over-budget script (must not be broken), an over-budgetasyncloop that yields (must not be broken), and line-number/SyntaxError-preservation checks. The tests wrap the guard in a hard deadline that throws, so a protection regression fails the suite instead of hanging jest/CI.pnpm --filter=@typescript/playground build(tsc) is clean, the fullpnpm bootstrapbuild passes, and new/changed files pass the repo prettier config.🤖 Generated with Claude Code