Youtube scroll lock timestamp in picture-in-picture

When using Picture-in-Picture (PiP), don't scroll to the top of the page when clicking on a timestamp that displays the time of the video.

As of 2023-05-16. See the latest version.

// ==UserScript==
// @name           Youtube scroll lock timestamp in picture-in-picture
// @name:ja        YouTubeでピクチャーインピクチャー使用時、タイムスタンプでページトップへの遷移防止
// @namespace      https://github.com/ziopuzzle/
// @version        1.3
// @description    When using Picture-in-Picture (PiP), don't scroll to the top of the page when clicking on a timestamp that displays the time of the video.
// @description:ja 動画をポップアウトしていても、タイムスタンプをクリック時にページトップに強制スクロールする現象を解決します。
// @author         ziopuzzle
// @match          https://www.youtube.com/*
// @grant          none
// @license        MIT
// ==/UserScript==

(function() {
    'use strict';

    const SEND_LOG = false;

    function sendLog(s) {
        if (SEND_LOG) {
            console.log('[PIP_SCROLL_LOCK] ' + s);
        }
    }
    function loadPage(fn) {
        // You can see "yt-*" events using the event list feature, which works only in Chrome DevTools.
        // getEventListeners(document);
        // Fook page change(Youtube is single page application)
        // Note: https://stackoverflow.com/questions/24297929/
        document.addEventListener('yt-navigate-finish', fn, false);
    }
    function timeLinkClick() {
        // If using Picture-in-Picture or using position 'fixed'
        const usingPIP = document.pictureInPictureElement != null;
        const usingFixed = document.querySelector('.ytd-player').style.position == 'fixed';
        if (usingPIP || usingFixed) {
            // After scrolling back to the top by Youtube, restore the scroll position.
            const scroll = document.documentElement.scrollTop;
            const eventListner = (event) => {
                if (event.detail === 0) {
                    sendLog('scroll to ' + scroll.toString());
                    document.documentElement.scrollTop = scroll;
                    document.removeEventListener('yt-watch-masthead-scroll', eventListner);
                }
            };
            document.addEventListener('yt-watch-masthead-scroll', eventListner);
        }
    }
    // Disabled script if youtube is embedded
    if (window.top === window.self) {
        loadPage(() => {
            sendLog('page loaded');
            // For check playback time link
            const videoID = new URL(window.location.href).searchParams.get('v');
            sendLog('VideoID: ' + videoID);
            // Observe comment elements to be added
            const observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => { mutation.addedNodes.forEach((element) => {
                    if (element.tagName == 'YTD-COMMENT-THREAD-RENDERER') {
                        // Extract links where the time written.
                        element.querySelectorAll('a[href*="t="][href*="v=' + videoID + '"]').forEach((element) => {
                            sendLog('playback link found');
                            // Make event when a link is clicked
                            element.addEventListener('click', timeLinkClick, false);
                        });
                    }
                });});
            })
            observer.observe(document.getElementById('content'), { childList: true, subtree: true });
            sendLog('observer connect');
            document.addEventListener('yt-navigate-start', () => {
                observer.disconnect();
                sendLog('observer disconnect');
            }, {once : true});
        });
    }

})();