YouTube 超快聊天

YouTube直播聊天的终极性能提升

当前为 2023-07-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Super Fast Chat
  3. // @version 0.9.4
  4. // @license MIT
  5. // @name:ja YouTube スーパーファーストチャット
  6. // @name:zh-TW YouTube 超快聊天
  7. // @name:zh-CN YouTube 超快聊天
  8. // @namespace UserScript
  9. // @match https://www.youtube.com/live_chat*
  10. // @author CY Fung
  11. // @require https://greasyfork.org/scripts/465819-api-for-customelements-in-youtube/code/API%20for%20CustomElements%20in%20YouTube.js?version=1215280
  12. // @run-at document-start
  13. // @grant none
  14. // @unwrap
  15. // @allFrames true
  16. // @inject-into page
  17. //
  18. // @description Ultimate Performance Boost for YouTube Live Chats
  19. // @description:ja YouTubeのライブチャットの究極のパフォーマンスブースト
  20. // @description:zh-TW YouTube直播聊天的終極性能提升
  21. // @description:zh-CN YouTube直播聊天的终极性能提升
  22. //
  23. // ==/UserScript==
  24.  
  25. ((__CONTEXT__) => {
  26.  
  27. const ENABLE_REDUCED_MAXITEMS_FOR_FLUSH = true;
  28. const MAX_ITEMS_FOR_TOTAL_DISPLAY = 90;
  29. const MAX_ITEMS_FOR_FULL_FLUSH = 25;
  30.  
  31. const ENABLE_NO_SMOOTH_TRANSFORM = true;
  32. // const ENABLE_CONTENT_HIDDEN = false;
  33. let ENABLE_FULL_RENDER_REQUIRED = false; // Chrome Only; Firefox Excluded
  34. const USE_OPTIMIZED_ON_SCROLL_ITEMS = true;
  35. const USE_WILL_CHANGE_CONTROLLER = false;
  36. const MODIFY_SCROLL_TO_BOTTOM = false; // NOT REQUIRED in the latest version
  37.  
  38.  
  39. let cssText1 = '';
  40. let cssText2 = '';
  41. let cssText3 = '';
  42. let cssText4 = '';
  43. let cssText5 = '';
  44. let cssText6 = '';
  45. let cssText7 = '';
  46.  
  47. function dr(s) {
  48. // reserved for future use
  49. return s;
  50. // return window.deWeakJS ? window.deWeakJS(s) : s;
  51. }
  52.  
  53. // let cssText2b= '';
  54.  
  55. /*
  56. if(ENABLE_CONTENT_HIDDEN){
  57.  
  58. cssText1 = `
  59.  
  60. [wSr93="hidden"]:nth-last-child(n+4) {
  61. --wsr93-content-visibility: auto;
  62. contain-intrinsic-size: auto var(--wsr94);
  63. }
  64. `;
  65.  
  66. cssText2b = `
  67.  
  68. [wSr93="hidden"] { /|* initial->[wSr93]->[wSr93="visible"]->[wSr93="hidden"] => reliable rendered height *|/
  69. --wsr93-contain: size layout style;
  70. height: var(--wsr94);
  71. }
  72.  
  73. `;
  74.  
  75. }
  76. */
  77.  
  78. /*
  79. if (1) {
  80. cssText2 = `
  81. [wSr93] {
  82. --wsr93-contain: layout style;
  83. contain: var(--wsr93-contain, unset) !important;
  84. box-sizing: border-box !important;
  85. content-visibility: var(--wsr93-content-visibility, visible);
  86. }
  87. ${cssText2b}
  88. `;
  89. }
  90. */
  91.  
  92. if (ENABLE_NO_SMOOTH_TRANSFORM) {
  93.  
  94. cssText3 = `
  95.  
  96. #item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer {
  97. position: static !important;
  98. }
  99. `
  100. cssText4 =
  101.  
  102.  
  103. `
  104.  
  105.  
  106. /* optional */
  107. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  108. height: auto !important;
  109. min-height: unset !important;
  110. }
  111.  
  112. #items.style-scope.yt-live-chat-item-list-renderer {
  113. transform: translateY(0px) !important;
  114. }
  115.  
  116. /* optional */
  117.  
  118. `
  119. }
  120.  
  121. if (1) {
  122. cssText5 = `
  123.  
  124.  
  125.  
  126. /* ------------------------------------------------------------------------------------------------------------- */
  127.  
  128. 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 {
  129. contain: layout style;
  130. }
  131.  
  132. body yt-live-chat-app {
  133. contain: size layout paint style;
  134. overflow: hidden;
  135. }
  136.  
  137. #items.style-scope.yt-live-chat-item-list-renderer {
  138. contain: layout paint style;
  139. }
  140.  
  141. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  142. contain: style;
  143. }
  144.  
  145. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  146. contain: size style;
  147. }
  148.  
  149. #contents.style-scope.yt-live-chat-item-list-renderer, #chat.style-scope.yt-live-chat-renderer, img.style-scope.yt-img-shadow[width][height] {
  150. contain: size layout paint style;
  151. }
  152.  
  153. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label], .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label] > #container {
  154. contain: layout paint style;
  155. }
  156.  
  157. 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 {
  158. contain: layout style;
  159. }
  160.  
  161. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  162. contain: layout paint style;
  163. }
  164.  
  165. /* ------------------------------------------------------------------------------------------------------------- */
  166.  
  167. `
  168. }
  169.  
  170. if (1) {
  171.  
  172. cssText6 = `
  173.  
  174.  
  175. yt-icon[icon="down_arrow"] > *, yt-icon-button#show-more > * {
  176. pointer-events: none !important;
  177. }
  178.  
  179. #continuations, #continuations * {
  180. contain: strict;
  181. position: fixed;
  182. top: 2px;
  183. height: 1px;
  184. width: 2px;
  185. height: 1px;
  186. visibility: collapse;
  187. }
  188.  
  189. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer{
  190. top: 4px;
  191. transition-property: top;
  192. bottom: unset;
  193. }
  194.  
  195. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer[disabled]{
  196. top: -42px;
  197. }
  198.  
  199. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer:not(:empty) {
  200. --yt-live-chat-action-panel-top-border: none;
  201. }
  202.  
  203. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer.iron-selected > *:first-child {
  204. border-top: 1px solid var(--yt-live-chat-panel-pages-border-color);
  205. }
  206.  
  207. html #panel-pages.yt-live-chat-renderer {
  208. border-top: 0;
  209. border-bottom: 0;
  210. }
  211.  
  212. #input-panel #picker-buttons yt-live-chat-icon-toggle-button-renderer#product-picker {
  213. /*
  214. overflow: hidden;
  215. contain: layout paint style;
  216. */
  217. contain: layout style;
  218. }
  219.  
  220. #chat.yt-live-chat-renderer ~ #panel-pages.yt-live-chat-renderer {
  221. overflow: visible;
  222. }
  223.  
  224.  
  225. `
  226. }
  227.  
  228. if (1) {
  229. cssText7 = `
  230.  
  231.  
  232. .ytp-contextmenu[class],
  233. .toggle-button.tp-yt-paper-toggle-button[class],
  234. .yt-spec-touch-feedback-shape__fill[class],
  235. .fill.yt-interaction[class],
  236. .ytp-videowall-still-info-content[class],
  237. .ytp-suggestion-image[class] {
  238. will-change: unset !important;
  239. }
  240.  
  241. img {
  242. content-visibility: visible !important;
  243. }
  244.  
  245. yt-img-shadow[height][width],
  246. yt-img-shadow {
  247. content-visibility: visible !important;
  248. }
  249.  
  250. yt-live-chat-item-list-renderer:not([allow-scroll]) #item-scroller.yt-live-chat-item-list-renderer {
  251. overflow-y: scroll;
  252. padding-right: 0;
  253. }
  254.  
  255. `
  256. }
  257.  
  258. function addCssElement() {
  259. let s = document.createElement('style')
  260. s.id = 'ewRvC';
  261. return s;
  262. }
  263.  
  264. const addCss = () => document.head.appendChild(dr(addCssElement())).textContent = `
  265.  
  266.  
  267. @supports (contain:layout paint style) and (content-visibility:auto) and (contain-intrinsic-size:auto var(--wsr94)) {
  268.  
  269. ${cssText1}
  270. }
  271.  
  272. @supports (contain:layout paint style) {
  273.  
  274. ${cssText2}
  275.  
  276.  
  277. ${cssText5}
  278.  
  279. }
  280.  
  281. @supports (color: var(--general)) {
  282.  
  283. ${cssText3}
  284.  
  285. ${cssText7}
  286.  
  287.  
  288. ${cssText4}
  289.  
  290. ${cssText6}
  291.  
  292. .no-anchor * {
  293. overflow-anchor: none;
  294. }
  295. .no-anchor > item-anchor {
  296. overflow-anchor: auto;
  297. }
  298.  
  299. item-anchor {
  300.  
  301. height:1px;
  302. width: 100%;
  303. transform: scaleY(0.00001);
  304. transform-origin:0 0;
  305. contain: strict;
  306. opacity:0;
  307. display:flex;
  308. position:relative;
  309. flex-shrink:0;
  310. flex-grow:0;
  311. margin-bottom:0;
  312. overflow:hidden;
  313. box-sizing:border-box;
  314. visibility: visible;
  315. content-visibility: visible;
  316. contain-intrinsic-size: auto 1px;
  317. pointer-events:none !important;
  318.  
  319. }
  320.  
  321. #item-scroller.style-scope.yt-live-chat-item-list-renderer[class] {
  322. overflow-anchor: initial !important;
  323. }
  324.  
  325. html item-anchor {
  326.  
  327. height: 1px;
  328. width: 1px;
  329. top:auto;
  330. left:auto;
  331. right:auto;
  332. bottom:auto;
  333. transform: translateY(-1px);
  334. position: absolute;
  335. z-index:-1;
  336.  
  337. }
  338.  
  339.  
  340. /*
  341. #qwcc{
  342. position:fixed;
  343. top:0;
  344. bottom:0;
  345. left:0;
  346. right:0;
  347. contain: strict;
  348. visibility: collapse;
  349. z-index:-1;
  350. }
  351. */
  352.  
  353.  
  354. /*
  355. .dont-render{
  356. position: absolute !important;
  357. visibility: collapse !important;
  358. z-index:-1 !important;
  359. width:auto !important;
  360. height:auto !important;
  361. contain: none !important;
  362. box-sizing: border-box !important;
  363.  
  364. }
  365. */
  366.  
  367.  
  368.  
  369. @keyframes dontRenderAnimation {
  370. 0% {
  371. background-position-x: 3px;
  372. }
  373. 100% {
  374. background-position-x: 4px;
  375. }
  376. }
  377.  
  378. /*html[dont-render-enabled] */ .dont-render{
  379. visibility: collapse !important;
  380. transform: scale(0.01) !important;
  381. transform: scale(0.00001) !important;
  382. transform: scale(0.0000001) !important;
  383. transform-origin:0 0 !important;
  384. z-index:-1 !important;
  385. contain: strict !important;
  386. box-sizing: border-box !important;
  387.  
  388. height:1px !important;
  389. height:0.1px !important;
  390. height:0.01px !important;
  391. height:0.0001px !important;
  392. height:0.000001px !important;
  393.  
  394.  
  395. animation: dontRenderAnimation 1ms linear 80ms 1 normal forwards !important;
  396.  
  397. }
  398.  
  399.  
  400.  
  401. }
  402.  
  403. `;
  404.  
  405. const { Promise, requestAnimationFrame, IntersectionObserver } = __CONTEXT__;
  406.  
  407. if (!IntersectionObserver) return console.error("Your browser does not support IntersectionObserver.\nPlease upgrade to the latest version.")
  408.  
  409. const isContainSupport = CSS.supports('contain', 'layout paint style');
  410. if (!isContainSupport) {
  411. console.warn("Your browser does not support css property 'contain'.\nPlease upgrade to the latest version.".trim());
  412. } else {
  413.  
  414. ENABLE_FULL_RENDER_REQUIRED = true; // Chromium-based browsers
  415.  
  416. }
  417.  
  418.  
  419. // let bufferRegion = null;
  420. // let listOfDom = [];
  421.  
  422.  
  423. ENABLE_FULL_RENDER_REQUIRED && document.addEventListener('animationstart', (evt) => {
  424.  
  425. if (evt.animationName === 'dontRenderAnimation') {
  426. evt.target.classList.remove('dont-render');
  427. }
  428.  
  429. }, true);
  430.  
  431. ENABLE_FULL_RENDER_REQUIRED && ((appendChild) => {
  432.  
  433. const f = (elm) => {
  434. if (elm && elm.nodeType === 1) {
  435. elm.classList.add('dont-render');
  436. }
  437. }
  438.  
  439. Node.prototype.appendChild = function (a) {
  440. a = dr(a);
  441.  
  442.  
  443. if (this.id === 'items' && this.classList.contains('yt-live-chat-item-list-renderer')) {
  444. // if(this.matches('.style-scope.yt-live-chat-item-list-renderer')){
  445.  
  446.  
  447. // let elms = [];
  448. // if(a.nodeType ===1) elms.push(a);
  449. // else if(a instanceof DocumentFragment ){
  450.  
  451. // for(let n = a.firstChild; n; n=n.nextSibling){
  452. // elms.push(n);
  453. // }
  454.  
  455. // }
  456.  
  457. // for(const elm of elms){
  458.  
  459.  
  460. // if(elm && elm.nodeType ===1){
  461.  
  462. // /*
  463.  
  464. // let placeholder = document.createElement('dom-placeholder');
  465.  
  466.  
  467. // placeholder.descTo = elm;
  468. // elm.placeHolderAs = placeholder;
  469. // appendChild.call(bufferRegion, elm);
  470. // return appendChild.call(this, placeholder);
  471.  
  472. // */
  473.  
  474. // elm.classList.add('dont-render');
  475. // // // listOfDom.push(elm);
  476. // // Promise.resolve(elm).then((elm)=>{
  477.  
  478. // // setTimeout(()=>{
  479.  
  480.  
  481. // // elm.classList.remove('dont-render');
  482. // // }, 80);
  483. // // });
  484.  
  485.  
  486.  
  487.  
  488. // }
  489.  
  490. // }
  491.  
  492.  
  493.  
  494. if (a && a.nodeType === 1) f(a);
  495. else if (a instanceof DocumentFragment) {
  496.  
  497. for (let n = a.firstChild; n; n = n.nextSibling) {
  498. f(n);
  499. }
  500.  
  501. }
  502.  
  503.  
  504. return appendChild.call(dr(this), a);
  505.  
  506. // }
  507.  
  508. }
  509. // console.log(11,this)
  510. return appendChild.call(dr(this), a)
  511.  
  512. }
  513.  
  514. })(Node.prototype.appendChild);
  515.  
  516.  
  517. ((appendChild) => {
  518.  
  519. DocumentFragment.prototype.appendChild = function (a) {
  520. a = dr(a)
  521.  
  522. // console.log(22,this)
  523. return appendChild.call(dr(this), a)
  524.  
  525. }
  526.  
  527. })(DocumentFragment.prototype.appendChild)
  528.  
  529. // const APPLY_delayAppendChild = false;
  530.  
  531. // let activeDeferredAppendChild = false; // deprecated
  532.  
  533. // let delayedAppendParentWS = new WeakSet();
  534. // let delayedAppendOperations = [];
  535. // let commonAppendParentStackSet = new Set();
  536.  
  537. // let firstVisibleItemDetected = false; // deprecated
  538.  
  539. const sp7 = Symbol();
  540.  
  541.  
  542. let dt0 = Date.now() - 2000;
  543. const dateNow = () => Date.now() - dt0;
  544. // let lastScroll = 0;
  545. // let lastLShow = 0;
  546. let lastWheel = 0;
  547.  
  548. const proxyHelperFn = (dummy) => ({
  549.  
  550. get(target, prop) {
  551. return (prop in dummy) ? dummy[prop] : prop === sp7 ? target : target[prop];
  552. },
  553. set(target, prop, value) {
  554. if (!(prop in dummy)) {
  555. target[prop] = value;
  556. }
  557. return true;
  558. },
  559. has(target, prop) {
  560. return (prop in target)
  561. },
  562. deleteProperty(target, prop) {
  563. return true;
  564. },
  565. ownKeys(target) {
  566. return Object.keys(target);
  567. },
  568. defineProperty(target, key, descriptor) {
  569. return Object.defineProperty(target, key, descriptor);
  570. },
  571. getOwnPropertyDescriptor(target, key) {
  572. return Object.getOwnPropertyDescriptor(target, key);
  573. },
  574.  
  575. });
  576.  
  577. const tickerContainerSetAttribute = function (attrName, attrValue) { // ensure '14.30000001%'.toFixed(1)
  578.  
  579. let yd = (this.__dataHost || (this.inst || 0).__dataHost).__data;
  580.  
  581. if (arguments.length === 2 && attrName === 'style' && yd && attrValue) {
  582.  
  583. // let v = yd.containerStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
  584. let v = `${attrValue}`;
  585. // conside a ticker is 101px width
  586. // 1% = 1.01px
  587. // 0.2% = 0.202px
  588.  
  589.  
  590. const ratio1 = (yd.ratio * 100);
  591. if (ratio1 > -1) { // avoid NaN
  592.  
  593. // countdownDurationMs
  594. // 600000 - 0.2% <1% = 6s> <0.2% = 1.2s>
  595. // 300000 - 0.5% <1% = 3s> <0.5% = 1.5s>
  596. // 150000 - 1% <1% = 1.5s>
  597. // 75000 - 2% <1% =0.75s > <2% = 1.5s>
  598. // 30000 - 5% <1% =0.3s > <5% = 1.5s>
  599.  
  600. // 99px * 5% = 4.95px
  601.  
  602. // 15000 - 10% <1% =0.15s > <10% = 1.5s>
  603.  
  604.  
  605.  
  606.  
  607. // 1% Duration
  608.  
  609. let ratio2 = ratio1;
  610.  
  611. const ydd = yd.data;
  612. const d1 = ydd.durationSec;
  613. const d2 = ydd.fullDurationSec;
  614.  
  615. if (d1 === d2 && d1 > 1) {
  616.  
  617. if (d1 > 400) ratio2 = Math.round(ratio2 * 5) / 5; // 0.2%
  618. else if (d1 > 200) ratio2 = Math.round(ratio2 * 2) / 2; // 0.5%
  619. else if (d1 > 100) ratio2 = Math.round(ratio2 * 1) / 1; // 1%
  620. else if (d1 > 50) ratio2 = Math.round(ratio2 * 0.5) / 0.5; // 2%
  621. else if (d1 > 25) ratio2 = Math.round(ratio2 * 0.2) / 0.2; // 5% (max => 99px * 5% = 4.95px)
  622. else ratio2 = Math.round(ratio2 * 0.2) / 0.2;
  623.  
  624. } else {
  625. ratio2 = Math.round(ratio2 * 5) / 5; // 0.2% (min)
  626. }
  627.  
  628. // ratio2 = Math.round(ratio2 * 5) / 5;
  629. ratio2 = ratio2.toFixed(1)
  630. v = v.replace(`${ratio1}%`, `${ratio2}%`).replace(`${ratio1}%`, `${ratio2}%`)
  631.  
  632. if (yd.__style_last__ === v) return;
  633. yd.__style_last__ = v;
  634. // do not consider any delay here.
  635. // it shall be inside the looping for all properties changes. all the css background ops are in the same microtask.
  636.  
  637. }
  638.  
  639. HTMLElement.prototype.setAttribute.call(dr(this), attrName, v);
  640.  
  641.  
  642. } else {
  643. HTMLElement.prototype.setAttribute.apply(dr(this), arguments);
  644. }
  645.  
  646. };
  647.  
  648. const fxOperator = (proto, propertyName) => {
  649. let propertyDescriptorGetter = null;
  650. try {
  651. propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
  652. } catch (e) { }
  653. return typeof propertyDescriptorGetter === 'function' ? (e) => {
  654. try {
  655.  
  656. return propertyDescriptorGetter.call(dr(e))
  657. } catch (e) { }
  658. return e[propertyName];
  659. } : (e) => e[propertyName];
  660. };
  661.  
  662. const nodeParent = fxOperator(Node.prototype, 'parentNode');
  663. // const nFirstElem = fxOperator(HTMLElement.prototype, 'firstElementChild');
  664. const nPrevElem = fxOperator(HTMLElement.prototype, 'previousElementSibling');
  665. const nNextElem = fxOperator(HTMLElement.prototype, 'nextElementSibling');
  666. const nLastElem = fxOperator(HTMLElement.prototype, 'lastElementChild');
  667.  
  668.  
  669. /* globals WeakRef:false */
  670.  
  671. /** @type {(o: Object | null) => WeakRef | null} */
  672. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  673.  
  674. /** @type {(wr: Object | null) => Object | null} */
  675. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  676.  
  677. const watchUserCSS = () => {
  678.  
  679. // if (!CSS.supports('contain-intrinsic-size', 'auto var(--wsr94)')) return;
  680.  
  681. const getElemFromWR = (nr) => {
  682. const n = kRef(nr);
  683. if (n && n.isConnected) return n;
  684. return null;
  685. }
  686.  
  687. const clearContentVisibilitySizing = () => {
  688. Promise.resolve().then(() => {
  689.  
  690. let btnShowMoreWR = mWeakRef(document.querySelector('#show-more[disabled]'));
  691.  
  692. let lastVisibleItemWR = null;
  693. for (const elm of document.querySelectorAll('[wSr93]')) {
  694. if (elm.getAttribute('wSr93') === 'visible') lastVisibleItemWR = mWeakRef(elm);
  695. elm.setAttribute('wSr93', '');
  696. // custom CSS property --wsr94 not working when attribute wSr93 removed
  697. }
  698. requestAnimationFrame(() => {
  699. const btnShowMore = getElemFromWR(btnShowMoreWR); btnShowMoreWR = null;
  700. if (btnShowMore) btnShowMore.click();
  701. else {
  702. // would not work if switch it frequently
  703. const lastVisibleItem = getElemFromWR(lastVisibleItemWR); lastVisibleItemWR = null;
  704. if (lastVisibleItem) {
  705.  
  706. Promise.resolve()
  707. .then(() => lastVisibleItem.scrollIntoView())
  708. .then(() => lastVisibleItem.scrollIntoView(false))
  709. .then(() => lastVisibleItem.scrollIntoView({ behavior: "instant", block: "end", inline: "nearest" }))
  710. .catch(e => { }) // break the chain when method not callable
  711.  
  712. }
  713. }
  714. })
  715.  
  716. })
  717.  
  718. }
  719.  
  720. const mutObserver = new MutationObserver((mutations) => {
  721. for (const mutation of mutations) {
  722. if ((mutation.addedNodes || 0).length >= 1) {
  723. for (const addedNode of mutation.addedNodes) {
  724. if (addedNode.nodeName === 'STYLE') {
  725. clearContentVisibilitySizing();
  726. return;
  727. }
  728. }
  729. }
  730. if ((mutation.removedNodes || 0).length >= 1) {
  731. for (const removedNode of mutation.removedNodes) {
  732. if (removedNode.nodeName === 'STYLE') {
  733. clearContentVisibilitySizing();
  734. return;
  735. }
  736. }
  737. }
  738. }
  739. });
  740.  
  741. mutObserver.observe(document.documentElement, {
  742. childList: true,
  743. subtree: false
  744. })
  745.  
  746. mutObserver.observe(document.head, {
  747. childList: true,
  748. subtree: false
  749. })
  750. mutObserver.observe(document.body, {
  751. childList: true,
  752. subtree: false
  753. });
  754.  
  755. }
  756.  
  757. const setupStyle = (m1, m2) => {
  758. if (!ENABLE_NO_SMOOTH_TRANSFORM) return;
  759.  
  760. const dummy1v = {
  761. transform: '',
  762. height: '',
  763. minHeight: '',
  764. paddingBottom: '',
  765. paddingTop: ''
  766. };
  767.  
  768. for (const k of ['toString', 'getPropertyPriority', 'getPropertyValue', 'item', 'removeProperty', 'setProperty']) {
  769. dummy1v[k] = ((k) => (function () { const style = this[sp7]; return style[k](...arguments); }))(k)
  770. }
  771.  
  772. const dummy1p = proxyHelperFn(dummy1v);
  773. const sp1v = new Proxy(m1.style, dummy1p);
  774. const sp2v = new Proxy(m2.style, dummy1p);
  775. Object.defineProperty(m1, 'style', { get() { return sp1v }, set() { }, enumerable: true, configurable: true });
  776. Object.defineProperty(m2, 'style', { get() { return sp2v }, set() { }, enumerable: true, configurable: true });
  777. m1.removeAttribute("style");
  778. m2.removeAttribute("style");
  779.  
  780. }
  781.  
  782.  
  783. class WillChangeController {
  784. constructor(itemScroller, willChangeValue) {
  785. this.element = itemScroller;
  786. this.counter = 0;
  787. this.active = false;
  788. this.willChangeValue = willChangeValue;
  789. }
  790.  
  791. beforeOper() {
  792. if (!this.active) {
  793. this.active = true;
  794. this.element.style.willChange = this.willChangeValue;
  795. }
  796. this.counter++;
  797. }
  798.  
  799. afterOper() {
  800. const c = this.counter;
  801. requestAnimationFrame(() => {
  802. if (c === this.counter) {
  803. this.active = false;
  804. this.element.style.willChange = '';
  805. }
  806. })
  807. }
  808.  
  809. release() {
  810. const element = this.element;
  811. this.element = null;
  812. this.counter = 1e16;
  813. this.active = false;
  814. try {
  815. element.style.willChange = '';
  816. } catch (e) { }
  817. }
  818.  
  819. }
  820.  
  821. customYtElements.onRegistryReady(() => {
  822.  
  823.  
  824. let scrollWillChangeController = null;
  825. let contensWillChangeController = null;
  826.  
  827. // as it links to event handling, it has to be injected using immediateCallback
  828. customYtElements.whenRegistered('yt-live-chat-item-list-renderer', (cProto) => {
  829.  
  830.  
  831. const mclp = cProto;
  832. console.assert(typeof mclp.scrollToBottom_ === 'function')
  833. console.assert(typeof mclp.scrollToBottom66_ !== 'function')
  834. console.assert(typeof mclp.flushActiveItems_ === 'function')
  835. console.assert(typeof mclp.flushActiveItems66_ !== 'function')
  836. console.assert(typeof mclp.async === 'function')
  837.  
  838.  
  839. mclp.__intermediate_delay__ = null;
  840.  
  841. let mzk = 0;
  842. let myk = 0;
  843. let mlf = 0;
  844. let myw = 0;
  845. let mzt = 0;
  846. let zarr = null;
  847.  
  848. if ((mclp.clearList || 0).length === 0) {
  849. mclp.clearList66 = mclp.clearList;
  850. mclp.clearList = function () {
  851. mzk++;
  852. myk++;
  853. mlf++;
  854. myw++;
  855. mzt++;
  856. zarr = null;
  857. this.__intermediate_delay__ = null;
  858. this.clearList66();
  859. };
  860. }
  861.  
  862. if ((mclp.scrollToBottom_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM && MODIFY_SCROLL_TO_BOTTOM) {
  863.  
  864. mclp.scrollToBottom66_ = mclp.scrollToBottom_;
  865.  
  866. mclp.scrollToBottom77_ = async function () {
  867. if (mzk > 1e9) mzk = 9;
  868. let tid = ++mzk;
  869.  
  870. await new Promise(requestAnimationFrame);
  871.  
  872. if (tid !== mzk) {
  873. return;
  874. }
  875.  
  876. const cnt = this;
  877. if (USE_WILL_CHANGE_CONTROLLER) {
  878. const itemScroller = cnt.itemScroller;
  879. if (scrollWillChangeController && scrollWillChangeController.element !== itemScroller) {
  880. scrollWillChangeController.release();
  881. scrollWillChangeController = null;
  882. }
  883. if (!scrollWillChangeController) scrollWillChangeController = new WillChangeController(itemScroller, 'scroll-position');
  884. }
  885. const wcController = scrollWillChangeController;
  886. wcController && wcController.beforeOper();
  887.  
  888. await Promise.resolve();
  889. cnt.scrollToBottom66_();
  890.  
  891. await Promise.resolve();
  892. wcController && wcController.afterOper();
  893.  
  894. }
  895.  
  896. mclp.scrollToBottom_ = function () {
  897. const cnt = this;
  898. cnt.__intermediate_delay__ = new Promise(resolve => {
  899. cnt.scrollToBottom77_().then(() => {
  900. resolve();
  901. });
  902. });
  903. }
  904. }
  905.  
  906.  
  907.  
  908. if ((mclp.showNewItems_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM) {
  909.  
  910.  
  911. mclp.showNewItems66_ = mclp.showNewItems_;
  912.  
  913. mclp.showNewItems77_ = async function () {
  914. if (myk > 1e9) myk = 9;
  915. let tid = ++myk;
  916.  
  917. await new Promise(requestAnimationFrame);
  918.  
  919. if (tid !== myk) {
  920. return;
  921. }
  922.  
  923. const cnt = this;
  924.  
  925. await Promise.resolve();
  926. cnt.showNewItems66_();
  927.  
  928. await Promise.resolve();
  929.  
  930. }
  931.  
  932. mclp.showNewItems_ = function () {
  933.  
  934. const cnt = this;
  935. cnt.__intermediate_delay__ = new Promise(resolve => {
  936. cnt.showNewItems77_().then(() => {
  937. resolve();
  938. });
  939. });
  940. }
  941.  
  942. }
  943.  
  944.  
  945.  
  946.  
  947. if ((mclp.flushActiveItems_ || 0).length === 0) {
  948.  
  949.  
  950. mclp.flushActiveItems66_ = mclp.flushActiveItems_;
  951.  
  952.  
  953. mclp.flushActiveItems77_ = async function () {
  954. try {
  955.  
  956. const cnt = this;
  957. if (mlf > 1e9) mlf = 9;
  958. let tid = ++mlf;
  959. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  960. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  961.  
  962. // 4 times to maxItems to avoid frequent trimming.
  963. // 1 ... 10 ... 20 ... 30 ... 40 ... 50 ... 60 => 16 ... 20 ... 30 ..... 60 ... => 16
  964.  
  965. this.activeItems_.length > this.data.maxItemsToDisplay * 4 && this.data.maxItemsToDisplay > 4 && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay - 1);
  966. if (cnt.canScrollToBottom_()) {
  967. let immd = cnt.__intermediate_delay__;
  968. await new Promise(requestAnimationFrame);
  969. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  970. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  971.  
  972. const oMaxItemsToDisplay = this.data.maxItemsToDisplay;
  973. const reducedMaxItemsToDisplay = MAX_ITEMS_FOR_FULL_FLUSH;
  974. let changeMaxItemsToDisplay = false;
  975. if (ENABLE_REDUCED_MAXITEMS_FOR_FLUSH && this.activeItems_.length > this.data.maxItemsToDisplay) {
  976. if (this.data.maxItemsToDisplay > reducedMaxItemsToDisplay) {
  977. // as all the rendered chats are already "outdated"
  978. // all old chats shall remove and reduced number of few chats will be rendered
  979. // then restore to the original number
  980. changeMaxItemsToDisplay = true;
  981. this.data.maxItemsToDisplay = reducedMaxItemsToDisplay;
  982. }
  983. this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  984. // console.log('changeMaxItemsToDisplay 01', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  985. }
  986.  
  987. // it is found that it will render all stacked chats after switching back from background
  988. // to avoid lagging in popular livestream with massive chats, trim first before rendering.
  989. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  990.  
  991.  
  992.  
  993. const items = (cnt.$ || 0).items;
  994.  
  995. if (USE_WILL_CHANGE_CONTROLLER) {
  996. if (contensWillChangeController && contensWillChangeController.element !== items) {
  997. contensWillChangeController.release();
  998. contensWillChangeController = null;
  999. }
  1000. if (!contensWillChangeController) contensWillChangeController = new WillChangeController(items, 'contents');
  1001. }
  1002. const wcController = contensWillChangeController;
  1003. cnt.__intermediate_delay__ = Promise.all([cnt.__intermediate_delay__ || null, immd || null]);
  1004. wcController && wcController.beforeOper();
  1005. await Promise.resolve();
  1006. const len1 = cnt.activeItems_.length;
  1007. cnt.flushActiveItems66_();
  1008. const len2 = cnt.activeItems_.length;
  1009. let bAsync = len1 !== len2;
  1010. await Promise.resolve();
  1011. if (wcController) {
  1012. if (bAsync) {
  1013. cnt.async(() => {
  1014. wcController.afterOper();
  1015. });
  1016. } else {
  1017. wcController.afterOper();
  1018. }
  1019. }
  1020. if (changeMaxItemsToDisplay) {
  1021. if (this.data.maxItemsToDisplay === reducedMaxItemsToDisplay) {
  1022. this.data.maxItemsToDisplay = oMaxItemsToDisplay;
  1023. // console.log('changeMaxItemsToDisplay 02', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  1024. }
  1025. }
  1026.  
  1027.  
  1028. if (!ENABLE_NO_SMOOTH_TRANSFORM) {
  1029.  
  1030.  
  1031. const ff = () => {
  1032.  
  1033. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1034. // if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1035. if (!cnt.atBottom && cnt.allowScroll && cnt.canScrollToBottomDLW_ && cnt.canScrollToBottomDLW_()) {
  1036. cnt.scrollToBottom_();
  1037.  
  1038. Promise.resolve().then(() => {
  1039.  
  1040. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1041. if (!cnt.canScrollToBottom_()) cnt.scrollToBottom_();
  1042. })
  1043.  
  1044.  
  1045. }
  1046. }
  1047.  
  1048. ff();
  1049.  
  1050.  
  1051. Promise.resolve().then(ff)
  1052.  
  1053. // requestAnimationFrame(ff);
  1054. } else if (false) {
  1055.  
  1056. Promise.resolve().then(() => {
  1057.  
  1058. if (!cnt.atBottom) {
  1059. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1060. cnt.scrollToBottom_();
  1061. }
  1062.  
  1063. }).then(() => {
  1064.  
  1065. if (!cnt.atBottom) {
  1066. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1067. cnt.scrollToBottom_();
  1068. }
  1069.  
  1070. })
  1071. }
  1072.  
  1073.  
  1074. return 1;
  1075. } else {
  1076. // cnt.flushActiveItems66_();
  1077. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  1078. return 2;
  1079. }
  1080. } catch (e) {
  1081. console.warn(e);
  1082. }
  1083. }
  1084.  
  1085. mclp.flushActiveItems_ = function () {
  1086. const cnt = this;
  1087.  
  1088. if (arguments.length !== 0 || !cnt.activeItems_ || !cnt.canScrollToBottom_) return cnt.flushActiveItems66_.apply(this, arguments);
  1089.  
  1090. if (cnt.activeItems_.length === 0) {
  1091. cnt.__intermediate_delay__ = null;
  1092. return;
  1093. }
  1094.  
  1095. const cntData = ((cnt || 0).data || 0);
  1096. if (cntData.maxItemsToDisplay > MAX_ITEMS_FOR_TOTAL_DISPLAY) cntData.maxItemsToDisplay = MAX_ITEMS_FOR_TOTAL_DISPLAY;
  1097.  
  1098. // ignore previous __intermediate_delay__ and create a new one
  1099. cnt.__intermediate_delay__ = new Promise(resolve => {
  1100. cnt.flushActiveItems77_().then(rt => {
  1101. if (rt === 1) resolve(1); // success, scroll to bottom
  1102. else if (rt === 2) resolve(2); // success, trim
  1103. else resolve(-1); // skip
  1104. });
  1105. });
  1106.  
  1107. }
  1108. }
  1109.  
  1110. if ((mclp.async || 0).length === 2) {
  1111.  
  1112.  
  1113. mclp.async66 = mclp.async;
  1114. mclp.async = function () {
  1115. // ensure the previous operation is done
  1116. // .async is usually after the time consuming functions like flushActiveItems_ and scrollToBottom_
  1117.  
  1118. const stack = new Error().stack;
  1119. const isFlushAsync = stack.indexOf('flushActiveItems_') >= 0;
  1120. (this.__intermediate_delay__ || Promise.resolve()).then(rk => {
  1121. if (isFlushAsync) {
  1122. if (rk < 0) return;
  1123. if (rk === 2 && arguments[0] === this.maybeScrollToBottom_) return;
  1124. }
  1125. this.async66.apply(this, arguments);
  1126. });
  1127.  
  1128. }
  1129.  
  1130. }
  1131.  
  1132.  
  1133. if ((mclp.onScrollItems_ || 0).length === 1) {
  1134.  
  1135. mclp.onScrollItems66_ = mclp.onScrollItems_;
  1136. mclp.onScrollItems77_ = async function (evt) {
  1137. if (myw > 1e9) myw = 9;
  1138. let tid = ++myw;
  1139.  
  1140. await new Promise(requestAnimationFrame);
  1141.  
  1142. if (tid !== myw) {
  1143. return;
  1144. }
  1145.  
  1146. const cnt = this;
  1147.  
  1148. await Promise.resolve();
  1149. if (USE_OPTIMIZED_ON_SCROLL_ITEMS) {
  1150. await Promise.resolve().then(() => {
  1151. this.ytRendererBehavior.onScroll(evt);
  1152. }).then(() => {
  1153. if (dateNow() - lastWheel < 80) {
  1154. this.setAtBottom();
  1155. }
  1156. }).then(() => {
  1157. this.flushActiveItems_();
  1158. });
  1159. } else {
  1160. cnt.onScrollItems66_(evt);
  1161. }
  1162.  
  1163.  
  1164.  
  1165. await Promise.resolve();
  1166.  
  1167. }
  1168.  
  1169. mclp.onScrollItems_ = function (evt) {
  1170.  
  1171. const cnt = this;
  1172. cnt.__intermediate_delay__ = new Promise(resolve => {
  1173. cnt.onScrollItems77_(evt).then(() => {
  1174. resolve();
  1175. });
  1176. });
  1177. }
  1178. }
  1179.  
  1180. if ((mclp.handleLiveChatActions_ || 0).length === 1) {
  1181. mclp.handleLiveChatActions66_ = mclp.handleLiveChatActions_;
  1182.  
  1183. mclp.handleLiveChatActions77_ = async function (arr) {
  1184. if (typeof (arr || 0).length !== 'number') {
  1185. this.handleLiveChatActions66_(arr);
  1186. return;
  1187. }
  1188. if (mzt > 1e9) mzt = 9;
  1189. let tid = ++mzt;
  1190.  
  1191. if (zarr === null) zarr = arr;
  1192. else Array.prototype.push.apply(zarr, arr);
  1193. arr = null;
  1194.  
  1195. await new Promise(requestAnimationFrame);
  1196.  
  1197. if (tid !== mzt || zarr === null) {
  1198. return;
  1199. }
  1200.  
  1201. const carr = zarr;
  1202. zarr = null;
  1203.  
  1204. await Promise.resolve();
  1205. this.handleLiveChatActions66_(carr);
  1206. await Promise.resolve();
  1207.  
  1208. }
  1209.  
  1210. mclp.handleLiveChatActions_ = function (arr) {
  1211.  
  1212. const cnt = this;
  1213. cnt.__intermediate_delay__ = new Promise(resolve => {
  1214. cnt.handleLiveChatActions77_(arr).then(() => {
  1215. resolve();
  1216. });
  1217. });
  1218. }
  1219.  
  1220. }
  1221.  
  1222.  
  1223.  
  1224.  
  1225. })
  1226.  
  1227. });
  1228.  
  1229. const getProto = (element) => {
  1230. let proto = null;
  1231. if (element) {
  1232. if (element.inst) proto = element.inst.constructor.prototype;
  1233. else proto = element.constructor.prototype;
  1234. }
  1235. return proto || null;
  1236. }
  1237.  
  1238. let done = 0;
  1239. let main = async (q) => {
  1240. if (done) return;
  1241.  
  1242. if (!q) return;
  1243. let m1 = nodeParent(q);
  1244. let m2 = q;
  1245. if (!(m1 && m1.id === 'item-offset' && m2 && m2.id === 'items')) return;
  1246.  
  1247. done = 1;
  1248.  
  1249. // setTimeout(()=>{
  1250. // document.documentElement.setAttribute('dont-render-enabled','')
  1251. // },80)
  1252.  
  1253. Promise.resolve().then(watchUserCSS);
  1254.  
  1255. addCss();
  1256.  
  1257. setupStyle(m1, m2);
  1258.  
  1259. let lcRendererWR = null;
  1260.  
  1261. const lcRendererElm = () => {
  1262. let lcRenderer = kRef(lcRendererWR);
  1263. if (!lcRenderer || !lcRenderer.isConnected) {
  1264. lcRenderer = document.querySelector('yt-live-chat-item-list-renderer.yt-live-chat-renderer');
  1265. lcRendererWR = lcRenderer ? mWeakRef(lcRenderer) : null;
  1266. }
  1267. return lcRenderer
  1268. };
  1269.  
  1270. let hasFirstShowMore = false;
  1271.  
  1272. const visObserverFn = (entry) => {
  1273.  
  1274. const target = entry.target;
  1275. if (!target) return;
  1276. // if(target.classList.contains('dont-render')) return;
  1277. let isVisible = entry.isIntersecting === true && entry.intersectionRatio > 0.5;
  1278. // const h = entry.boundingClientRect.height;
  1279. /*
  1280. if (h < 16) { // wrong: 8 (padding/margin); standard: 32; test: 16 or 20
  1281. // e.g. under fullscreen. the element created but not rendered.
  1282. target.setAttribute('wSr93', '');
  1283. return;
  1284. }
  1285. */
  1286. if (isVisible) {
  1287. // target.style.setProperty('--wsr94', h + 'px');
  1288. target.setAttribute('wSr93', 'visible');
  1289. if (nNextElem(target) === null) {
  1290.  
  1291. // firstVisibleItemDetected = true;
  1292. /*
  1293. if (dateNow() - lastScroll < 80) {
  1294. lastLShow = 0;
  1295. lastScroll = 0;
  1296. Promise.resolve().then(clickShowMore);
  1297. } else {
  1298. lastLShow = dateNow();
  1299. }
  1300. */
  1301. // lastLShow = dateNow();
  1302. } else if (!hasFirstShowMore) { // should more than one item being visible
  1303. // implement inside visObserver to ensure there is sufficient delay
  1304. hasFirstShowMore = true;
  1305. requestAnimationFrame(() => {
  1306. // foreground page
  1307. // activeDeferredAppendChild = true;
  1308. // page visibly ready -> load the latest comments at initial loading
  1309. const lcRenderer = lcRendererElm();
  1310. if (lcRenderer) {
  1311. (lcRenderer.inst || lcRenderer).scrollToBottom_();
  1312. }
  1313. });
  1314. }
  1315. }
  1316. else if (target.getAttribute('wSr93') === 'visible') { // ignore target.getAttribute('wSr93') === '' to avoid wrong sizing
  1317.  
  1318. // target.style.setProperty('--wsr94', h + 'px');
  1319. target.setAttribute('wSr93', 'hidden');
  1320. } // note: might consider 0 < entry.intersectionRatio < 0.5 and target.getAttribute('wSr93') === '' <new last item>
  1321.  
  1322. }
  1323.  
  1324. const visObserver = new IntersectionObserver((entries) => {
  1325.  
  1326. for (const entry of entries) {
  1327.  
  1328. Promise.resolve(entry).then(visObserverFn);
  1329.  
  1330. }
  1331.  
  1332. }, {
  1333. /*
  1334. root: items,
  1335. rootMargin: "0px",
  1336. threshold: 1.0,
  1337. */
  1338. // root: HTMLElement.prototype.closest.call(m2, '#item-scroller.yt-live-chat-item-list-renderer'), // nullable
  1339. rootMargin: "0px",
  1340. threshold: [0.05, 0.95],
  1341. });
  1342.  
  1343. //m2.style.visibility='';
  1344.  
  1345. const mutFn = (items) => {
  1346. for (let node = nLastElem(items); node !== null; node = nPrevElem(node)) {
  1347. if (node.hasAttribute('wSr93')) break;
  1348. node.setAttribute('wSr93', '');
  1349. visObserver.observe(node);
  1350. }
  1351. }
  1352.  
  1353. const mutObserver = new MutationObserver((mutations) => {
  1354. const items = (mutations[0] || 0).target;
  1355. if (!items) return;
  1356. mutFn(items);
  1357. });
  1358.  
  1359.  
  1360. // let lzf = 0;
  1361. /*
  1362. const buffObserver = new MutationObserver((mutations) => {
  1363.  
  1364. const buff = (mutations[0] || 0).target;
  1365. if (!buff) return;
  1366.  
  1367. let m2 = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer');
  1368. if(!m2) return;
  1369.  
  1370.  
  1371. let uz = 0;
  1372. for(const mutation of mutations){
  1373. if(mutation.addedNodes){
  1374. for(const node of mutation.addedNodes){
  1375.  
  1376. uz++;
  1377.  
  1378. Promise.resolve(node).then((node) => {
  1379.  
  1380.  
  1381. const placeholder = node.placeHolderAs;
  1382. if (placeholder && placeholder.isConnected) {
  1383. placeholder.descTo = null;
  1384. node.placeHolderAs = null;
  1385.  
  1386. requestAnimationFrame(() => {
  1387. if (placeholder.isConnected && node.isConnected) {
  1388.  
  1389.  
  1390. placeholder.replaceWith(node);
  1391. try {
  1392. placeholder.remove();
  1393. } catch (e) { }
  1394. }
  1395. })
  1396. }
  1397. })
  1398.  
  1399. }
  1400. }
  1401. }
  1402.  
  1403. if(uz===0) return;
  1404.  
  1405.  
  1406. if(lzf>1e9) lzf = 9;
  1407. let tid = ++lzf;
  1408. /|*
  1409.  
  1410. let f = ()=>{
  1411.  
  1412. if(lzf !== tid) return;
  1413. let r = [];
  1414. let remain = false;
  1415. for(let node = buff.firstChild; node !==null ; node=node.nextSibling){
  1416. if(node.mkkReady) r.push(node);
  1417. else remain = true;
  1418. }
  1419.  
  1420. m2.append(...r);
  1421.  
  1422. if(remain) requestAnimationFrame(f);
  1423.  
  1424. };
  1425.  
  1426. requestAnimationFrame(f)
  1427. *|/
  1428. });
  1429. */
  1430.  
  1431. const setupMutObserver = (m2) => {
  1432. mutObserver.disconnect();
  1433. mutObserver.takeRecords();
  1434. if (m2) {
  1435. mutObserver.observe(m2, {
  1436. childList: true,
  1437. subtree: false
  1438. });
  1439. mutFn(m2);
  1440.  
  1441.  
  1442. if (ENABLE_NO_SMOOTH_TRANSFORM) {
  1443.  
  1444. let items = m2;
  1445. let addedAnchor = false;
  1446. if (items) {
  1447. if (items.nextElementSibling === null) {
  1448. items.classList.add('no-anchor');
  1449. addedAnchor = true;
  1450. items.parentNode.appendChild(dr(document.createElement('item-anchor')));
  1451. }
  1452. }
  1453.  
  1454.  
  1455.  
  1456. if (addedAnchor) {
  1457. nodeParent(m2).classList.add('no-anchor'); // required
  1458. }
  1459.  
  1460. }
  1461.  
  1462. // let div = document.createElement('div');
  1463. // div.id = 'qwcc';
  1464. // HTMLElement.prototype.appendChild.call(document.querySelector('yt-live-chat-item-list-renderer'), div )
  1465. // bufferRegion =div;
  1466.  
  1467. // buffObserver.takeRecords();
  1468. // buffObserver.disconnect();
  1469. // buffObserver.observe(div, {
  1470. // childList: true,
  1471. // subtree: false
  1472. // })
  1473.  
  1474.  
  1475.  
  1476. }
  1477. }
  1478.  
  1479. setupMutObserver(m2);
  1480.  
  1481. const mclp = getProto(document.querySelector('yt-live-chat-item-list-renderer'));
  1482. if (mclp && mclp.attached) {
  1483.  
  1484. mclp.attached66 = mclp.attached;
  1485. mclp.attached = function () {
  1486. let m2 = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer');
  1487. let m1 = nodeParent(m2);
  1488. setupStyle(m1, m2);
  1489. setupMutObserver(m2);
  1490. return this.attached66();
  1491. }
  1492.  
  1493. mclp.detached66 = mclp.detached;
  1494. mclp.detached = function () {
  1495. setupMutObserver();
  1496. return this.detached66();
  1497. }
  1498.  
  1499. mclp.canScrollToBottomDLW_ = () => !(dateNow() - lastWheel < 80);
  1500.  
  1501. mclp.canScrollToBottom_ = function () {
  1502. return this.atBottom && this.allowScroll && this.canScrollToBottomDLW_();
  1503. }
  1504.  
  1505. if (ENABLE_NO_SMOOTH_TRANSFORM) {
  1506.  
  1507.  
  1508. mclp.isSmoothScrollEnabled_ = function () {
  1509. return false;
  1510. }
  1511.  
  1512.  
  1513.  
  1514. mclp.maybeResizeScrollContainer_ = function () {
  1515. //
  1516. }
  1517.  
  1518. mclp.refreshOffsetContainerHeight_ = function () {
  1519. //
  1520. }
  1521.  
  1522. mclp.smoothScroll_ = function () {
  1523. //
  1524. }
  1525.  
  1526. mclp.resetSmoothScroll_ = function () {
  1527. //
  1528. }
  1529. }
  1530.  
  1531. } else {
  1532. console.warn(`proto.attached for yt-live-chat-item-list-renderer is unavailable.`)
  1533. }
  1534.  
  1535.  
  1536. let scrollCount = 0;
  1537. document.addEventListener('scroll', (evt) => {
  1538. if (!evt || !evt.isTrusted) return;
  1539. // lastScroll = dateNow();
  1540. if (++scrollCount > 1e9) scrollCount = 9;
  1541. }, { passive: true, capture: true }); // support contain => support passive
  1542.  
  1543. // document.addEventListener('scroll', (evt) => {
  1544.  
  1545. // if (!evt || !evt.isTrusted) return;
  1546. // if (!firstVisibleItemDetected) return;
  1547. // const isUserAction = dateNow() - lastWheel < 80; // continuous wheel -> continuous scroll -> continuous wheel -> continuous scroll
  1548. // if (!isUserAction) return;
  1549. // // lastScroll = dateNow();
  1550.  
  1551. // }, { passive: true, capture: true }) // support contain => support passive
  1552.  
  1553.  
  1554. let lastScrollCount = -1;
  1555. document.addEventListener('wheel', (evt) => {
  1556.  
  1557. if (!evt || !evt.isTrusted) return;
  1558. if (lastScrollCount === scrollCount) return;
  1559. lastScrollCount = scrollCount;
  1560. lastWheel = dateNow();
  1561.  
  1562. }, { passive: true, capture: true }); // support contain => support passive
  1563.  
  1564.  
  1565. const fp = (renderer) => {
  1566. const cnt = renderer.inst || renderer;
  1567. const container = (cnt.$ || 0).container;
  1568. if (container) {
  1569. container.setAttribute = tickerContainerSetAttribute;
  1570. }
  1571. };
  1572. const tags = ["yt-live-chat-ticker-paid-message-item-renderer", "yt-live-chat-ticker-paid-sticker-item-renderer",
  1573. "yt-live-chat-ticker-renderer", "yt-live-chat-ticker-sponsor-item-renderer"];
  1574. for (const tag of tags) {
  1575. const dummy = document.createElement(tag);
  1576.  
  1577. const cProto = getProto(dummy);
  1578. if (!cProto || !cProto.attached) {
  1579. console.warn(`proto.attached for ${tag} is unavailable.`)
  1580. continue;
  1581. }
  1582.  
  1583. const __updateTimeout__ = cProto.updateTimeout;
  1584.  
  1585. const canDoUpdateTimeoutReplacement = (() => {
  1586.  
  1587. if (dummy.countdownMs < 1 && dummy.lastCountdownTimeMs < 1 && dummy.countdownMs < 1 && dummy.countdownDurationMs < 1) {
  1588. return typeof dummy.setContainerWidth === 'function' && typeof dummy.slideDown === 'function';
  1589. }
  1590. return false;
  1591.  
  1592. })(dummy.inst || dummy) && ((__updateTimeout__ + "").indexOf("window.requestAnimationFrame(this.updateTimeout.bind(this))") > 0);
  1593.  
  1594.  
  1595.  
  1596. if (canDoUpdateTimeoutReplacement) {
  1597.  
  1598. const killTicker = (cnt) => {
  1599. if ("auto" === cnt.hostElement.style.width) cnt.setContainerWidth();
  1600. cnt.slideDown()
  1601. };
  1602.  
  1603. cProto.__ratio__ = null;
  1604. cProto._updateTimeout21_ = function (a) {
  1605.  
  1606. /*
  1607. let pRatio = this.countdownMs / this.countdownDurationMs;
  1608. this.countdownMs -= (a - (this.lastCountdownTimeMs || 0));
  1609. let noMoreCountDown = this.countdownMs < 1e-6;
  1610. let qRatio = this.countdownMs / this.countdownDurationMs;
  1611. if(noMoreCountDown){
  1612. this.countdownMs = 0;
  1613. this.ratio = 0;
  1614. } else if( pRatio - qRatio < 0.001 && qRatio < pRatio){
  1615.  
  1616. }else{
  1617. this.ratio = qRatio;
  1618. }
  1619. */
  1620.  
  1621. this.countdownMs -= (a - (this.lastCountdownTimeMs || 0));
  1622.  
  1623. let currentRatio = this.__ratio__;
  1624. let tdv = this.countdownMs / this.countdownDurationMs;
  1625. let nextRatio = Math.round(tdv * 500) / 500; // might generate 0.143000000001
  1626.  
  1627. const validCountDown = nextRatio > 0;
  1628. const isAttached = this.isAttached;
  1629.  
  1630. if (!validCountDown) {
  1631.  
  1632. this.lastCountdownTimeMs = null;
  1633.  
  1634. this.countdownMs = 0;
  1635. this.__ratio__ = null;
  1636. this.ratio = 0;
  1637.  
  1638. if (isAttached) Promise.resolve(this).then(killTicker);
  1639.  
  1640. } else if (!isAttached) {
  1641.  
  1642. this.lastCountdownTimeMs = null;
  1643.  
  1644. } else {
  1645.  
  1646. this.lastCountdownTimeMs = a;
  1647.  
  1648. const ratioDiff = currentRatio - nextRatio; // 0.144 - 0.142 = 0.002
  1649. if (ratioDiff < 0.001 && ratioDiff > -1e-6) {
  1650. // ratioDiff = 0
  1651.  
  1652. } else {
  1653. // ratioDiff = 0.002 / 0.004 ....
  1654. // OR ratioDiff < 0
  1655.  
  1656. this.__ratio__ = nextRatio;
  1657.  
  1658. this.ratio = nextRatio;
  1659. }
  1660.  
  1661. return true;
  1662. }
  1663.  
  1664. };
  1665.  
  1666. cProto._updateTimeout21_ = function (a) {
  1667. this.countdownMs = Math.max(0, this.countdownMs - (a - (this.lastCountdownTimeMs || 0)));
  1668. this.ratio = this.countdownMs / this.countdownDurationMs;
  1669. if (this.isAttached && this.countdownMs) {
  1670. this.lastCountdownTimeMs = a;
  1671. return true;
  1672. } else {
  1673. this.lastCountdownTimeMs = null;
  1674. if (this.isAttached) {
  1675. ("auto" === this.hostElement.style.width && this.setContainerWidth(), this.slideDown())
  1676. }
  1677. }
  1678. }
  1679.  
  1680.  
  1681. // temporarily removed; buggy for playback
  1682. /*
  1683. cProto.updateTimeout = async function (a) {
  1684.  
  1685. let ret = this._updateTimeout21_(a);
  1686. while (ret) {
  1687. let a = await new Promise(resolve => {
  1688. this.rafId = requestAnimationFrame(resolve)
  1689. }); // could be never resolve
  1690. ret = this._updateTimeout21_(a);
  1691. }
  1692.  
  1693. };
  1694. */
  1695.  
  1696.  
  1697. }
  1698.  
  1699. cProto.attached77 = cProto.attached
  1700.  
  1701. cProto.attached = function () {
  1702. fp(this.hostElement || this);
  1703. return this.attached77();
  1704. }
  1705.  
  1706. for (const elm of document.getElementsByTagName(tag)) {
  1707. fp(elm);
  1708. }
  1709.  
  1710.  
  1711. }
  1712.  
  1713. };
  1714.  
  1715.  
  1716. function onReady() {
  1717. let tmObserver = new MutationObserver(() => {
  1718.  
  1719. let p = document.getElementById('items'); // fast
  1720. if (!p) return;
  1721. let q = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer'); // check
  1722.  
  1723. if (q) {
  1724. tmObserver.disconnect();
  1725. tmObserver.takeRecords();
  1726. tmObserver = null;
  1727. Promise.resolve(q).then((q) => {
  1728. // confirm Promis.resolve() is resolveable
  1729. // execute main without direct blocking
  1730. main(q);
  1731. })
  1732. }
  1733.  
  1734. });
  1735.  
  1736. tmObserver.observe(document.body || document.documentElement, {
  1737. childList: true,
  1738. subtree: true
  1739. });
  1740.  
  1741. }
  1742.  
  1743. Promise.resolve().then(() => {
  1744.  
  1745. if (document.readyState !== 'loading') {
  1746. onReady();
  1747. } else {
  1748. window.addEventListener("DOMContentLoaded", onReady, false);
  1749. }
  1750.  
  1751. });
  1752.  
  1753. })({ Promise, requestAnimationFrame, IntersectionObserver });