Threads Scroll Restoration Fix (Firefox)

Fix back button not restoring scroll position on threads.com in Firefox

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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,
  });
})();