YouTube 超快聊天

YouTube直播聊天的終極性能提升

目前為 2023-07-20 提交的版本,檢視 最新版本

// ==UserScript==
// @name                YouTube Super Fast Chat
// @version             0.10.3
// @license             MIT
// @name:ja             YouTube スーパーファーストチャット
// @name:zh-TW          YouTube 超快聊天
// @name:zh-CN          YouTube 超快聊天
// @namespace           UserScript
// @match               https://www.youtube.com/live_chat*
// @author              CY Fung
// @require             https://greasyfork.org/scripts/465819-api-for-customelements-in-youtube/code/API%20for%20CustomElements%20in%20YouTube.js?version=1215280
// @run-at              document-start
// @grant               none
// @unwrap
// @allFrames           true
// @inject-into         page
//
// @description         Ultimate Performance Boost for YouTube Live Chats
// @description:ja      YouTubeのライブチャットの究極のパフォーマンスブースト
// @description:zh-TW   YouTube直播聊天的終極性能提升
// @description:zh-CN   YouTube直播聊天的终极性能提升
//
// ==/UserScript==

((__CONTEXT__) => {
    'use strict';

    const ENABLE_REDUCED_MAXITEMS_FOR_FLUSH = true;
    const MAX_ITEMS_FOR_TOTAL_DISPLAY = 90;
    const MAX_ITEMS_FOR_FULL_FLUSH = 25;

    const ENABLE_NO_SMOOTH_TRANSFORM = true;
    const USE_OPTIMIZED_ON_SCROLL_ITEMS = true;
    const USE_WILL_CHANGE_CONTROLLER = false;
    const ENABLE_FULL_RENDER_REQUIRED_PREFERRED = true;
    const ENABLE_OVERFLOW_ANCHOR_PREFERRED = true;


    let cssText1 = '';
    let cssText2 = '';
    let cssText3 = '';
    let cssText4 = '';
    let cssText5 = '';
    let cssText6 = '';
    let cssText7 = '';

    function dr(s) {
        // reserved for future use
        return s;
        // return window.deWeakJS ? window.deWeakJS(s) : s;
    }


    if (ENABLE_NO_SMOOTH_TRANSFORM) {

        cssText3 = `

        #item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer {
            position: static !important;
        }
    `
        cssText4 =


            `


        /* optional */
        #item-offset.style-scope.yt-live-chat-item-list-renderer {
        height: auto !important;
        min-height: unset !important;
        }

        #items.style-scope.yt-live-chat-item-list-renderer {
        transform: translateY(0px) !important;
        }

        /* optional */

      `
    }

    if (1) {
        cssText5 = `



      /* ------------------------------------------------------------------------------------------------------------- */

      yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip, yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer, yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer #image, yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer #image img {
        contain: layout style;
      }

      #items.style-scope.yt-live-chat-item-list-renderer {
        contain: layout paint style;
      }

      #item-offset.style-scope.yt-live-chat-item-list-renderer {
        contain: style;
      }

      #item-scroller.style-scope.yt-live-chat-item-list-renderer {
        contain: size style;
      }

      #contents.style-scope.yt-live-chat-item-list-renderer, #chat.style-scope.yt-live-chat-renderer, img.style-scope.yt-img-shadow[width][height] {
        contain: size layout paint style;
      }

      .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label], .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label] > #container {
        contain: layout paint style;
      }

      yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer, yt-live-chat-membership-item-renderer.style-scope.yt-live-chat-item-list-renderer, yt-live-chat-paid-message-renderer.style-scope.yt-live-chat-item-list-renderer, yt-live-chat-banner-manager.style-scope.yt-live-chat-item-list-renderer {
        contain: layout style;
      }

      tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
        contain: layout paint style;
      }

      /* ------------------------------------------------------------------------------------------------------------- */

    `
    }

    if (1) {

        cssText6 = `


        yt-icon[icon="down_arrow"] > *, yt-icon-button#show-more > * {
        pointer-events: none !important;
        }

        #continuations, #continuations * {
            contain: strict;
            position: fixed;
            top: 2px;
            height: 1px;
            width: 2px;
            height: 1px;
            visibility: collapse;
        }

        yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer{
            top: 4px;
            transition-property: top;
            bottom: unset;
        }

        yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer[disabled]{
            top: -42px;
        }

        html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer:not(:empty) {
            --yt-live-chat-action-panel-top-border: none;
        }

        html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer.iron-selected > *:first-child {
            border-top: 1px solid var(--yt-live-chat-panel-pages-border-color);
        }

        html #panel-pages.yt-live-chat-renderer {
            border-top: 0;
            border-bottom: 0;
        }

        #input-panel #picker-buttons yt-live-chat-icon-toggle-button-renderer#product-picker {
            /*
            overflow: hidden;
            contain: layout paint style;
            */
            contain: layout style;
        }

        #chat.yt-live-chat-renderer ~ #panel-pages.yt-live-chat-renderer {
            overflow: visible;
        }


    `
    }

    if (1) {
        cssText7 = `


        .ytp-contextmenu[class],
        .toggle-button.tp-yt-paper-toggle-button[class],
        .yt-spec-touch-feedback-shape__fill[class],
        .fill.yt-interaction[class],
        .ytp-videowall-still-info-content[class],
        .ytp-suggestion-image[class] {
          will-change: unset !important;
        }

        img {
          content-visibility: visible !important;
        }

        yt-img-shadow[height][width],
        yt-img-shadow {
          content-visibility: visible !important;
        }

        yt-live-chat-item-list-renderer:not([allow-scroll]) #item-scroller.yt-live-chat-item-list-renderer {
            overflow-y: scroll;
            padding-right: 0;
        }

    `
    }

    function addCssElement() {
        let s = document.createElement('style')
        s.id = 'ewRvC';
        return s;
    }

    const addCss = () => document.head.appendChild(dr(addCssElement())).textContent = `


    @supports (contain:layout paint style) and (content-visibility:auto) and (contain-intrinsic-size:auto var(--wsr94)) {

        ${cssText1}
    }

    @supports (contain:layout paint style) {

      ${cssText2}


      ${cssText5}

    }

    @supports (color: var(--general)) {

        html {
          --yt-live-chat-item-list-renderer-padding: 0px 0px;
        }

        ${cssText3}

        ${cssText7}


        ${cssText4}

        ${cssText6}

        .no-anchor * {
            overflow-anchor: none;
        }
        .no-anchor > item-anchor {
            overflow-anchor: auto;
        }

        item-anchor {

            height:1px;
            width: 100%;
            transform: scaleY(0.00001);
            transform-origin:0 0;
            contain: strict;
            opacity:0;
            display:flex;
            position:relative;
            flex-shrink:0;
            flex-grow:0;
            margin-bottom:0;
            overflow:hidden;
            box-sizing:border-box;
            visibility: visible;
            content-visibility: visible;
            contain-intrinsic-size: auto 1px;
            pointer-events:none !important;

        }

        #item-scroller.style-scope.yt-live-chat-item-list-renderer[class] {
            overflow-anchor: initial !important; /* whenever ENABLE_OVERFLOW_ANCHOR or not */
        }

        html item-anchor {

            height: 1px;
            width: 1px;
            top:auto;
            left:auto;
            right:auto;
            bottom:auto;
            transform: translateY(-1px);
            position: absolute;
            z-index:-1;

        }



        @keyframes dontRenderAnimation {
            0% {
                background-position-x: 3px;
            }
            100% {
                background-position-x: 4px;
            }
        }

        /*html[dont-render-enabled] */ .dont-render{
            visibility: collapse !important;
            transform: scale(0.01) !important;
            transform: scale(0.00001) !important;
            transform: scale(0.0000001) !important;
            transform-origin:0 0 !important;
            z-index:-1 !important;
            contain: strict !important;
            box-sizing: border-box !important;

            height:1px !important;
            height:0.1px !important;
            height:0.01px !important;
            height:0.0001px !important;
            height:0.000001px !important;


            animation: dontRenderAnimation 1ms linear 80ms 1 normal forwards !important;

        }



    }

    `;


    const { IntersectionObserver } = __CONTEXT__;

    /** @type {globalThis.PromiseConstructor} */
    const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.

    if (!IntersectionObserver) return console.error("Your browser does not support IntersectionObserver.\nPlease upgrade to the latest version.")

    let ENABLE_FULL_RENDER_REQUIRED_CAPABLE = false;
    const isContainSupport = CSS.supports('contain', 'layout paint style');
    if (!isContainSupport) {
        console.warn("Your browser does not support css property 'contain'.\nPlease upgrade to the latest version.".trim());
    } else {

        ENABLE_FULL_RENDER_REQUIRED_CAPABLE = true; // mainly for Chromium-based browsers

    }



    let ENABLE_OVERFLOW_ANCHOR_CAPABLE = false;
    const isOverflowAnchorSupport = CSS.supports('overflow-anchor', 'auto');
    if (!isOverflowAnchorSupport) {
        console.warn("Your browser does not support css property 'overflow-anchor'.\nPlease upgrade to the latest version.".trim());
    } else {

        ENABLE_OVERFLOW_ANCHOR_CAPABLE = true; // mainly for Chromium-based browsers

    }


    const ENABLE_OVERFLOW_ANCHOR = ENABLE_OVERFLOW_ANCHOR_PREFERRED && ENABLE_OVERFLOW_ANCHOR_CAPABLE && ENABLE_NO_SMOOTH_TRANSFORM;


    const ENABLE_FULL_RENDER_REQUIRED = ENABLE_FULL_RENDER_REQUIRED_PREFERRED && ENABLE_FULL_RENDER_REQUIRED_CAPABLE && ENABLE_OVERFLOW_ANCHOR && ENABLE_NO_SMOOTH_TRANSFORM;



    const win = this || window;

    // Create a unique key for the script and check if it is already running
    const hkey_script = 'mchbwnoasqph';
    if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
    win[hkey_script] = true;

    const cleanContext = async (win) => {
        const waitFn = requestAnimationFrame; // shall have been binded to window
        try {
            const mx = 16; // MAX TRIAL
            const frame = document.createElement('iframe');
            frame.sandbox = 'allow-same-origin';
            const n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
            n.appendChild(frame);
            while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
            const root = document.documentElement;
            root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
            while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
            const fc = frame.contentWindow;
            if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
            const { requestAnimationFrame } = fc;
            const res = { requestAnimationFrame };
            for (let k in res) res[k] = res[k].bind(win); // necessary
            n.remove();
            return res;
        } catch (e) {
            console.warn(e);
            return null;
        }
    };

    cleanContext(win).then(__CONTEXT__ => {
        if (!__CONTEXT__) return null;


        const { requestAnimationFrame } = __CONTEXT__;


        ENABLE_FULL_RENDER_REQUIRED && document.addEventListener('animationstart', (evt) => {

            if (evt.animationName === 'dontRenderAnimation') {
                evt.target.classList.remove('dont-render');
                if (scrollChatFn) scrollChatFn();
            }

        }, true);

        ENABLE_FULL_RENDER_REQUIRED && (() => {

            const f = (elm) => {
                if (elm && elm.nodeType === 1) {
                    elm.classList.add('dont-render');
                }
            }

            Node.prototype.__appendChild931__ = function (a) {
                a = dr(a);
                if (this.id === 'items' && this.classList.contains('yt-live-chat-item-list-renderer')) {
                    if (a && a.nodeType === 1) f(a);
                    else if (a instanceof DocumentFragment) {
                        for (let n = a.firstChild; n; n = n.nextSibling) {
                            f(n);
                        }
                    }
                }
            }

            Node.prototype.__appendChild932__ = function () {
                this.__appendChild931__.apply(this, arguments);
                return Node.prototype.appendChild.apply(this, arguments)
            }


        })();

        // let delayedAppendParentWS = new WeakSet();
        // let delayedAppendOperations = [];
        // let commonAppendParentStackSet = new Set();

        // let firstVisibleItemDetected = false; // deprecated

        const sp7 = Symbol();


        let dt0 = Date.now() - 2000;
        const dateNow = () => Date.now() - dt0;
        // let lastScroll = 0;
        // let lastLShow = 0;
        let lastWheel = 0;

        let scrollChatFn = null;
        let lastAddition = 0;

        const proxyHelperFn = (dummy) => ({

            get(target, prop) {
                return (prop in dummy) ? dummy[prop] : prop === sp7 ? target : target[prop];
            },
            set(target, prop, value) {
                if (!(prop in dummy)) {
                    target[prop] = value;
                }
                return true;
            },
            has(target, prop) {
                return (prop in target)
            },
            deleteProperty(target, prop) {
                return true;
            },
            ownKeys(target) {
                return Object.keys(target);
            },
            defineProperty(target, key, descriptor) {
                return Object.defineProperty(target, key, descriptor);
            },
            getOwnPropertyDescriptor(target, key) {
                return Object.getOwnPropertyDescriptor(target, key);
            },

        });

        const tickerContainerSetAttribute = function (attrName, attrValue) { // ensure '14.30000001%'.toFixed(1)

            let yd = (this.__dataHost || (this.inst || 0).__dataHost).__data;

            if (arguments.length === 2 && attrName === 'style' && yd && attrValue) {

                // let v = yd.containerStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
                let v = `${attrValue}`;
                // conside a ticker is 101px width
                // 1% = 1.01px
                // 0.2% = 0.202px


                const ratio1 = (yd.ratio * 100);
                if (ratio1 > -1) { // avoid NaN

                    // countdownDurationMs
                    // 600000 - 0.2%    <1% = 6s>  <0.2% = 1.2s>
                    // 300000 - 0.5%    <1% = 3s>  <0.5% = 1.5s>
                    // 150000 - 1%    <1% = 1.5s>
                    // 75000 - 2%    <1% =0.75s > <2% = 1.5s>
                    // 30000 - 5%    <1% =0.3s > <5% = 1.5s>

                    // 99px * 5% = 4.95px

                    // 15000 - 10%    <1% =0.15s > <10% = 1.5s>




                    // 1% Duration

                    let ratio2 = ratio1;

                    const ydd = yd.data;
                    const d1 = ydd.durationSec;
                    const d2 = ydd.fullDurationSec;

                    if (d1 === d2 && d1 > 1) {

                        if (d1 > 400) ratio2 = Math.round(ratio2 * 5) / 5; // 0.2%
                        else if (d1 > 200) ratio2 = Math.round(ratio2 * 2) / 2; // 0.5%
                        else if (d1 > 100) ratio2 = Math.round(ratio2 * 1) / 1; // 1%
                        else if (d1 > 50) ratio2 = Math.round(ratio2 * 0.5) / 0.5; // 2%
                        else if (d1 > 25) ratio2 = Math.round(ratio2 * 0.2) / 0.2; // 5% (max => 99px * 5% = 4.95px)
                        else ratio2 = Math.round(ratio2 * 0.2) / 0.2;

                    } else {
                        ratio2 = Math.round(ratio2 * 5) / 5; // 0.2% (min)
                    }

                    // ratio2 = Math.round(ratio2 * 5) / 5;
                    ratio2 = ratio2.toFixed(1)
                    v = v.replace(`${ratio1}%`, `${ratio2}%`).replace(`${ratio1}%`, `${ratio2}%`)

                    if (yd.__style_last__ === v) return;
                    yd.__style_last__ = v;
                    // do not consider any delay here.
                    // it shall be inside the looping for all properties changes. all the css background ops are in the same microtask.

                }

                HTMLElement.prototype.setAttribute.call(dr(this), attrName, v);


            } else {
                HTMLElement.prototype.setAttribute.apply(dr(this), arguments);
            }

        };

        const fxOperator = (proto, propertyName) => {
            let propertyDescriptorGetter = null;
            try {
                propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
            } catch (e) { }
            return typeof propertyDescriptorGetter === 'function' ? (e) => {
                try {

                    return propertyDescriptorGetter.call(dr(e))
                } catch (e) { }
                return e[propertyName];
            } : (e) => e[propertyName];
        };

        const nodeParent = fxOperator(Node.prototype, 'parentNode');
        // const nFirstElem = fxOperator(HTMLElement.prototype, 'firstElementChild');
        const nPrevElem = fxOperator(HTMLElement.prototype, 'previousElementSibling');
        const nNextElem = fxOperator(HTMLElement.prototype, 'nextElementSibling');
        const nLastElem = fxOperator(HTMLElement.prototype, 'lastElementChild');


        /* globals WeakRef:false */

        /** @type {(o: Object | null) => WeakRef | null} */
        const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'

        /** @type {(wr: Object | null) => Object | null} */
        const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);

        const watchUserCSS = () => {

            // if (!CSS.supports('contain-intrinsic-size', 'auto var(--wsr94)')) return;

            const getElemFromWR = (nr) => {
                const n = kRef(nr);
                if (n && n.isConnected) return n;
                return null;
            }

            const clearContentVisibilitySizing = () => {
                Promise.resolve().then(() => {

                    let btnShowMoreWR = mWeakRef(document.querySelector('#show-more[disabled]'));

                    let lastVisibleItemWR = null;
                    for (const elm of document.querySelectorAll('[wSr93]')) {
                        if (elm.getAttribute('wSr93') === 'visible') lastVisibleItemWR = mWeakRef(elm);
                        elm.setAttribute('wSr93', '');
                        // custom CSS property --wsr94 not working when attribute wSr93 removed
                    }
                    requestAnimationFrame(() => {
                        const btnShowMore = getElemFromWR(btnShowMoreWR); btnShowMoreWR = null;
                        if (btnShowMore) btnShowMore.click();
                        else {
                            // would not work if switch it frequently
                            const lastVisibleItem = getElemFromWR(lastVisibleItemWR); lastVisibleItemWR = null;
                            if (lastVisibleItem) {

                                Promise.resolve()
                                    .then(() => lastVisibleItem.scrollIntoView())
                                    .then(() => lastVisibleItem.scrollIntoView(false))
                                    .then(() => lastVisibleItem.scrollIntoView({ behavior: "instant", block: "end", inline: "nearest" }))
                                    .catch(e => { }) // break the chain when method not callable

                            }
                        }
                    })

                })

            }

            const mutObserver = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    if ((mutation.addedNodes || 0).length >= 1) {
                        for (const addedNode of mutation.addedNodes) {
                            if (addedNode.nodeName === 'STYLE') {
                                clearContentVisibilitySizing();
                                return;
                            }
                        }
                    }
                    if ((mutation.removedNodes || 0).length >= 1) {
                        for (const removedNode of mutation.removedNodes) {
                            if (removedNode.nodeName === 'STYLE') {
                                clearContentVisibilitySizing();
                                return;
                            }
                        }
                    }
                }
            });

            mutObserver.observe(document.documentElement, {
                childList: true,
                subtree: false
            })

            mutObserver.observe(document.head, {
                childList: true,
                subtree: false
            })
            mutObserver.observe(document.body, {
                childList: true,
                subtree: false
            });

        }

        const setupStyle = (m1, m2) => {
            if (!ENABLE_NO_SMOOTH_TRANSFORM) return;

            const dummy1v = {
                transform: '',
                height: '',
                minHeight: '',
                paddingBottom: '',
                paddingTop: ''
            };

            for (const k of ['toString', 'getPropertyPriority', 'getPropertyValue', 'item', 'removeProperty', 'setProperty']) {
                dummy1v[k] = ((k) => (function () { const style = this[sp7]; return style[k](...arguments); }))(k)
            }

            const dummy1p = proxyHelperFn(dummy1v);
            const sp1v = new Proxy(m1.style, dummy1p);
            const sp2v = new Proxy(m2.style, dummy1p);
            Object.defineProperty(m1, 'style', { get() { return sp1v }, set() { }, enumerable: true, configurable: true });
            Object.defineProperty(m2, 'style', { get() { return sp2v }, set() { }, enumerable: true, configurable: true });
            m1.removeAttribute("style");
            m2.removeAttribute("style");

        }


        class WillChangeController {
            constructor(itemScroller, willChangeValue) {
                this.element = itemScroller;
                this.counter = 0;
                this.active = false;
                this.willChangeValue = willChangeValue;
            }

            beforeOper() {
                if (!this.active) {
                    this.active = true;
                    this.element.style.willChange = this.willChangeValue;
                }
                this.counter++;
            }

            afterOper() {
                const c = this.counter;
                requestAnimationFrame(() => {
                    if (c === this.counter) {
                        this.active = false;
                        this.element.style.willChange = '';
                    }
                })
            }

            release() {
                const element = this.element;
                this.element = null;
                this.counter = 1e16;
                this.active = false;
                try {
                    element.style.willChange = '';
                } catch (e) { }
            }

        }

        customYtElements.onRegistryReady(() => {


            let scrollWillChangeController = null;
            let contensWillChangeController = null;

            // as it links to event handling, it has to be injected using immediateCallback
            customYtElements.whenRegistered('yt-live-chat-item-list-renderer', (cProto) => {


                const mclp = cProto;
                console.assert(typeof mclp.scrollToBottom_ === 'function')
                console.assert(typeof mclp.scrollToBottom66_ !== 'function')
                console.assert(typeof mclp.flushActiveItems_ === 'function')
                console.assert(typeof mclp.flushActiveItems66_ !== 'function')
                console.assert(typeof mclp.async === 'function')


                mclp.__intermediate_delay__ = null;

                let mzk = 0;
                let myk = 0;
                let mlf = 0;
                let myw = 0;
                let mzt = 0;
                let zarr = null;

                if ((mclp.clearList || 0).length === 0) {
                    mclp.clearList66 = mclp.clearList;
                    mclp.clearList = function () {
                        mzk++;
                        myk++;
                        mlf++;
                        myw++;
                        mzt++;
                        zarr = null;
                        this.__intermediate_delay__ = null;
                        this.clearList66();
                    };
                }



                if ((mclp.showNewItems_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM) {


                    mclp.showNewItems66_ = mclp.showNewItems_;

                    mclp.showNewItems77_ = async function () {
                        if (myk > 1e9) myk = 9;
                        let tid = ++myk;

                        await new Promise(requestAnimationFrame);

                        if (tid !== myk) {
                            return;
                        }

                        const cnt = this;

                        await Promise.resolve();
                        cnt.showNewItems66_();

                        await Promise.resolve();

                    }

                    mclp.showNewItems_ = function () {

                        const cnt = this;
                        cnt.__intermediate_delay__ = new Promise(resolve => {
                            cnt.showNewItems77_().then(() => {
                                resolve();
                            });
                        });
                    }

                }




                if ((mclp.flushActiveItems_ || 0).length === 0) {


                    mclp.flushActiveItems66_ = mclp.flushActiveItems_;


                    mclp.flushActiveItems77_ = async function () {
                        try {

                            const cnt = this;
                            if (mlf > 1e9) mlf = 9;
                            let tid = ++mlf;
                            if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
                            if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;

                            // 4 times to maxItems to avoid frequent trimming.
                            // 1 ... 10 ... 20 ... 30 ... 40 ... 50 ... 60 => 16 ... 20 ... 30 ..... 60 ... => 16

                            this.activeItems_.length > this.data.maxItemsToDisplay * 4 && this.data.maxItemsToDisplay > 4 && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay - 1);
                            if (cnt.canScrollToBottom_()) {
                                let immd = cnt.__intermediate_delay__;
                                await new Promise(requestAnimationFrame);
                                if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
                                if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;

                                const oMaxItemsToDisplay = this.data.maxItemsToDisplay;
                                const reducedMaxItemsToDisplay = MAX_ITEMS_FOR_FULL_FLUSH;
                                let changeMaxItemsToDisplay = false;
                                if (ENABLE_REDUCED_MAXITEMS_FOR_FLUSH && this.activeItems_.length > this.data.maxItemsToDisplay) {
                                    if (this.data.maxItemsToDisplay > reducedMaxItemsToDisplay) {
                                        // as all the rendered chats are already "outdated"
                                        // all old chats shall remove and reduced number of few chats will be rendered
                                        // then restore to the original number
                                        changeMaxItemsToDisplay = true;
                                        this.data.maxItemsToDisplay = reducedMaxItemsToDisplay;
                                    }
                                    this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
                                    //   console.log('changeMaxItemsToDisplay 01', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
                                }

                                // it is found that it will render all stacked chats after switching back from background
                                // to avoid lagging in popular livestream with massive chats, trim first before rendering.
                                // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);



                                const items = (cnt.$ || 0).items;

                                if (USE_WILL_CHANGE_CONTROLLER) {
                                    if (contensWillChangeController && contensWillChangeController.element !== items) {
                                        contensWillChangeController.release();
                                        contensWillChangeController = null;
                                    }
                                    if (!contensWillChangeController) contensWillChangeController = new WillChangeController(items, 'contents');
                                }
                                const wcController = contensWillChangeController;
                                cnt.__intermediate_delay__ = Promise.all([cnt.__intermediate_delay__ || null, immd || null]);
                                wcController && wcController.beforeOper();
                                await Promise.resolve();
                                const len1 = cnt.activeItems_.length;
                                cnt.flushActiveItems66_();
                                const len2 = cnt.activeItems_.length;
                                let bAsync = len1 !== len2;
                                await Promise.resolve();
                                if (wcController) {
                                    if (bAsync) {
                                        cnt.async(() => {
                                            wcController.afterOper();
                                        });
                                    } else {
                                        wcController.afterOper();
                                    }
                                }
                                if (changeMaxItemsToDisplay) {
                                    if (this.data.maxItemsToDisplay === reducedMaxItemsToDisplay) {
                                        this.data.maxItemsToDisplay = oMaxItemsToDisplay;
                                        //   console.log('changeMaxItemsToDisplay 02', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
                                    }
                                }


                                if (!ENABLE_NO_SMOOTH_TRANSFORM) {


                                    const ff = () => {

                                        if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
                                        //   if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
                                        if (!cnt.atBottom && cnt.allowScroll && cnt.canScrollToBottomDLW_ && cnt.canScrollToBottomDLW_()) {
                                            cnt.scrollToBottom_();

                                            Promise.resolve().then(() => {

                                                if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
                                                if (!cnt.canScrollToBottom_()) cnt.scrollToBottom_();
                                            })


                                        }
                                    }

                                    ff();


                                    Promise.resolve().then(ff)

                                    // requestAnimationFrame(ff);
                                } else if (true) { // it might not be sticky to bottom when there is a full refresh.

                                    const knt = cnt;
                                    if (!scrollChatFn) {
                                        const cnt = knt;
                                        const f = () => {
                                            const itemScroller = cnt.itemScroller;
                                            if (!itemScroller || itemScroller.isConnected === false || cnt.isAttached === false) return;
                                            if (!cnt.atBottom) {
                                                cnt.scrollToBottom_();
                                            } else if (itemScroller.scrollTop === 0) { // not yet interacted by user; cannot stick to bottom
                                                itemScroller.scrollTop = itemScroller.scrollHeight;
                                            }
                                        };
                                        scrollChatFn = () => Promise.resolve().then(f).then(f);
                                    }

                                    if (!ENABLE_FULL_RENDER_REQUIRED) scrollChatFn();
                                }


                                return 1;
                            } else {
                                // cnt.flushActiveItems66_();
                                // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
                                return 2;
                            }
                        } catch (e) {
                            console.warn(e);
                        }
                    }

                    mclp.flushActiveItems_ = function () {
                        const cnt = this;

                        if (arguments.length !== 0 || !cnt.activeItems_ || !cnt.canScrollToBottom_) return cnt.flushActiveItems66_.apply(this, arguments);

                        if (cnt.activeItems_.length === 0) {
                            cnt.__intermediate_delay__ = null;
                            return;
                        }

                        const cntData = ((cnt || 0).data || 0);
                        if (cntData.maxItemsToDisplay > MAX_ITEMS_FOR_TOTAL_DISPLAY) cntData.maxItemsToDisplay = MAX_ITEMS_FOR_TOTAL_DISPLAY;

                        // ignore previous __intermediate_delay__ and create a new one
                        cnt.__intermediate_delay__ = new Promise(resolve => {
                            cnt.flushActiveItems77_().then(rt => {
                                if (rt === 1) resolve(1); // success, scroll to bottom
                                else if (rt === 2) resolve(2); // success, trim
                                else resolve(-1); // skip
                            });
                        });

                    }
                }

                if ((mclp.async || 0).length === 2) {


                    mclp.async66 = mclp.async;
                    mclp.async = function () {
                        // ensure the previous operation is done
                        // .async is usually after the time consuming functions like flushActiveItems_ and scrollToBottom_

                        const stack = new Error().stack;
                        const isFlushAsync = stack.indexOf('flushActiveItems_') >= 0;
                        (this.__intermediate_delay__ || Promise.resolve()).then(rk => {
                            if (isFlushAsync) {
                                if (rk < 0) return;
                                if (rk === 2 && arguments[0] === this.maybeScrollToBottom_) return;
                            }
                            this.async66.apply(this, arguments);
                        });

                    }

                }


                if ((mclp.onScrollItems_ || 0).length === 1) {

                    mclp.onScrollItems66_ = mclp.onScrollItems_;
                    mclp.onScrollItems77_ = async function (evt) {
                        if (myw > 1e9) myw = 9;
                        let tid = ++myw;

                        await new Promise(requestAnimationFrame);

                        if (tid !== myw) {
                            return;
                        }

                        const cnt = this;

                        await Promise.resolve();
                        if (USE_OPTIMIZED_ON_SCROLL_ITEMS) {
                            await Promise.resolve().then(() => {
                                this.ytRendererBehavior.onScroll(evt);
                            }).then(() => {
                                if (dateNow() - lastWheel < 80) {
                                    this.setAtBottom();
                                }
                            }).then(() => {
                                this.flushActiveItems_();
                            });
                        } else {
                            cnt.onScrollItems66_(evt);
                        }




                        await Promise.resolve();

                    }

                    mclp.onScrollItems_ = function (evt) {

                        if (Date.now() - lastAddition < 80) {
                            console.log(12345)
                            lastAddition = 0;
                            cnt.scrollToBottom_();
                        }

                        const cnt = this;
                        cnt.__intermediate_delay__ = new Promise(resolve => {
                            cnt.onScrollItems77_(evt).then(() => {
                                resolve();
                            });
                        });
                    }
                }

                if ((mclp.handleLiveChatActions_ || 0).length === 1) {
                    mclp.handleLiveChatActions66_ = mclp.handleLiveChatActions_;

                    mclp.handleLiveChatActions77_ = async function (arr) {
                        if (typeof (arr || 0).length !== 'number') {
                            this.handleLiveChatActions66_(arr);
                            return;
                        }
                        if (mzt > 1e9) mzt = 9;
                        let tid = ++mzt;

                        if (zarr === null) zarr = arr;
                        else Array.prototype.push.apply(zarr, arr);
                        arr = null;

                        await new Promise(requestAnimationFrame);

                        if (tid !== mzt || zarr === null) {
                            return;
                        }

                        const carr = zarr;
                        zarr = null;

                        await Promise.resolve();
                        this.handleLiveChatActions66_(carr);
                        await Promise.resolve();

                    }

                    mclp.handleLiveChatActions_ = function (arr) {

                        const cnt = this;
                        cnt.__intermediate_delay__ = new Promise(resolve => {
                            cnt.handleLiveChatActions77_(arr).then(() => {
                                resolve();
                            });
                        });
                    }

                }




            })

        });

        const getProto = (element) => {
            let proto = null;
            if (element) {
                if (element.inst) proto = element.inst.constructor.prototype;
                else proto = element.constructor.prototype;
            }
            return proto || null;
        }

        let done = 0;
        let main = async (q) => {
            if (done) return;

            if (!q) return;
            let m1 = nodeParent(q);
            let m2 = q;
            if (!(m1 && m1.id === 'item-offset' && m2 && m2.id === 'items')) return;

            done = 1;

            // setTimeout(()=>{
            //   document.documentElement.setAttribute('dont-render-enabled','')
            // },80)

            Promise.resolve().then(watchUserCSS);

            addCss();

            setupStyle(m1, m2);

            let lcRendererWR = null;

            const lcRendererElm = () => {
                let lcRenderer = kRef(lcRendererWR);
                if (!lcRenderer || !lcRenderer.isConnected) {
                    lcRenderer = document.querySelector('yt-live-chat-item-list-renderer.yt-live-chat-renderer');
                    lcRendererWR = lcRenderer ? mWeakRef(lcRenderer) : null;
                }
                return lcRenderer
            };

            let hasFirstShowMore = false;

            const visObserverFn = (entry) => {

                const target = entry.target;
                if (!target) return;
                // if(target.classList.contains('dont-render')) return;
                let isVisible = entry.isIntersecting === true && entry.intersectionRatio > 0.5;
                // const h = entry.boundingClientRect.height;
                /*
                if (h < 16) { // wrong: 8 (padding/margin); standard: 32; test: 16 or 20
                    // e.g. under fullscreen. the element created but not rendered.
                    target.setAttribute('wSr93', '');
                    return;
                }
                */
                if (isVisible) {
                    // target.style.setProperty('--wsr94', h + 'px');
                    target.setAttribute('wSr93', 'visible');
                    if (nNextElem(target) === null) {

                        // firstVisibleItemDetected = true;
                        /*
                          if (dateNow() - lastScroll < 80) {
                              lastLShow = 0;
                              lastScroll = 0;
                              Promise.resolve().then(clickShowMore);
                          } else {
                              lastLShow = dateNow();
                          }
                          */
                        // lastLShow = dateNow();
                    } else if (!hasFirstShowMore) { // should more than one item being visible
                        // implement inside visObserver to ensure there is sufficient delay
                        hasFirstShowMore = true;
                        requestAnimationFrame(() => {
                            // foreground page
                            // page visibly ready -> load the latest comments at initial loading
                            const lcRenderer = lcRendererElm();
                            if (lcRenderer) {
                                (lcRenderer.inst || lcRenderer).scrollToBottom_();
                            }
                        });
                    }
                }
                else if (target.getAttribute('wSr93') === 'visible') { // ignore target.getAttribute('wSr93') === '' to avoid wrong sizing

                    // target.style.setProperty('--wsr94', h + 'px');
                    target.setAttribute('wSr93', 'hidden');
                } // note: might consider 0 < entry.intersectionRatio < 0.5 and target.getAttribute('wSr93') === '' <new last item>

            }

            const visObserver = new IntersectionObserver((entries) => {

                for (const entry of entries) {

                    Promise.resolve(entry).then(visObserverFn);

                }

            }, {
                // root: HTMLElement.prototype.closest.call(m2, '#item-scroller.yt-live-chat-item-list-renderer'), // nullable
                rootMargin: "0px",
                threshold: [0.05, 0.95],
            });

            //m2.style.visibility='';

            const mutFn = (items) => {
                for (let node = nLastElem(items); node !== null; node = nPrevElem(node)) {
                    if (node.hasAttribute('wSr93')) break;
                    node.setAttribute('wSr93', '');
                    visObserver.observe(node);
                }
            }

            const mutObserver = new MutationObserver((mutations) => {
                const items = (mutations[0] || 0).target;
                if (!items) return;
                mutFn(items);
            });

            const setupMutObserver = (m2) => {
                scrollChatFn = null;
                mutObserver.disconnect();
                mutObserver.takeRecords();
                if (m2) {
                    if (typeof m2.__appendChild932__ === 'function') {
                        if (typeof m2.appendChild === 'function') m2.appendChild = m2.__appendChild932__;
                        if (typeof m2.__shady_native_appendChild === 'function') m2.__shady_native_appendChild = m2.__appendChild932__;
                    }
                    mutObserver.observe(m2, {
                        childList: true,
                        subtree: false
                    });
                    mutFn(m2);


                    if (ENABLE_OVERFLOW_ANCHOR) {

                        let items = m2;
                        let addedAnchor = false;
                        if (items) {
                            if (items.nextElementSibling === null) {
                                items.classList.add('no-anchor');
                                addedAnchor = true;
                                items.parentNode.appendChild(dr(document.createElement('item-anchor')));
                            }
                        }



                        if (addedAnchor) {
                            nodeParent(m2).classList.add('no-anchor'); // required
                        }

                    }

                    // let div = document.createElement('div');
                    // div.id = 'qwcc';
                    // HTMLElement.prototype.appendChild.call(document.querySelector('yt-live-chat-item-list-renderer'), div )
                    // bufferRegion =div;

                    // buffObserver.takeRecords();
                    // buffObserver.disconnect();
                    // buffObserver.observe(div,  {
                    //     childList: true,
                    //     subtree: false
                    // })



                }
            }

            setupMutObserver(m2);

            const mclp = getProto(document.querySelector('yt-live-chat-item-list-renderer'));
            if (mclp && mclp.attached) {

                mclp.attached66 = mclp.attached;
                mclp.attached = function () {
                    let m2 = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer');
                    let m1 = nodeParent(m2);
                    setupStyle(m1, m2);
                    setupMutObserver(m2);
                    return this.attached66();
                }

                mclp.detached66 = mclp.detached;
                mclp.detached = function () {
                    setupMutObserver();
                    return this.detached66();
                }

                mclp.canScrollToBottomDLW_ = () => !(dateNow() - lastWheel < 80);

                mclp.canScrollToBottom_ = function () {
                    return this.atBottom && this.allowScroll && this.canScrollToBottomDLW_();
                }

                if (ENABLE_NO_SMOOTH_TRANSFORM) {


                    mclp.isSmoothScrollEnabled_ = function () {
                        return false;
                    }



                    mclp.maybeResizeScrollContainer_ = function () {
                        //
                    }

                    mclp.refreshOffsetContainerHeight_ = function () {
                        //
                    }

                    mclp.smoothScroll_ = function () {
                        //
                    }

                    mclp.resetSmoothScroll_ = function () {
                        //
                    }
                }

            } else {
                console.warn(`proto.attached for yt-live-chat-item-list-renderer is unavailable.`)
            }


            let scrollCount = 0;
            document.addEventListener('scroll', (evt) => {
                if (!evt || !evt.isTrusted) return;
                // lastScroll = dateNow();
                if (++scrollCount > 1e9) scrollCount = 9;
            }, { passive: true, capture: true }); // support contain => support passive

            let lastScrollCount = -1;
            document.addEventListener('wheel', (evt) => {

                if (!evt || !evt.isTrusted) return;
                if (lastScrollCount === scrollCount) return;
                lastScrollCount = scrollCount;
                lastWheel = dateNow();

            }, { passive: true, capture: true }); // support contain => support passive


            const fp = (renderer) => {
                const cnt = renderer.inst || renderer;
                const container = (cnt.$ || 0).container;
                if (container) {
                    container.setAttribute = tickerContainerSetAttribute;
                }
            };
            const tags = ["yt-live-chat-ticker-paid-message-item-renderer", "yt-live-chat-ticker-paid-sticker-item-renderer",
                "yt-live-chat-ticker-renderer", "yt-live-chat-ticker-sponsor-item-renderer"];
            for (const tag of tags) {
                const dummy = document.createElement(tag);

                const cProto = getProto(dummy);
                if (!cProto || !cProto.attached) {
                    console.warn(`proto.attached for ${tag} is unavailable.`)
                    continue;
                }

                const __updateTimeout__ = cProto.updateTimeout;

                const canDoUpdateTimeoutReplacement = (() => {

                    if (dummy.countdownMs < 1 && dummy.lastCountdownTimeMs < 1 && dummy.countdownMs < 1 && dummy.countdownDurationMs < 1) {
                        return typeof dummy.setContainerWidth === 'function' && typeof dummy.slideDown === 'function';
                    }
                    return false;

                })(dummy.inst || dummy) && ((__updateTimeout__ + "").indexOf("window.requestAnimationFrame(this.updateTimeout.bind(this))") > 0);



                if (canDoUpdateTimeoutReplacement) {

                    const killTicker = (cnt) => {
                        if ("auto" === cnt.hostElement.style.width) cnt.setContainerWidth();
                        cnt.slideDown()
                    };

                    cProto.__ratio__ = null;
                    cProto._updateTimeout21_ = function (a) {

                        this.countdownMs -= (a - (this.lastCountdownTimeMs || 0));

                        let currentRatio = this.__ratio__;
                        let tdv = this.countdownMs / this.countdownDurationMs;
                        let nextRatio = Math.round(tdv * 500) / 500; // might generate 0.143000000001

                        const validCountDown = nextRatio > 0;
                        const isAttached = this.isAttached;

                        if (!validCountDown) {

                            this.lastCountdownTimeMs = null;

                            this.countdownMs = 0;
                            this.__ratio__ = null;
                            this.ratio = 0;

                            if (isAttached) Promise.resolve(this).then(killTicker);

                        } else if (!isAttached) {

                            this.lastCountdownTimeMs = null;

                        } else {

                            this.lastCountdownTimeMs = a;

                            const ratioDiff = currentRatio - nextRatio;  // 0.144 - 0.142 = 0.002
                            if (ratioDiff < 0.001 && ratioDiff > -1e-6) {
                                // ratioDiff = 0

                            } else {
                                // ratioDiff = 0.002 / 0.004 ....
                                // OR ratioDiff < 0

                                this.__ratio__ = nextRatio;

                                this.ratio = nextRatio;
                            }

                            return true;
                        }

                    };

                    cProto._updateTimeout21_ = function (a) {
                        this.countdownMs = Math.max(0, this.countdownMs - (a - (this.lastCountdownTimeMs || 0)));
                        this.ratio = this.countdownMs / this.countdownDurationMs;
                        if (this.isAttached && this.countdownMs) {
                            this.lastCountdownTimeMs = a;
                            return true;
                        } else {
                            this.lastCountdownTimeMs = null;
                            if (this.isAttached) {
                                ("auto" === this.hostElement.style.width && this.setContainerWidth(), this.slideDown())
                            }
                        }
                    }


                }

                cProto.attached77 = cProto.attached

                cProto.attached = function () {
                    fp(this.hostElement || this);
                    return this.attached77();
                }

                for (const elm of document.getElementsByTagName(tag)) {
                    fp(elm);
                }


            }

        };


        function onReady() {
            let tmObserver = new MutationObserver(() => {

                let p = document.getElementById('items'); // fast
                if (!p) return;
                let q = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer'); // check

                if (q) {
                    tmObserver.disconnect();
                    tmObserver.takeRecords();
                    tmObserver = null;
                    Promise.resolve(q).then((q) => {
                        // confirm Promis.resolve() is resolveable
                        // execute main without direct blocking
                        main(q);
                    })
                }

            });

            tmObserver.observe(document.body || document.documentElement, {
                childList: true,
                subtree: true
            });

        }

        Promise.resolve().then(() => {

            if (document.readyState !== 'loading') {
                onReady();
            } else {
                window.addEventListener("DOMContentLoaded", onReady, false);
            }

        });

    });

})({ IntersectionObserver });