YouTube 超快聊天

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

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

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