From f06c6fdcbf24ba56fc16e69069734d4436010622 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Sun, 26 Feb 2023 15:08:02 -0800 Subject: [PATCH] Revert "x-img: simplify logic of cache" This reverts commit d86e9406d38238eac08a9eaa995193a148b379bf. --- src/components/frame/XImg.vue | 70 +++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/src/components/frame/XImg.vue b/src/components/frame/XImg.vue index c2790b6a..efe3b0b6 100644 --- a/src/components/frame/XImg.vue +++ b/src/components/frame/XImg.vue @@ -6,7 +6,7 @@ import { defineComponent } from "vue"; import { fetchImage } from "./XImgCache"; -const BLOB_CACHE: { [src: string]: string } = {}; +const BLOB_CACHE: { [src: string]: [number, string] } = {}; const BLANK_IMG = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; @@ -26,11 +26,13 @@ export default defineComponent({ data: () => { return { dataSrc: BLANK_IMG, + isDestroyed: false, }; }, watch: { - src() { + src(newSrc, oldSrc) { + this.cleanup(oldSrc); this.loadImage(); }, }, @@ -39,9 +41,15 @@ export default defineComponent({ this.loadImage(); }, + beforeDestroy() { + this.cleanup(this.src); + this.isDestroyed = true; + }, + methods: { async loadImage() { if (!this.src) return; + this.isDestroyed = false; // Just set src if not http if (this.src.startsWith("data:") || this.src.startsWith("blob:")) { @@ -51,20 +59,35 @@ export default defineComponent({ // Fetch image with axios try { - const src = this.src; - if (BLOB_CACHE[src]) { - this.dataSrc = BLOB_CACHE[src]; - return; - } + // Use BLOB from cache assuming it exists + const usedCache = (src: string) => { + if (BLOB_CACHE[src]) { + this.dataSrc = BLOB_CACHE[src][1]; + BLOB_CACHE[src][0]++; + return true; + } + return false; + }; - const newBlob = await fetchImage(src); - if (this.src === src) { - const blobUrl = URL.createObjectURL(newBlob); - BLOB_CACHE[src] = this.dataSrc = blobUrl; - setTimeout(() => { - if (BLOB_CACHE[src] === blobUrl) delete BLOB_CACHE[src]; - URL.revokeObjectURL(blobUrl); - }, 60 * 1000); + // Check if the blob cache exists + if (!usedCache(this.src)) { + const src = this.src; + const newBlob = URL.createObjectURL(await fetchImage(src)); + if (this.src !== src || this.isDestroyed) { + URL.revokeObjectURL(newBlob); // the src has changed, abort + return; + } + + // Check if the blob cache exists now + // In this case, someone else already created the blob + // Free up the current blob and use the existing one instead + if (usedCache(src)) { + URL.revokeObjectURL(newBlob); + } else { + // Create new blob cache entry + this.dataSrc = newBlob; + BLOB_CACHE[src] = [1, this.dataSrc]; + } } } catch (error) { this.dataSrc = BLANK_IMG; @@ -76,6 +99,23 @@ export default defineComponent({ if (this.dataSrc === BLANK_IMG) return; this.$emit("load", this.dataSrc); }, + + async cleanup(src: string) { + if (!src) return; + + // Wait for 1s before collecting garbage + await new Promise((r) => setTimeout(r, 1000)); + + // Clean up blob cache + const cache = BLOB_CACHE[src]; + if (!cache) return; + + // Remove blob from cache + if (--cache[0] <= 0) { + URL.revokeObjectURL(cache[1]); + delete BLOB_CACHE[src]; + } + }, }, });