Fix back button not restoring scroll position on threads.com in Firefox
// ==UserScript==
// @name Threads Scroll Restoration Fix (Firefox)
// @namespace Tsuyumi25
// @version 0.2
// @description Fix back button not restoring scroll position on threads.com in Firefox
// @match https://www.threads.com/*
// @match https://threads.com/*
// @run-at document-start
// @inject-into page
// @license MIT
// @grant none
// ==/UserScript==
(function () {
'use strict';
const SCROLL_KEY = '__threads_scroll_pos';
let restoring = false;
history.scrollRestoration = 'manual';
function saveScroll() {
if (!restoring) {
sessionStorage.setItem(SCROLL_KEY + ':' + location.href, String(document.documentElement.scrollTop));
}
}
function tryRestore(url) {
const saved = sessionStorage.getItem(SCROLL_KEY + ':' + url);
if (saved === null) return;
const target = parseInt(saved, 10);
if (target === 0) return;
restoring = true;
let attempts = 0;
const maxAttempts = 50;
function tick() {
attempts++;
if (document.documentElement.scrollHeight >= target + window.innerHeight || attempts >= maxAttempts) {
window.scrollTo(0, target);
// 短暫守住位置,防止 Threads 的 JS 之後 scrollTo(0,0) 蓋掉
setTimeout(() => { restoring = false; }, 500);
return;
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
// 攔截 pushState — 存 scroll,導航後嘗試恢復目標 URL 的位置
const _pushState = history.pushState.bind(history);
history.pushState = function (state, title, url) {
saveScroll();
_pushState(state, title, url);
// 如果目標 URL 有存過 scroll(例如 in-app 返回按鈕走 pushState 回時間線)
const dest = new URL(url, location.href).href;
tryRestore(dest);
};
// 攔截 replaceState
const _replaceState = history.replaceState.bind(history);
history.replaceState = function (state, title, url) {
if (url) saveScroll();
return _replaceState(state, title, url);
};
// popstate(瀏覽器 back/forward 按鈕、滑鼠側鍵)
window.addEventListener('popstate', function () {
tryRestore(location.href);
});
// 攔截 scrollTo,在恢復期間擋掉 scrollTo(0,0)
const _scrollTo = window.scrollTo.bind(window);
window.scrollTo = function (x, y) {
if (restoring) {
// 只擋 scrollTo(0, 0),其他放行
if ((x === 0 && y === 0) || (typeof x === 'object' && x.top === 0)) return;
}
return _scrollTo(x, y);
};
// 鎖死 scrollRestoration
Object.defineProperty(history, 'scrollRestoration', {
get: () => 'manual',
set: () => {},
configurable: false,
});
})();