From 5da3be5200eef298b62c33869f7382f402307466 Mon Sep 17 00:00:00 2001 From: John Factotum <50942278+johnfactotum@users.noreply.github.com> Date: Tue, 9 May 2023 13:51:28 +0800 Subject: [PATCH] Add destroy method Which revokes all blob URLs and removes the element from document. --- comic-book.js | 4 ++++ epub.js | 13 ++++++++++--- fb2.js | 5 +++++ fixed-layout.js | 10 +++++++++- mobi.js | 7 +++++++ paginator.js | 7 ++++++- view.js | 4 ++++ 7 files changed, 45 insertions(+), 5 deletions(-) diff --git a/comic-book.js b/comic-book.js index a554526..cb7212f 100644 --- a/comic-book.js +++ b/comic-book.js @@ -36,5 +36,9 @@ export const makeComicBook = ({ entries, loadBlob, getSize }, file) => { book.resolveHref = href => ({ index: book.sections.findIndex(s => s.id === href) }) book.splitTOCHref = href => [href, null] book.getTOCFragment = doc => doc.documentElement + book.destroy = () => { + for (const arr of urls.values()) + for (const url of arr) URL.revokeObjectURL(url) + } return book } diff --git a/epub.js b/epub.js index 40971c6..9cd442f 100644 --- a/epub.js +++ b/epub.js @@ -611,6 +611,9 @@ class Loader { unloadItem(item) { this.unref(item?.href) } + destroy() { + for (const url of this.#cache.values()) URL.revokeObjectURL(url) + } } const getHTMLFragment = (doc, id) => doc.getElementById(id) @@ -628,6 +631,7 @@ const getPageSpread = properties => { export class EPUB { parser = new DOMParser() + #loader #encryption constructor({ loadText, loadBlob, getSize, sha1 }) { this.loadText = loadText @@ -662,7 +666,7 @@ export class EPUB { opf, resolveHref: url => resolveURL(url, opfPath), }) - const loader = new Loader({ + this.#loader = new Loader({ loadText: this.loadText, loadBlob: uri => Promise.resolve(this.loadBlob(uri)) .then(this.#encryption.getDecoder(uri)), @@ -677,8 +681,8 @@ export class EPUB { } return { id: this.resources.getItemByID(idref)?.href, - load: () => loader.loadItem(item), - unload: () => loader.unloadItem(item), + load: () => this.#loader.loadItem(item), + unload: () => this.#loader.unloadItem(item), createDocument: () => this.loadDocument(item), size: this.getSize(item.href), cfi: this.resources.cfis[index], @@ -805,4 +809,7 @@ export class EPUB { return JSON.parse(json) } } + destroy() { + this.#loader?.destroy() + } } diff --git a/fb2.js b/fb2.js index 45d2166..7fe3ae1 100644 --- a/fb2.js +++ b/fb2.js @@ -272,6 +272,7 @@ export const makeFB2 = async blob => { }), converted] }) + const urls = [] const sectionData = bodyData[0][0] // make a separate section for each section in the first body .map(({ el, ids }) => { @@ -294,6 +295,7 @@ export const makeFB2 = async blob => { const str = template(el.outerHTML) const blob = new Blob([str], { type: MIME.XHTML }) const url = URL.createObjectURL(blob) + urls.push(url) const title = normalizeWhitespace( el.querySelector('.title, .subtitle, p')?.textContent ?? (el.classList.contains('title') ? el.textContent : '')) @@ -338,5 +340,8 @@ export const makeFB2 = async blob => { book.splitTOCHref = href => href?.split('#')?.map(x => Number(x)) ?? [] book.getTOCFragment = (doc, id) => doc.querySelector(`[${dataID}="${id}"]`) + book.destroy = () => { + for (const url of urls) URL.revokeObjectURL(url) + } return book } diff --git a/fixed-layout.js b/fixed-layout.js index c00183d..2c15caf 100644 --- a/fixed-layout.js +++ b/fixed-layout.js @@ -30,6 +30,7 @@ const getViewport = (doc, viewport) => { } class Container { + #observer = new ResizeObserver(() => this.render()) #element = document.createElement('div') defaultViewport spread @@ -46,7 +47,7 @@ class Container { justifyContent: 'center', alignItems: 'center', }) - new ResizeObserver(() => this.render()).observe(this.#element) + this.#observer.observe(this.#element) } get element() { return this.#element @@ -170,6 +171,10 @@ class Container { return true } } + destroy() { + this.#observer.unobserve(this.#element) + this.#element.remove() + } } export class FixedLayout { @@ -289,4 +294,7 @@ export class FixedLayout { for (const frame of this.#container.element.querySelectorAll('iframe')) frame.contentWindow.getSelection().removeAllRanges() } + destroy() { + this.#container.destroy() + } } diff --git a/mobi.js b/mobi.js index 3b72840..371b42f 100644 --- a/mobi.js +++ b/mobi.js @@ -842,6 +842,10 @@ class MOBI6 { isExternal(uri) { return /^(?!blob|filepos)\w+:/i.test(uri) } + destroy() { + for (const url of this.#resourceCache.values()) URL.revokeObjectURL(url) + for (const url of this.#cache.values()) URL.revokeObjectURL(url) + } } // handlers for `kindle:` uris @@ -1154,4 +1158,7 @@ class KF8 { isExternal(uri) { return /^(?!blob|kindle)\w+:/i.test(uri) } + destroy() { + for (const url of this.#cache.values()) URL.revokeObjectURL(url) + } } diff --git a/paginator.js b/paginator.js index c4c5d21..475a983 100644 --- a/paginator.js +++ b/paginator.js @@ -329,6 +329,7 @@ class View { export class Paginator { #gap = 0 #shouldUpdateGap = true + #observer = new ResizeObserver(() => this.render()) #element = document.createElement('div') #background = document.createElement('div') #maxSizeContainer = document.createElement('div') @@ -394,7 +395,7 @@ export class Paginator { this.#maxSizeContainer.append(this.#header) this.#maxSizeContainer.append(this.#footer) - new ResizeObserver(() => this.render()).observe(this.#element) + this.#observer.observe(this.#element) this.#container.addEventListener('scroll', debounce(() => { if (this.scrolled) this.#afterScroll('scroll') }, 250)) @@ -774,4 +775,8 @@ export class Paginator { this.#anchor = anchor await this.#scrollToAnchor(select) } + destroy() { + this.#observer.unobserve(this.#element) + this.#element.remove() + } } diff --git a/view.js b/view.js index 6656fc3..b84e09f 100644 --- a/view.js +++ b/view.js @@ -270,4 +270,8 @@ export class View { subitems: result.subitems, } : result } + destroy() { + this.book.destroy?.() + this.renderer?.destroy?.() + } }