YouTube 超快聊天

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

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

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