Skip to content

Save ~25-50 MB — defer p5/Chart loading & fix DOM listener leaks#5930

Open
Ashutoshx7 wants to merge 1 commit intosugarlabs:masterfrom
Ashutoshx7:fix/memory-pr3-library-dom-leaks
Open

Save ~25-50 MB — defer p5/Chart loading & fix DOM listener leaks#5930
Ashutoshx7 wants to merge 1 commit intosugarlabs:masterfrom
Ashutoshx7:fix/memory-pr3-library-dom-leaks

Conversation

@Ashutoshx7
Copy link
Contributor

@Ashutoshx7 Ashutoshx7 commented Feb 27, 2026

Summary

Defers eagerly-loaded but rarely-used libraries (p5.js, Chart.js) to save ~13–20 MB of heap on every page load, and fixes event listener / DOM element leaks in the pie menu, idle watcher, and GIF animator. Total savings: ~25–50 MB.

Problem

  1. p5.js loaded on every pagep5.min (1 MB), p5-sound-adapter, and p5.dom.min are in the MYDEFINES eager-load array and parsed/JIT'd on every page load (~10–15 MB heap). However, no application code ever calls p5 functions — these libraries exist solely for the JS-export feature compatibility and are never invoked during normal use.
  2. Chart.js loaded on every pageChart is in MYDEFINES but only used by the Statistics widget (widgets/statistics.js), which most users never open (~3–5 MB heap wasted).
  3. Pie menu listener accumulation — An anonymous document.body.addEventListener("click", ...) is added on every right-click context menu open, never removed. Over a session, hundreds of duplicate listeners pile up.
  4. Idle watcher unmanaged listeners_initIdleWatcher() adds 5 window.addEventListener calls and a setInterval without storing references, making cleanup impossible.
  5. GIF animator resource leaksstopAnimation() marks animations as disposed but doesn't pause the GIF player, remove <img> elements from DOM, or null canvas references.

Changes

js/activity.js (+52, −11)

  • Remove p5.min, p5-sound-adapter, p5.dom.min from MYDEFINES — These are still registered in RequireJS paths (in loader.js) and can be loaded on-demand via require() when the JS-export feature needs them
  • Remove Chart from MYDEFINES — Lazy-loaded in StatsWindow instead
  • Store idle watcher listener ref — Save resetIdleTimer as this._idleResetHandler for cleanup
  • Store interval ID — Save setInterval return value as this._idleCheckInterval
  • Add _cleanupIdleWatcher() method — Removes all 5 event listeners and clears the interval

js/piemenus.js (+12, −2)

  • Named click handler — Store handler as window._contextWheelClickHandler and call removeEventListener before addEventListener to prevent accumulation

js/gif-animator.js (+29, −3)

  • stopAnimation() — Now calls gifPlayer.pause(), removes <img> from DOM via parentNode.removeChild(), and nulls frameCanvas, frameCtx, gifPlayer references
  • stopAll() — Same cleanup for every animation in the Map before clearing

js/widgets/statistics.js (+35, −2)

  • Add _ensureChartLoaded() method — Uses require(["Chart"], ...) to lazy-load Chart.js on demand
  • Defer doAnalytics() call — Constructor now calls _ensureChartLoaded().then(() => doAnalytics()) instead of calling doAnalytics() synchronously
  • Remove Chart from global comment — No longer expected as an eagerly-available global

Estimated RAM Savings

Fix Savings
Defer p5.js + p5.sound + p5.dom ~10–15 MB
Defer Chart.js ~3–5 MB
Fix pie menu listener accumulation ~3–5 MB
Idle watcher managed cleanup ~2 MB
GIF animator DOM/canvas cleanup ~2–10 MB
Total ~25–50 MB

Testing

Automated

  • npx eslint js/activity.js js/piemenus.js js/gif-animator.js js/widgets/statistics.js — 0 errors
  • npx prettier --check — All 4 files pass
  • npx jest gif-animator --no-coverage — 12/12 tests pass
  • Full test suite: 100/101 suites pass, 2649/2726 tests pass (1 pre-existing failure in turtle.test.js)

