From 2492ca8c25a1fa6328a2fd850990b87928210e31 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Sun, 12 Jul 2026 19:46:17 +0200 Subject: [PATCH] chromium --- public/s/js/f0ckm.js | 119 +++++++++++++++++++++++-------------------- src/index.mjs | 11 ++++ 2 files changed, 76 insertions(+), 54 deletions(-) diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index 6d9e21d..33228ea 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -184,61 +184,69 @@ window.cancelAnimFrame = (function () { return; } - if (!lazyObserver) { - lazyObserver = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - const thumb = entry.target; - let bg = thumb.dataset.bg; - if (bg && !thumb.classList.contains('loaded')) { - const mode = thumb.getAttribute('data-mode'); - const blurNsfw = localStorage.getItem('blurNsfw') === 'true'; - const blurNsfl = localStorage.getItem('blurNsfl') === 'true'; - const blurSfw = localStorage.getItem('blurSfw') === 'true'; - const blurUntagged = localStorage.getItem('blurUntagged') === 'true'; - - let shouldBlurThis = false; - if (mode === 'nsfw') shouldBlurThis = blurNsfw; - else if (mode === 'nsfl') shouldBlurThis = blurNsfl; - else if (mode === 'sfw') shouldBlurThis = blurSfw; - else if (mode === 'null' || !mode) shouldBlurThis = blurUntagged; - - if (shouldBlurThis && !thumb.classList.contains('revealed')) { - bg = bg.replace('.webp', '_blur.webp'); - } - - bg = window.applyThumbCacheBust(bg); - const img = new Image(); - img.onload = () => { - thumb.style.backgroundImage = `url('${bg}')`; - thumb.classList.add('loaded'); - thumb.classList.remove('lazy-thumb'); - }; - img.onerror = () => { - const retries = parseInt(thumb.dataset.retries || '0'); - if (retries < 3) { - thumb.dataset.retries = retries + 1; - setTimeout(() => { - img.src = bg + '?r=' + Date.now(); - }, 1000); - } else { - // All retries exhausted — show fallback for audio items - const mime = thumb.dataset.mime || ''; - if (mime.startsWith('audio/')) { - thumb.style.backgroundImage = `url('/s/img/audio.webp')`; - thumb.classList.add('thumb-fallback'); - } - thumb.classList.remove('lazy-thumb'); - } - }; - img.src = bg; - } - lazyObserver.unobserve(thumb); - } - }); - }, { rootMargin: '300px 0px', threshold: 0.01 }); + // Chromium fix: always disconnect the old observer before creating a new one. + // Chromium holds detached DOM nodes in an IntersectionObserver alive much longer + // than Firefox does. If we reuse a stale observer after an AJAX content swap, + // Chromium can fire callbacks for old detached nodes, causing duplicate image + // fetches and flooding the network. Disconnecting releases those references. + if (lazyObserver) { + lazyObserver.disconnect(); + lazyObserver = null; } + lazyObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const thumb = entry.target; + let bg = thumb.dataset.bg; + if (bg && !thumb.classList.contains('loaded')) { + const mode = thumb.getAttribute('data-mode'); + const blurNsfw = localStorage.getItem('blurNsfw') === 'true'; + const blurNsfl = localStorage.getItem('blurNsfl') === 'true'; + const blurSfw = localStorage.getItem('blurSfw') === 'true'; + const blurUntagged = localStorage.getItem('blurUntagged') === 'true'; + + let shouldBlurThis = false; + if (mode === 'nsfw') shouldBlurThis = blurNsfw; + else if (mode === 'nsfl') shouldBlurThis = blurNsfl; + else if (mode === 'sfw') shouldBlurThis = blurSfw; + else if (mode === 'null' || !mode) shouldBlurThis = blurUntagged; + + if (shouldBlurThis && !thumb.classList.contains('revealed')) { + bg = bg.replace('.webp', '_blur.webp'); + } + + bg = window.applyThumbCacheBust(bg); + const img = new Image(); + img.onload = () => { + thumb.style.backgroundImage = `url('${bg}')`; + thumb.classList.add('loaded'); + thumb.classList.remove('lazy-thumb'); + }; + img.onerror = () => { + const retries = parseInt(thumb.dataset.retries || '0'); + if (retries < 3) { + thumb.dataset.retries = retries + 1; + setTimeout(() => { + img.src = bg + '?r=' + Date.now(); + }, 1000); + } else { + // All retries exhausted — show fallback for audio items + const mime = thumb.dataset.mime || ''; + if (mime.startsWith('audio/')) { + thumb.style.backgroundImage = `url('/s/img/audio.webp')`; + thumb.classList.add('thumb-fallback'); + } + thumb.classList.remove('lazy-thumb'); + } + }; + img.src = bg; + } + lazyObserver.unobserve(thumb); + } + }); + }, { rootMargin: '300px 0px', threshold: 0.01 }); + // Nudge lazy loading on tab switch to prevent stuck skeletons in inactive tabs if (!window._lazyVisibilityBound) { window._lazyVisibilityBound = true; @@ -254,7 +262,10 @@ window.cancelAnimFrame = (function () { } document.querySelectorAll('.lazy-thumb').forEach(thumb => { - if (!thumb.dataset.lazyObserved) { + // Reset lazyObserved on every initLazyLoading call so the fresh observer + // picks up all unloaded thumbs regardless of previous observation state. + delete thumb.dataset.lazyObserved; + if (!thumb.classList.contains('loaded')) { thumb.dataset.lazyObserved = 'true'; lazyObserver.observe(thumb); } diff --git a/src/index.mjs b/src/index.mjs index ef17308..1e04607 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -535,6 +535,17 @@ process.on('uncaughtException', err => { } return; } + // Thumbnails (/t/) and covers (/ca/) are static WebP files that never change at a + // given URL — the client appends a ?t= cache-bust param when a thumb is regenerated. + // Without an explicit Cache-Control header, Chromium treats these as uncacheable and + // re-fetches every visible thumbnail on every AJAX navigation, causing severe lag. + // Firefox uses a more lenient heuristic and caches them anyway — hence the difference. + if (req.url.pathname.match(/^\/(t|ca)\//)) { + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); + } else if (req.url.pathname.match(/^\/(b|a|memes)\//)) { + // Full-size images and avatars: cache for 1 hour, re-validate after + res.setHeader('Cache-Control', 'public, max-age=3600'); + } return; } const availableThemes = cfg.websrv.themes || ['amoled', 'atmos', 'f0ck', 'f0ck95d', 'iced', 'orange', 'p1nk', '4d'];