YouTube 超快聊天

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

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

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