fix(ui): prevent console error when leaving reader#956
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
📝 WalkthroughWalkthroughReplaced explicit Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/src/assets/foliate/paginator.js (2)
1120-1126:⚠️ Potential issue | 🟡 MinorGuard unload path when
open()hasn’t initialized sections yet.At Line 1124,
this.sections[this.#index]?.unload?.()throws ifthis.sectionsis undefined (e.g., element disconnected beforeopen(book)).✅ Minimal safe fix
- this.sections[this.#index]?.unload?.() + this.sections?.[this.#index]?.unload?.()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/assets/foliate/paginator.js` around lines 1120 - 1126, disconnectedCallback currently accesses this.sections[this.#index]?.unload?.() which throws if this.sections is undefined (e.g., element disconnected before open(book) ran); update disconnectedCallback (method name) to guard the path by checking this.sections is truthy (and that this.#index is a valid index) before calling unload on the chosen section, e.g., only call section.unload if this.sections exists and the indexed section is defined; ensure you still disconnect this.#observer, call this.#view?.disconnectedCallback(), set this.#view = null, and remove the media query listener as before.
452-626:⚠️ Potential issue | 🟠 MajorPrevent duplicate handlers on reconnect in
connectedCallback().
connectedCallback()can run more than once per instance (Web Components lifecycle), and lines 567–574 add new bound listeners each time since.bind(this)creates a fresh function reference.disconnectedCallback()(lines 1120–1126) only removes themediaQuerylistener but leaves the host listeners intact. Reattaching the element adds duplicatetouchstart,touchmove, andtouchendhandlers, causing navigation/selection to fire multiple times.The same applies to the
'load'and'relocate'listeners (lines 583–603) and the container scroll listeners (lines 567–572).Fix by storing stable handler references as fields and guarding setup with an initialization flag:
♻️ Suggested fix
export class Paginator extends HTMLElement { + `#initialized` = false + `#onHostTouchStart` = e => this.#onTouchStart(e) + `#onHostTouchMove` = e => this.#onTouchMove(e) + `#onHostTouchEnd` = e => this.#onTouchEnd(e) + `#onMediaQueryChange` = () => { + if (!this.#view) return + this.#background.style.background = getBackground(this.#view.document) + } + connectedCallback() { + if (this.#initialized) { + this.#observer.observe(this.#container) + this.#mediaQuery.addEventListener('change', this.#onMediaQueryChange) + return + } + this.#initialized = true // ... existing setup ... - this.addEventListener('touchstart', this.#onTouchStart.bind(this), opts) - this.addEventListener('touchmove', this.#onTouchMove.bind(this), opts) - this.addEventListener('touchend', this.#onTouchEnd.bind(this)) + this.addEventListener('touchstart', this.#onHostTouchStart, opts) + this.addEventListener('touchmove', this.#onHostTouchMove, opts) + this.addEventListener('touchend', this.#onHostTouchEnd) // ... other listeners ... - this.#mediaQueryListener = () => { - if (!this.#view) return - this.#background.style.background = getBackground(this.#view.document) - } - this.#mediaQuery.addEventListener('change', this.#mediaQueryListener) + this.#mediaQuery.addEventListener('change', this.#onMediaQueryChange) } disconnectedCallback() { this.#observer.disconnect() + this.removeEventListener('touchstart', this.#onHostTouchStart) + this.removeEventListener('touchmove', this.#onHostTouchMove) + this.removeEventListener('touchend', this.#onHostTouchEnd) + this.removeEventListener('load', this.#onLoad) + this.removeEventListener('relocate', this.#onRelocate) + this.#container.removeEventListener('scroll', this.#onContainerScroll) + this.#container.removeEventListener('scroll', this.#onContainerScrollDebounced) + this.#mediaQuery.removeEventListener('change', this.#onMediaQueryChange) // ... existing cleanup ... } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/assets/foliate/paginator.js` around lines 452 - 626, connectedCallback currently binds and adds new anonymous listeners each time (touchstart/touchmove/touchend, container scroll handlers, 'load' and 'relocate' handlers and mediaQuery listener) which creates duplicates on reconnect; fix by creating persistent bound handler fields (e.g. this._boundOnTouchStart = this.#onTouchStart.bind(this), this._boundOnTouchMove, this._boundOnTouchEnd, this._boundOnContainerScroll, this._boundOnContainerDebouncedScroll, this._boundOnLoad, this._boundOnRelocate, this.#mediaQueryListener) and use those fields when calling addEventListener in connectedCallback, add a guard flag (e.g. this._initialized) to skip reattaching when already initialized, and ensure disconnectedCallback removes the same stored handlers (removeEventListener for touch, container scroll, load, relocate, and mediaQuery change) and clears the flag so reattachment is safe.
🧹 Nitpick comments (1)
frontend/src/assets/foliate/paginator.js (1)
667-669: Prefer a neutral disposer name over manualdisconnectedCallback()invocation.Line 667 manually calls
this.#view.disconnectedCallback(), butViewis not a custom element. Renaming this todispose()improves lifecycle clarity and avoids confusion with platform-managed callbacks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/assets/foliate/paginator.js` around lines 667 - 669, Replace the manual call to the platform-style disconnectedCallback on the View instance with a neutral disposer method: add/rename the View class method from disconnectedCallback to dispose (or add a dispose that calls the existing disconnectedCallback implementation), then update the call site in paginator to call this.#view.dispose() instead of this.#view.disconnectedCallback(); ensure any other references to disconnectedCallback on the View class are updated to the new dispose method to keep lifecycle semantics consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/assets/foliate/paginator.js`:
- Around line 418-420: Disconnected cleanup currently only disconnects
this.#observer but doesn't prevent the pending doc.fonts.ready.then(() =>
this.expand()) from running after teardown; add a disposed boolean property
(e.g. this.#disposed) set to true in disconnectedCallback alongside
this.#observer.disconnect(), store or wrap the fonts.ready then-handler so it
checks the disposed flag before calling this.expand(), and make expand()
defensive by early-returning if this.#disposed or if this.document is
null/undefined to avoid accessing this.document after teardown.
---
Outside diff comments:
In `@frontend/src/assets/foliate/paginator.js`:
- Around line 1120-1126: disconnectedCallback currently accesses
this.sections[this.#index]?.unload?.() which throws if this.sections is
undefined (e.g., element disconnected before open(book) ran); update
disconnectedCallback (method name) to guard the path by checking this.sections
is truthy (and that this.#index is a valid index) before calling unload on the
chosen section, e.g., only call section.unload if this.sections exists and the
indexed section is defined; ensure you still disconnect this.#observer, call
this.#view?.disconnectedCallback(), set this.#view = null, and remove the media
query listener as before.
- Around line 452-626: connectedCallback currently binds and adds new anonymous
listeners each time (touchstart/touchmove/touchend, container scroll handlers,
'load' and 'relocate' handlers and mediaQuery listener) which creates duplicates
on reconnect; fix by creating persistent bound handler fields (e.g.
this._boundOnTouchStart = this.#onTouchStart.bind(this), this._boundOnTouchMove,
this._boundOnTouchEnd, this._boundOnContainerScroll,
this._boundOnContainerDebouncedScroll, this._boundOnLoad, this._boundOnRelocate,
this.#mediaQueryListener) and use those fields when calling addEventListener in
connectedCallback, add a guard flag (e.g. this._initialized) to skip reattaching
when already initialized, and ensure disconnectedCallback removes the same
stored handlers (removeEventListener for touch, container scroll, load,
relocate, and mediaQuery change) and clears the flag so reattachment is safe.
---
Nitpick comments:
In `@frontend/src/assets/foliate/paginator.js`:
- Around line 667-669: Replace the manual call to the platform-style
disconnectedCallback on the View instance with a neutral disposer method:
add/rename the View class method from disconnectedCallback to dispose (or add a
dispose that calls the existing disconnectedCallback implementation), then
update the call site in paginator to call this.#view.dispose() instead of
this.#view.disconnectedCallback(); ensure any other references to
disconnectedCallback on the View class are updated to the new dispose method to
keep lifecycle semantics consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: a785ebc4-cc7c-4558-ae5d-a002ab429c6d
📒 Files selected for processing (1)
frontend/src/assets/foliate/paginator.js
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 334
File: booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java:402-407
Timestamp: 2026-04-02T09:25:48.330Z
Learning: In grimmory-tools/grimmory, before commenting on any file processing code (epub or pdf), always verify the current state of the relevant upstream grimmory libraries: `grimmory-tools/epub4j` (for epub) and `grimmory-tools/PDFium4j` (for pdf). These custom libraries may have different APIs, capabilities, and limitations compared to the third-party libraries they replace. Issues about streaming, buffering, or API surface may need to be filed/addressed in those upstream repos rather than in grimmory itself.
the reader was not properly cleaning up after itself after being closed. instead of using the `destroy` helper, we use the custom element callback `disconnectedCallback()` to ensure clean up happens
f539b14 to
57950d3
Compare
the reader was not properly cleaning up after itself after being closed. instead of using the
destroyhelper, we use the custom element callbackdisconnectedCallback()to ensure clean up happensDescription
Linked Issue: Fixes #954
Changes
Paginatorwhen disconnected from the documentSummary by CodeRabbit