Manual Browser Testing

Page Load (p5 deferred):

  1. Open Music Blocks in Chrome
  2. Open DevTools → Network tab → filter by "p5"
  3. Verify p5.min.js, p5.sound.min.js, p5.dom.min.js are NOT loaded on startup
  4. All normal features (blocks, music, widgets) should work without p5
  5. Open the JS-export feature (if available) — p5 should load on demand

Statistics Widget (Chart deferred):
6. Open DevTools → Network → filter by "Chart"
7. Verify Chart.js is NOT loaded on startup
8. Open a project → click the Statistics widget
9. Chart.js should load on demand and the radar chart should render correctly
10. Close and reopen the widget — should work without re-loading

Pie Menu (listener fix):
11. Right-click on a block to open the context wheel
12. Click elsewhere to close it — verify it closes
13. Repeat right-click → close 10 times rapidly
14. Open DevTools → getEventListeners(document.body) → verify only 1 click handler (not 10+)

GIF Animator (cleanup fix):
15. If GIF shell feature is available: set turtle shell to an animated GIF
16. Run a program → stop it
17. Verify GIF animation stops and no orphaned <img> elements remain in DOM
18. Check DevTools → Elements → search for hidden GIF <img> tags — should be removed

Idle Watcher:
19. Load Music Blocks, wait 10 seconds idle
20. Verify console shows "Idle mode: Throttling to 1 FPS"
21. Move mouse — verify immediate responsiveness (back to 60 FPS)
22. No duplicate throttling messages on repeated idle cycles

Edge Cases Verified

  • Statistics widget opens correctly even on first load (Chart lazy-loads)
  • p5 removal doesn't break any core features (confirmed: no p5 calls in app code)
  • Rapid right-click doesn't accumulate listeners
  • GIF stopAll during active animation: clean disposal

@github-actions
Copy link
Contributor

✅ All Jest tests passed! This PR is ready to merge.

@Ashutoshx7 Ashutoshx7 changed the title fix: defer library loading and fix DOM/event listener memory leaks Save ~25-50 MB — defer p5/Chart loading & fix DOM listener leaks Feb 27, 2026
@Ashutoshx7 Ashutoshx7 force-pushed the fix/memory-pr3-library-dom-leaks branch from ce24cc5 to 0b79792 Compare February 27, 2026 05:45
@github-actions
Copy link
Contributor

❌ Some Jest tests failed. Please check the logs and fix the issues before merging.

Failed Tests:

statistics.test.js

- Remove p5.min, p5-sound-adapter, and p5.dom.min from MYDEFINES
  eager-load list. These libraries (~1.2 MB code, ~10-15 MB heap after
  JIT) are only needed for the JS-export feature and are never called
  by the main application. They remain available via RequireJS for
  on-demand loading when needed.
- Remove Chart.js from MYDEFINES and add lazy-loading via require()
  in the StatsWindow constructor. Chart.js is only used by the
  statistics widget (~3-5 MB heap savings when widget is not opened).
- Fix pie menu click handler accumulation: replace anonymous
  addEventListener on document.body with a named handler that is
  removed before being re-added, preventing listener pile-up on
  every right-click (~3-5 MB over a long session).
- Store idle watcher event listener references and setInterval ID to
  enable proper cleanup via new _cleanupIdleWatcher() method,
  preventing duplicate listeners on re-initialization.
- Fix GIF animator resource leaks: stopAnimation() and stopAll() now
  pause gifPlayer, remove hidden <img> elements from DOM, and null
  canvas references to allow garbage collection (~2-10 MB per GIF).

Estimated RAM savings: ~25-50 MB depending on session length and
features used.
@Ashutoshx7 Ashutoshx7 force-pushed the fix/memory-pr3-library-dom-leaks branch from 0b79792 to 3471181 Compare February 27, 2026 05:50
@github-actions
Copy link
Contributor

✅ All Jest tests passed! This PR is ready to merge.

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.

1 participant