YouTube 超快聊天

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

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

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