YouTube 超快聊天

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

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

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