YouTube 超快聊天

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

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

  1. // ==UserScript==
  2. // @name YouTube Super Fast Chat
  3. // @version 0.9.1
  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://gf.qytechs.cn/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.  
  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. }else{
  407.  
  408. ENABLE_FULL_RENDER_REQUIRED = true; // Chromium-based browsers
  409.  
  410. }
  411.  
  412.  
  413. // let bufferRegion = null;
  414. // let listOfDom = [];
  415.  
  416.  
  417. ENABLE_FULL_RENDER_REQUIRED && document.addEventListener('animationstart', (evt)=>{
  418.  
  419. if(evt.animationName === 'dontRenderAnimation'){
  420. evt.target.classList.remove('dont-render');
  421. }
  422.  
  423. }, true);
  424.  
  425. ENABLE_FULL_RENDER_REQUIRED && ((appendChild)=>{
  426.  
  427. const f = (elm)=>{
  428. if(elm && elm.nodeType === 1){
  429. elm.classList.add('dont-render');
  430. }
  431. }
  432.  
  433. Node.prototype.appendChild = function(a){
  434.  
  435. if(this.id==='items' && this.classList.contains('yt-live-chat-item-list-renderer')){
  436. // if(this.matches('.style-scope.yt-live-chat-item-list-renderer')){
  437.  
  438.  
  439. // let elms = [];
  440. // if(a.nodeType ===1) elms.push(a);
  441. // else if(a instanceof DocumentFragment ){
  442.  
  443. // for(let n = a.firstChild; n; n=n.nextSibling){
  444. // elms.push(n);
  445. // }
  446.  
  447. // }
  448.  
  449. // for(const elm of elms){
  450.  
  451.  
  452. // if(elm && elm.nodeType ===1){
  453.  
  454. // /*
  455.  
  456. // let placeholder = document.createElement('dom-placeholder');
  457.  
  458.  
  459. // placeholder.descTo = elm;
  460. // elm.placeHolderAs = placeholder;
  461. // appendChild.call(bufferRegion, elm);
  462. // return appendChild.call(this, placeholder);
  463.  
  464. // */
  465.  
  466. // elm.classList.add('dont-render');
  467. // // // listOfDom.push(elm);
  468. // // Promise.resolve(elm).then((elm)=>{
  469.  
  470. // // setTimeout(()=>{
  471.  
  472.  
  473. // // elm.classList.remove('dont-render');
  474. // // }, 80);
  475. // // });
  476.  
  477.  
  478.  
  479.  
  480. // }
  481.  
  482. // }
  483.  
  484.  
  485.  
  486. if(a && a.nodeType ===1) f(a);
  487. else if(a instanceof DocumentFragment ){
  488.  
  489. for(let n = a.firstChild; n; n=n.nextSibling){
  490. f(n);
  491. }
  492.  
  493. }
  494.  
  495.  
  496. return appendChild.call(this, a);
  497.  
  498. // }
  499.  
  500. }
  501. // console.log(11,this)
  502. return appendChild.call(this, a)
  503.  
  504. }
  505.  
  506. })(Node.prototype.appendChild);
  507.  
  508.  
  509. ((appendChild)=>{
  510.  
  511. DocumentFragment.prototype.appendChild = function(a){
  512.  
  513. // console.log(22,this)
  514. return appendChild.call(this, a)
  515.  
  516. }
  517.  
  518. })(DocumentFragment.prototype.appendChild)
  519.  
  520. // const APPLY_delayAppendChild = false;
  521.  
  522. // let activeDeferredAppendChild = false; // deprecated
  523.  
  524. // let delayedAppendParentWS = new WeakSet();
  525. // let delayedAppendOperations = [];
  526. // let commonAppendParentStackSet = new Set();
  527.  
  528. // let firstVisibleItemDetected = false; // deprecated
  529.  
  530. const sp7 = Symbol();
  531.  
  532.  
  533. let dt0 = Date.now() - 2000;
  534. const dateNow = () => Date.now() - dt0;
  535. // let lastScroll = 0;
  536. // let lastLShow = 0;
  537. let lastWheel = 0;
  538.  
  539. const proxyHelperFn = (dummy) => ({
  540.  
  541. get(target, prop) {
  542. return (prop in dummy) ? dummy[prop] : prop === sp7 ? target : target[prop];
  543. },
  544. set(target, prop, value) {
  545. if (!(prop in dummy)) {
  546. target[prop] = value;
  547. }
  548. return true;
  549. },
  550. has(target, prop) {
  551. return (prop in target)
  552. },
  553. deleteProperty(target, prop) {
  554. return true;
  555. },
  556. ownKeys(target) {
  557. return Object.keys(target);
  558. },
  559. defineProperty(target, key, descriptor) {
  560. return Object.defineProperty(target, key, descriptor);
  561. },
  562. getOwnPropertyDescriptor(target, key) {
  563. return Object.getOwnPropertyDescriptor(target, key);
  564. },
  565.  
  566. });
  567.  
  568. const tickerContainerSetAttribute = function (attrName, attrValue) { // ensure '14.30000001%'.toFixed(1)
  569.  
  570. let yd = (this.__dataHost || (this.inst || 0).__dataHost).__data;
  571.  
  572. if (arguments.length === 2 && attrName === 'style' && yd && attrValue) {
  573.  
  574. // let v = yd.containerStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
  575. let v = `${attrValue}`;
  576. // conside a ticker is 101px width
  577. // 1% = 1.01px
  578. // 0.2% = 0.202px
  579.  
  580.  
  581. const ratio1 = (yd.ratio * 100);
  582. if (ratio1 > -1) { // avoid NaN
  583.  
  584. // countdownDurationMs
  585. // 600000 - 0.2% <1% = 6s> <0.2% = 1.2s>
  586. // 300000 - 0.5% <1% = 3s> <0.5% = 1.5s>
  587. // 150000 - 1% <1% = 1.5s>
  588. // 75000 - 2% <1% =0.75s > <2% = 1.5s>
  589. // 30000 - 5% <1% =0.3s > <5% = 1.5s>
  590.  
  591. // 99px * 5% = 4.95px
  592.  
  593. // 15000 - 10% <1% =0.15s > <10% = 1.5s>
  594.  
  595.  
  596.  
  597.  
  598. // 1% Duration
  599.  
  600. let ratio2 = ratio1;
  601.  
  602. const ydd = yd.data;
  603. const d1 = ydd.durationSec;
  604. const d2 = ydd.fullDurationSec;
  605.  
  606. if (d1 === d2 && d1 > 1) {
  607.  
  608. if (d1 > 400) ratio2 = Math.round(ratio2 * 5) / 5; // 0.2%
  609. else if (d1 > 200) ratio2 = Math.round(ratio2 * 2) / 2; // 0.5%
  610. else if (d1 > 100) ratio2 = Math.round(ratio2 * 1) / 1; // 1%
  611. else if (d1 > 50) ratio2 = Math.round(ratio2 * 0.5) / 0.5; // 2%
  612. else if (d1 > 25) ratio2 = Math.round(ratio2 * 0.2) / 0.2; // 5% (max => 99px * 5% = 4.95px)
  613. else ratio2 = Math.round(ratio2 * 0.2) / 0.2;
  614.  
  615. } else {
  616. ratio2 = Math.round(ratio2 * 5) / 5; // 0.2% (min)
  617. }
  618.  
  619. // ratio2 = Math.round(ratio2 * 5) / 5;
  620. ratio2 = ratio2.toFixed(1)
  621. v = v.replace(`${ratio1}%`, `${ratio2}%`).replace(`${ratio1}%`, `${ratio2}%`)
  622.  
  623. if (yd.__style_last__ === v) return;
  624. yd.__style_last__ = v;
  625. // do not consider any delay here.
  626. // it shall be inside the looping for all properties changes. all the css background ops are in the same microtask.
  627.  
  628. }
  629.  
  630. HTMLElement.prototype.setAttribute.call(this, attrName, v);
  631.  
  632.  
  633. } else {
  634. HTMLElement.prototype.setAttribute.apply(this, arguments);
  635. }
  636.  
  637. };
  638.  
  639. const fxOperator = (proto, propertyName) => {
  640. let propertyDescriptorGetter = null;
  641. try {
  642. propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
  643. } catch (e) { }
  644. return typeof propertyDescriptorGetter === 'function' ? (e) => propertyDescriptorGetter.call(e) : (e) => e[propertyName];
  645. };
  646.  
  647. const nodeParent = fxOperator(Node.prototype, 'parentNode');
  648. // const nFirstElem = fxOperator(HTMLElement.prototype, 'firstElementChild');
  649. const nPrevElem = fxOperator(HTMLElement.prototype, 'previousElementSibling');
  650. const nNextElem = fxOperator(HTMLElement.prototype, 'nextElementSibling');
  651. const nLastElem = fxOperator(HTMLElement.prototype, 'lastElementChild');
  652.  
  653.  
  654. /* globals WeakRef:false */
  655.  
  656. /** @type {(o: Object | null) => WeakRef | null} */
  657. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  658.  
  659. /** @type {(wr: Object | null) => Object | null} */
  660. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  661.  
  662. const watchUserCSS = () => {
  663.  
  664. // if (!CSS.supports('contain-intrinsic-size', 'auto var(--wsr94)')) return;
  665.  
  666. const getElemFromWR = (nr) => {
  667. const n = kRef(nr);
  668. if (n && n.isConnected) return n;
  669. return null;
  670. }
  671.  
  672. const clearContentVisibilitySizing = () => {
  673. Promise.resolve().then(() => {
  674.  
  675. let btnShowMoreWR = mWeakRef(document.querySelector('#show-more[disabled]'));
  676.  
  677. let lastVisibleItemWR = null;
  678. for (const elm of document.querySelectorAll('[wSr93]')) {
  679. if (elm.getAttribute('wSr93') === 'visible') lastVisibleItemWR = mWeakRef(elm);
  680. elm.setAttribute('wSr93', '');
  681. // custom CSS property --wsr94 not working when attribute wSr93 removed
  682. }
  683. requestAnimationFrame(() => {
  684. const btnShowMore = getElemFromWR(btnShowMoreWR); btnShowMoreWR = null;
  685. if (btnShowMore) btnShowMore.click();
  686. else {
  687. // would not work if switch it frequently
  688. const lastVisibleItem = getElemFromWR(lastVisibleItemWR); lastVisibleItemWR = null;
  689. if (lastVisibleItem) {
  690.  
  691. Promise.resolve()
  692. .then(() => lastVisibleItem.scrollIntoView())
  693. .then(() => lastVisibleItem.scrollIntoView(false))
  694. .then(() => lastVisibleItem.scrollIntoView({ behavior: "instant", block: "end", inline: "nearest" }))
  695. .catch(e => { }) // break the chain when method not callable
  696.  
  697. }
  698. }
  699. })
  700.  
  701. })
  702.  
  703. }
  704.  
  705. const mutObserver = new MutationObserver((mutations) => {
  706. for (const mutation of mutations) {
  707. if ((mutation.addedNodes || 0).length >= 1) {
  708. for (const addedNode of mutation.addedNodes) {
  709. if (addedNode.nodeName === 'STYLE') {
  710. clearContentVisibilitySizing();
  711. return;
  712. }
  713. }
  714. }
  715. if ((mutation.removedNodes || 0).length >= 1) {
  716. for (const removedNode of mutation.removedNodes) {
  717. if (removedNode.nodeName === 'STYLE') {
  718. clearContentVisibilitySizing();
  719. return;
  720. }
  721. }
  722. }
  723. }
  724. });
  725.  
  726. mutObserver.observe(document.documentElement, {
  727. childList: true,
  728. subtree: false
  729. })
  730.  
  731. mutObserver.observe(document.head, {
  732. childList: true,
  733. subtree: false
  734. })
  735. mutObserver.observe(document.body, {
  736. childList: true,
  737. subtree: false
  738. });
  739.  
  740. }
  741.  
  742. const setupStyle = (m1, m2) => {
  743. if (!ENABLE_NO_SMOOTH_TRANSFORM) return;
  744.  
  745. const dummy1v = {
  746. transform: '',
  747. height: '',
  748. minHeight: '',
  749. paddingBottom: '',
  750. paddingTop: ''
  751. };
  752.  
  753. for (const k of ['toString', 'getPropertyPriority', 'getPropertyValue', 'item', 'removeProperty', 'setProperty']) {
  754. dummy1v[k] = ((k) => (function () { const style = this[sp7]; return style[k](...arguments); }))(k)
  755. }
  756.  
  757. const dummy1p = proxyHelperFn(dummy1v);
  758. const sp1v = new Proxy(m1.style, dummy1p);
  759. const sp2v = new Proxy(m2.style, dummy1p);
  760. Object.defineProperty(m1, 'style', { get() { return sp1v }, set() { }, enumerable: true, configurable: true });
  761. Object.defineProperty(m2, 'style', { get() { return sp2v }, set() { }, enumerable: true, configurable: true });
  762. m1.removeAttribute("style");
  763. m2.removeAttribute("style");
  764.  
  765. }
  766.  
  767.  
  768. class WillChangeController {
  769. constructor(itemScroller, willChangeValue) {
  770. this.element = itemScroller;
  771. this.counter = 0;
  772. this.active = false;
  773. this.willChangeValue = willChangeValue;
  774. }
  775.  
  776. beforeOper() {
  777. if (!this.active) {
  778. this.active = true;
  779. this.element.style.willChange = this.willChangeValue;
  780. }
  781. this.counter++;
  782. }
  783.  
  784. afterOper() {
  785. const c = this.counter;
  786. requestAnimationFrame(() => {
  787. if (c === this.counter) {
  788. this.active = false;
  789. this.element.style.willChange = '';
  790. }
  791. })
  792. }
  793.  
  794. release() {
  795. const element = this.element;
  796. this.element = null;
  797. this.counter = 1e16;
  798. this.active = false;
  799. try {
  800. element.style.willChange = '';
  801. } catch (e) { }
  802. }
  803.  
  804. }
  805.  
  806.  
  807. customYtElements.onRegistryReady(() => {
  808.  
  809.  
  810. let scrollWillChangeController = null;
  811. let contensWillChangeController = null;
  812.  
  813. // as it links to event handling, it has to be injected using immediateCallback
  814. customYtElements.whenRegistered('yt-live-chat-item-list-renderer', (cProto) => {
  815.  
  816. const mclp = cProto;
  817. console.assert(typeof mclp.scrollToBottom_ === 'function')
  818. console.assert(typeof mclp.scrollToBottom66_ !== 'function')
  819. console.assert(typeof mclp.flushActiveItems_ === 'function')
  820. console.assert(typeof mclp.flushActiveItems66_ !== 'function')
  821. console.assert(typeof mclp.async === 'function')
  822.  
  823.  
  824. mclp.__intermediate_delay__ = null;
  825.  
  826. let mzk = 0;
  827. let myk = 0;
  828. let mlf = 0;
  829. let myw = 0;
  830. let mzt = 0;
  831. let zarr = null;
  832.  
  833. if ((mclp.clearList || 0).length === 0) {
  834. mclp.clearList66 = mclp.clearList;
  835. mclp.clearList = function () {
  836. mzk++;
  837. myk++;
  838. mlf++;
  839. myw++;
  840. mzt++;
  841. zarr = null;
  842. this.__intermediate_delay__ = null;
  843. this.clearList66();
  844. };
  845. }
  846.  
  847. if ((mclp.scrollToBottom_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM) {
  848.  
  849. mclp.scrollToBottom66_ = mclp.scrollToBottom_;
  850.  
  851. mclp.scrollToBottom77_ = async function () {
  852. if (mzk > 1e9) mzk = 9;
  853. let tid = ++mzk;
  854.  
  855. await new Promise(requestAnimationFrame);
  856.  
  857. if (tid !== mzk) {
  858. return;
  859. }
  860.  
  861. const cnt = this;
  862. const itemScroller = cnt.itemScroller;
  863. if (scrollWillChangeController && scrollWillChangeController.element !== itemScroller) {
  864. scrollWillChangeController.release();
  865. scrollWillChangeController = null;
  866. }
  867. if (!scrollWillChangeController) scrollWillChangeController = new WillChangeController(itemScroller, 'scroll-position');
  868. const wcController = scrollWillChangeController;
  869. wcController.beforeOper();
  870.  
  871. await Promise.resolve();
  872. cnt.scrollToBottom66_();
  873.  
  874. await Promise.resolve();
  875. wcController.afterOper();
  876.  
  877. }
  878.  
  879. mclp.scrollToBottom_ = function () {
  880. const cnt = this;
  881. cnt.__intermediate_delay__ = new Promise(resolve => {
  882. cnt.scrollToBottom77_().then(() => {
  883. resolve();
  884. });
  885. });
  886. }
  887. }
  888.  
  889.  
  890. if ((mclp.showNewItems_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM) {
  891.  
  892.  
  893. mclp.showNewItems66_ = mclp.showNewItems_;
  894.  
  895. mclp.showNewItems77_ = async function () {
  896. if (myk > 1e9) myk = 9;
  897. let tid = ++myk;
  898.  
  899. await new Promise(requestAnimationFrame);
  900.  
  901. if (tid !== myk) {
  902. return;
  903. }
  904.  
  905. const cnt = this;
  906.  
  907. await Promise.resolve();
  908. cnt.showNewItems66_();
  909.  
  910. await Promise.resolve();
  911.  
  912. }
  913.  
  914. mclp.showNewItems_ = function () {
  915.  
  916. const cnt = this;
  917. cnt.__intermediate_delay__ = new Promise(resolve => {
  918. cnt.showNewItems77_().then(() => {
  919. resolve();
  920. });
  921. });
  922. }
  923.  
  924. }
  925.  
  926.  
  927.  
  928.  
  929. if ((mclp.flushActiveItems_ || 0).length === 0) {
  930.  
  931.  
  932. mclp.flushActiveItems66_ = mclp.flushActiveItems_;
  933.  
  934.  
  935. mclp.flushActiveItems77_ = async function () {
  936. try {
  937.  
  938. const cnt = this;
  939. if (mlf > 1e9) mlf = 9;
  940. let tid = ++mlf;
  941. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  942. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  943.  
  944. // 4 times to maxItems to avoid frequent trimming.
  945. // 1 ... 10 ... 20 ... 30 ... 40 ... 50 ... 60 => 16 ... 20 ... 30 ..... 60 ... => 16
  946.  
  947. this.activeItems_.length > this.data.maxItemsToDisplay * 4 && this.data.maxItemsToDisplay > 4 && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay - 1);
  948. if (cnt.canScrollToBottom_()) {
  949. let immd = cnt.__intermediate_delay__;
  950. await new Promise(requestAnimationFrame);
  951. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  952. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  953.  
  954. const oMaxItemsToDisplay = this.data.maxItemsToDisplay;
  955. const reducedMaxItemsToDisplay = MAX_ITEMS_FOR_FULL_FLUSH;
  956. let changeMaxItemsToDisplay = false;
  957. if (ENABLE_REDUCED_MAXITEMS_FOR_FLUSH && this.activeItems_.length > this.data.maxItemsToDisplay) {
  958. if (this.data.maxItemsToDisplay > reducedMaxItemsToDisplay) {
  959. // as all the rendered chats are already "outdated"
  960. // all old chats shall remove and reduced number of few chats will be rendered
  961. // then restore to the original number
  962. changeMaxItemsToDisplay = true;
  963. this.data.maxItemsToDisplay = reducedMaxItemsToDisplay;
  964. }
  965. this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  966. // console.log('changeMaxItemsToDisplay 01', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  967. }
  968.  
  969. // it is found that it will render all stacked chats after switching back from background
  970. // to avoid lagging in popular livestream with massive chats, trim first before rendering.
  971. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  972.  
  973.  
  974.  
  975. const items = (cnt.$ || 0).items;
  976. if (contensWillChangeController && contensWillChangeController.element !== items) {
  977. contensWillChangeController.release();
  978. contensWillChangeController = null;
  979. }
  980. if (!contensWillChangeController) contensWillChangeController = new WillChangeController(items, 'contents');
  981. const wcController = contensWillChangeController;
  982. cnt.__intermediate_delay__ = Promise.all([cnt.__intermediate_delay__ || null, immd || null]);
  983. wcController.beforeOper();
  984. await Promise.resolve();
  985. const len1 = cnt.activeItems_.length;
  986. cnt.flushActiveItems66_();
  987. const len2 = cnt.activeItems_.length;
  988. let bAsync = len1 !== len2;
  989. await Promise.resolve();
  990. if (bAsync) {
  991. cnt.async(() => {
  992. wcController.afterOper();
  993. });
  994. } else {
  995. wcController.afterOper();
  996. }
  997. if (changeMaxItemsToDisplay) {
  998. if (this.data.maxItemsToDisplay === reducedMaxItemsToDisplay) {
  999. this.data.maxItemsToDisplay = oMaxItemsToDisplay;
  1000. // console.log('changeMaxItemsToDisplay 02', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  1001. }
  1002. }
  1003.  
  1004.  
  1005. if (!ENABLE_NO_SMOOTH_TRANSFORM) {
  1006.  
  1007.  
  1008. const ff = () => {
  1009.  
  1010. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1011. // if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1012. if (!cnt.atBottom && cnt.allowScroll && cnt.canScrollToBottomDLW_ && cnt.canScrollToBottomDLW_()) {
  1013. cnt.scrollToBottom_();
  1014.  
  1015. Promise.resolve().then(() => {
  1016.  
  1017. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1018. if (!cnt.canScrollToBottom_()) cnt.scrollToBottom_();
  1019. })
  1020.  
  1021.  
  1022. }
  1023. }
  1024.  
  1025. ff();
  1026.  
  1027.  
  1028. Promise.resolve().then(ff)
  1029.  
  1030. // requestAnimationFrame(ff);
  1031. } else if(false) {
  1032.  
  1033. Promise.resolve().then(() => {
  1034.  
  1035. if (!cnt.atBottom) {
  1036. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1037. cnt.scrollToBottom_();
  1038. }
  1039.  
  1040. }).then(()=>{
  1041.  
  1042. if (!cnt.atBottom) {
  1043. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1044. cnt.scrollToBottom_();
  1045. }
  1046.  
  1047. })
  1048. }
  1049.  
  1050.  
  1051. return 1;
  1052. } else {
  1053. // cnt.flushActiveItems66_();
  1054. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  1055. return 2;
  1056. }
  1057. } catch (e) {
  1058. console.warn(e);
  1059. }
  1060. }
  1061.  
  1062. mclp.flushActiveItems_ = function () {
  1063. const cnt = this;
  1064.  
  1065. if (arguments.length !== 0 || !cnt.activeItems_ || !cnt.canScrollToBottom_) return cnt.flushActiveItems66_.apply(this, arguments);
  1066.  
  1067. if (cnt.activeItems_.length === 0) {
  1068. cnt.__intermediate_delay__ = null;
  1069. return;
  1070. }
  1071.  
  1072. const cntData = ((cnt || 0).data || 0);
  1073. if (cntData.maxItemsToDisplay > MAX_ITEMS_FOR_TOTAL_DISPLAY) cntData.maxItemsToDisplay = MAX_ITEMS_FOR_TOTAL_DISPLAY;
  1074.  
  1075. // ignore previous __intermediate_delay__ and create a new one
  1076. cnt.__intermediate_delay__ = new Promise(resolve => {
  1077. cnt.flushActiveItems77_().then(rt => {
  1078. if (rt === 1) resolve(1); // success, scroll to bottom
  1079. else if (rt === 2) resolve(2); // success, trim
  1080. else resolve(-1); // skip
  1081. });
  1082. });
  1083.  
  1084. }
  1085. }
  1086.  
  1087. if ((mclp.async || 0).length === 2) {
  1088.  
  1089.  
  1090. mclp.async66 = mclp.async;
  1091. mclp.async = function () {
  1092. // ensure the previous operation is done
  1093. // .async is usually after the time consuming functions like flushActiveItems_ and scrollToBottom_
  1094.  
  1095. const stack = new Error().stack;
  1096. const isFlushAsync = stack.indexOf('flushActiveItems_') >= 0;
  1097. (this.__intermediate_delay__ || Promise.resolve()).then(rk => {
  1098. if (isFlushAsync) {
  1099. if (rk < 0) return;
  1100. if (rk === 2 && arguments[0] === this.maybeScrollToBottom_) return;
  1101. }
  1102. this.async66.apply(this, arguments);
  1103. });
  1104.  
  1105. }
  1106.  
  1107. }
  1108.  
  1109.  
  1110. if ((mclp.onScrollItems_ || 0).length === 1) {
  1111.  
  1112. mclp.onScrollItems66_ = mclp.onScrollItems_;
  1113. mclp.onScrollItems77_ = async function (evt) {
  1114. if (myw > 1e9) myw = 9;
  1115. let tid = ++myw;
  1116.  
  1117. await new Promise(requestAnimationFrame);
  1118.  
  1119. if (tid !== myw) {
  1120. return;
  1121. }
  1122.  
  1123. const cnt = this;
  1124.  
  1125. await Promise.resolve();
  1126. cnt.onScrollItems66_(evt);
  1127.  
  1128. await Promise.resolve();
  1129.  
  1130. }
  1131.  
  1132. mclp.onScrollItems_ = function (evt) {
  1133.  
  1134. const cnt = this;
  1135. cnt.__intermediate_delay__ = new Promise(resolve => {
  1136. cnt.onScrollItems77_(evt).then(() => {
  1137. resolve();
  1138. });
  1139. });
  1140. }
  1141. }
  1142.  
  1143. if ((mclp.handleLiveChatActions_ || 0).length === 1) {
  1144. mclp.handleLiveChatActions66_ = mclp.handleLiveChatActions_;
  1145.  
  1146. mclp.handleLiveChatActions77_ = async function (arr) {
  1147. if (typeof (arr || 0).length !== 'number') {
  1148. this.handleLiveChatActions66_(arr);
  1149. return;
  1150. }
  1151. if (mzt > 1e9) mzt = 9;
  1152. let tid = ++mzt;
  1153.  
  1154. if (zarr === null) zarr = arr;
  1155. else Array.prototype.push.apply(zarr, arr);
  1156. arr = null;
  1157.  
  1158. await new Promise(requestAnimationFrame);
  1159.  
  1160. if (tid !== mzt || zarr === null) {
  1161. return;
  1162. }
  1163.  
  1164. const carr = zarr;
  1165. zarr = null;
  1166.  
  1167. await Promise.resolve();
  1168. this.handleLiveChatActions66_(carr);
  1169. await Promise.resolve();
  1170.  
  1171. }
  1172.  
  1173. mclp.handleLiveChatActions_ = function (arr) {
  1174.  
  1175. const cnt = this;
  1176. cnt.__intermediate_delay__ = new Promise(resolve => {
  1177. cnt.handleLiveChatActions77_(arr).then(() => {
  1178. resolve();
  1179. });
  1180. });
  1181. }
  1182.  
  1183. }
  1184.  
  1185.  
  1186.  
  1187.  
  1188. })
  1189.  
  1190. });
  1191.  
  1192. const getProto = (element) => {
  1193. let proto = null;
  1194. if (element) {
  1195. if (element.inst) proto = element.inst.constructor.prototype;
  1196. else proto = element.constructor.prototype;
  1197. }
  1198. return proto || null;
  1199. }
  1200.  
  1201. let done = 0;
  1202. let main = async (q) => {
  1203. if (done) return;
  1204.  
  1205. if (!q) return;
  1206. let m1 = nodeParent(q);
  1207. let m2 = q;
  1208. if (!(m1 && m1.id === 'item-offset' && m2 && m2.id === 'items')) return;
  1209.  
  1210. done = 1;
  1211.  
  1212. Promise.resolve().then(watchUserCSS);
  1213.  
  1214. addCss();
  1215.  
  1216. setupStyle(m1, m2);
  1217.  
  1218. let lcRendererWR = null;
  1219.  
  1220. const lcRendererElm = () => {
  1221. let lcRenderer = kRef(lcRendererWR);
  1222. if (!lcRenderer || !lcRenderer.isConnected) {
  1223. lcRenderer = document.querySelector('yt-live-chat-item-list-renderer.yt-live-chat-renderer');
  1224. lcRendererWR = lcRenderer ? mWeakRef(lcRenderer) : null;
  1225. }
  1226. return lcRenderer
  1227. };
  1228.  
  1229. let hasFirstShowMore = false;
  1230.  
  1231. const visObserverFn = (entry) => {
  1232.  
  1233. const target = entry.target;
  1234. if (!target) return;
  1235. // if(target.classList.contains('dont-render')) return;
  1236. let isVisible = entry.isIntersecting === true && entry.intersectionRatio > 0.5;
  1237. // const h = entry.boundingClientRect.height;
  1238. /*
  1239. if (h < 16) { // wrong: 8 (padding/margin); standard: 32; test: 16 or 20
  1240. // e.g. under fullscreen. the element created but not rendered.
  1241. target.setAttribute('wSr93', '');
  1242. return;
  1243. }
  1244. */
  1245. if (isVisible) {
  1246. // target.style.setProperty('--wsr94', h + 'px');
  1247. target.setAttribute('wSr93', 'visible');
  1248. if (nNextElem(target) === null) {
  1249.  
  1250. // firstVisibleItemDetected = true;
  1251. /*
  1252. if (dateNow() - lastScroll < 80) {
  1253. lastLShow = 0;
  1254. lastScroll = 0;
  1255. Promise.resolve().then(clickShowMore);
  1256. } else {
  1257. lastLShow = dateNow();
  1258. }
  1259. */
  1260. // lastLShow = dateNow();
  1261. } else if (!hasFirstShowMore) { // should more than one item being visible
  1262. // implement inside visObserver to ensure there is sufficient delay
  1263. hasFirstShowMore = true;
  1264. requestAnimationFrame(() => {
  1265. // foreground page
  1266. // activeDeferredAppendChild = true;
  1267. // page visibly ready -> load the latest comments at initial loading
  1268. const lcRenderer = lcRendererElm();
  1269. if (lcRenderer) {
  1270. (lcRenderer.inst || lcRenderer).scrollToBottom_();
  1271. }
  1272. });
  1273. }
  1274. }
  1275. else if (target.getAttribute('wSr93') === 'visible') { // ignore target.getAttribute('wSr93') === '' to avoid wrong sizing
  1276.  
  1277. // target.style.setProperty('--wsr94', h + 'px');
  1278. target.setAttribute('wSr93', 'hidden');
  1279. } // note: might consider 0 < entry.intersectionRatio < 0.5 and target.getAttribute('wSr93') === '' <new last item>
  1280.  
  1281. }
  1282.  
  1283. const visObserver = new IntersectionObserver((entries) => {
  1284.  
  1285. for (const entry of entries) {
  1286.  
  1287. Promise.resolve(entry).then(visObserverFn);
  1288.  
  1289. }
  1290.  
  1291. }, {
  1292. /*
  1293. root: items,
  1294. rootMargin: "0px",
  1295. threshold: 1.0,
  1296. */
  1297. // root: HTMLElement.prototype.closest.call(m2, '#item-scroller.yt-live-chat-item-list-renderer'), // nullable
  1298. rootMargin: "0px",
  1299. threshold: [0.05, 0.95],
  1300. });
  1301.  
  1302. //m2.style.visibility='';
  1303.  
  1304. const mutFn = (items) => {
  1305. for (let node = nLastElem(items); node !== null; node = nPrevElem(node)) {
  1306. if (node.hasAttribute('wSr93')) break;
  1307. node.setAttribute('wSr93', '');
  1308. visObserver.observe(node);
  1309. }
  1310. }
  1311.  
  1312. const mutObserver = new MutationObserver((mutations) => {
  1313. const items = (mutations[0] || 0).target;
  1314. if (!items) return;
  1315. mutFn(items);
  1316. });
  1317.  
  1318.  
  1319. // let lzf = 0;
  1320. /*
  1321. const buffObserver = new MutationObserver((mutations) => {
  1322.  
  1323. const buff = (mutations[0] || 0).target;
  1324. if (!buff) return;
  1325.  
  1326. let m2 = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer');
  1327. if(!m2) return;
  1328.  
  1329.  
  1330. let uz = 0;
  1331. for(const mutation of mutations){
  1332. if(mutation.addedNodes){
  1333. for(const node of mutation.addedNodes){
  1334.  
  1335. uz++;
  1336.  
  1337. Promise.resolve(node).then((node) => {
  1338.  
  1339.  
  1340. const placeholder = node.placeHolderAs;
  1341. if (placeholder && placeholder.isConnected) {
  1342. placeholder.descTo = null;
  1343. node.placeHolderAs = null;
  1344.  
  1345. requestAnimationFrame(() => {
  1346. if (placeholder.isConnected && node.isConnected) {
  1347.  
  1348.  
  1349. placeholder.replaceWith(node);
  1350. try {
  1351. placeholder.remove();
  1352. } catch (e) { }
  1353. }
  1354. })
  1355. }
  1356. })
  1357.  
  1358. }
  1359. }
  1360. }
  1361.  
  1362. if(uz===0) return;
  1363.  
  1364.  
  1365. if(lzf>1e9) lzf = 9;
  1366. let tid = ++lzf;
  1367. /|*
  1368.  
  1369. let f = ()=>{
  1370.  
  1371. if(lzf !== tid) return;
  1372. let r = [];
  1373. let remain = false;
  1374. for(let node = buff.firstChild; node !==null ; node=node.nextSibling){
  1375. if(node.mkkReady) r.push(node);
  1376. else remain = true;
  1377. }
  1378.  
  1379. m2.append(...r);
  1380.  
  1381. if(remain) requestAnimationFrame(f);
  1382.  
  1383. };
  1384.  
  1385. requestAnimationFrame(f)
  1386. *|/
  1387. });
  1388. */
  1389.  
  1390. const setupMutObserver = (m2) => {
  1391. mutObserver.disconnect();
  1392. mutObserver.takeRecords();
  1393. if (m2) {
  1394. mutObserver.observe(m2, {
  1395. childList: true,
  1396. subtree: false
  1397. });
  1398. mutFn(m2);
  1399.  
  1400.  
  1401. if(ENABLE_NO_SMOOTH_TRANSFORM){
  1402.  
  1403. let items = m2;
  1404. let addedAnchor = false;
  1405. if(items){
  1406. if(items.nextElementSibling === null){
  1407. items.classList.add('no-anchor');
  1408. addedAnchor = true;
  1409. items.parentNode.appendChild(document.createElement('item-anchor'));
  1410. }
  1411. }
  1412.  
  1413.  
  1414.  
  1415. if(addedAnchor){
  1416. nodeParent(m2).classList.add('no-anchor'); // required
  1417. }
  1418.  
  1419. }
  1420.  
  1421. // let div = document.createElement('div');
  1422. // div.id = 'qwcc';
  1423. // HTMLElement.prototype.appendChild.call(document.querySelector('yt-live-chat-item-list-renderer'), div )
  1424. // bufferRegion =div;
  1425.  
  1426. // buffObserver.takeRecords();
  1427. // buffObserver.disconnect();
  1428. // buffObserver.observe(div, {
  1429. // childList: true,
  1430. // subtree: false
  1431. // })
  1432.  
  1433.  
  1434.  
  1435. }
  1436. }
  1437.  
  1438. setupMutObserver(m2);
  1439.  
  1440. const mclp = getProto(document.querySelector('yt-live-chat-item-list-renderer'));
  1441. if (mclp && mclp.attached) {
  1442.  
  1443. mclp.attached66 = mclp.attached;
  1444. mclp.attached = function () {
  1445. let m2 = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer');
  1446. let m1 = nodeParent(m2);
  1447. setupStyle(m1, m2);
  1448. setupMutObserver(m2);
  1449. return this.attached66();
  1450. }
  1451.  
  1452. mclp.detached66 = mclp.detached;
  1453. mclp.detached = function () {
  1454. setupMutObserver();
  1455. return this.detached66();
  1456. }
  1457.  
  1458. mclp.canScrollToBottomDLW_ = () => !(dateNow() - lastWheel < 80);
  1459.  
  1460. mclp.canScrollToBottom_ = function () {
  1461. return this.atBottom && this.allowScroll && this.canScrollToBottomDLW_();
  1462. }
  1463.  
  1464. if (ENABLE_NO_SMOOTH_TRANSFORM) {
  1465.  
  1466.  
  1467. mclp.isSmoothScrollEnabled_ = function () {
  1468. return false;
  1469. }
  1470.  
  1471.  
  1472.  
  1473. mclp.maybeResizeScrollContainer_ = function () {
  1474. //
  1475. }
  1476.  
  1477. mclp.refreshOffsetContainerHeight_ = function () {
  1478. //
  1479. }
  1480.  
  1481. mclp.smoothScroll_ = function () {
  1482. //
  1483. }
  1484.  
  1485. mclp.resetSmoothScroll_ = function () {
  1486. //
  1487. }
  1488. }
  1489.  
  1490. } else {
  1491. console.warn(`proto.attached for yt-live-chat-item-list-renderer is unavailable.`)
  1492. }
  1493.  
  1494.  
  1495. let scrollCount = 0;
  1496. document.addEventListener('scroll', (evt) => {
  1497. if (!evt || !evt.isTrusted) return;
  1498. // lastScroll = dateNow();
  1499. if (++scrollCount > 1e9) scrollCount = 9;
  1500. }, { passive: true, capture: true }) // support contain => support passive
  1501.  
  1502. // document.addEventListener('scroll', (evt) => {
  1503.  
  1504. // if (!evt || !evt.isTrusted) return;
  1505. // if (!firstVisibleItemDetected) return;
  1506. // const isUserAction = dateNow() - lastWheel < 80; // continuous wheel -> continuous scroll -> continuous wheel -> continuous scroll
  1507. // if (!isUserAction) return;
  1508. // // lastScroll = dateNow();
  1509.  
  1510. // }, { passive: true, capture: true }) // support contain => support passive
  1511.  
  1512.  
  1513. let lastScrollCount = -1;
  1514. document.addEventListener('wheel', (evt) => {
  1515.  
  1516. if (!evt || !evt.isTrusted) return;
  1517. if (lastScrollCount === scrollCount) return;
  1518. lastScrollCount = scrollCount;
  1519. lastWheel = dateNow();
  1520.  
  1521. }, { passive: true, capture: true }) // support contain => support passive
  1522.  
  1523.  
  1524. const fp = (renderer) => {
  1525. const cnt = renderer.inst || renderer;
  1526. const container = (cnt.$ || 0).container;
  1527. if (container) {
  1528. container.setAttribute = tickerContainerSetAttribute;
  1529. }
  1530. }
  1531. const tags = ["yt-live-chat-ticker-paid-message-item-renderer", "yt-live-chat-ticker-paid-sticker-item-renderer",
  1532. "yt-live-chat-ticker-renderer", "yt-live-chat-ticker-sponsor-item-renderer"];
  1533. for (const tag of tags) {
  1534. const dummy = document.createElement(tag);
  1535.  
  1536. const cProto = getProto(dummy);
  1537. if (!cProto || !cProto.attached) {
  1538. console.warn(`proto.attached for ${tag} is unavailable.`)
  1539. continue;
  1540. }
  1541.  
  1542. const __updateTimeout__ = cProto.updateTimeout;
  1543.  
  1544. const canDoUpdateTimeoutReplacement = (() => {
  1545.  
  1546. if (dummy.countdownMs < 1 && dummy.lastCountdownTimeMs < 1 && dummy.countdownMs < 1 && dummy.countdownDurationMs < 1) {
  1547. return typeof dummy.setContainerWidth === 'function' && typeof dummy.slideDown === 'function';
  1548. }
  1549. return false;
  1550.  
  1551. })(dummy.inst || dummy) && ((__updateTimeout__ + "").indexOf("window.requestAnimationFrame(this.updateTimeout.bind(this))") > 0);
  1552.  
  1553.  
  1554.  
  1555. if (canDoUpdateTimeoutReplacement) {
  1556.  
  1557. const killTicker = (cnt) => {
  1558. if ("auto" === cnt.hostElement.style.width) cnt.setContainerWidth();
  1559. cnt.slideDown()
  1560. };
  1561.  
  1562. cProto.__ratio__ = null;
  1563. cProto._updateTimeout21_ = function (a) {
  1564.  
  1565. /*
  1566. let pRatio = this.countdownMs / this.countdownDurationMs;
  1567. this.countdownMs -= (a - (this.lastCountdownTimeMs || 0));
  1568. let noMoreCountDown = this.countdownMs < 1e-6;
  1569. let qRatio = this.countdownMs / this.countdownDurationMs;
  1570. if(noMoreCountDown){
  1571. this.countdownMs = 0;
  1572. this.ratio = 0;
  1573. } else if( pRatio - qRatio < 0.001 && qRatio < pRatio){
  1574.  
  1575. }else{
  1576. this.ratio = qRatio;
  1577. }
  1578. */
  1579.  
  1580. this.countdownMs -= (a - (this.lastCountdownTimeMs || 0));
  1581.  
  1582. let currentRatio = this.__ratio__;
  1583. let tdv = this.countdownMs / this.countdownDurationMs;
  1584. let nextRatio = Math.round(tdv * 500) / 500; // might generate 0.143000000001
  1585.  
  1586. const validCountDown = nextRatio > 0;
  1587. const isAttached = this.isAttached;
  1588.  
  1589. if (!validCountDown) {
  1590.  
  1591. this.lastCountdownTimeMs = null;
  1592.  
  1593. this.countdownMs = 0;
  1594. this.__ratio__ = null;
  1595. this.ratio = 0;
  1596.  
  1597. if (isAttached) Promise.resolve(this).then(killTicker);
  1598.  
  1599. } else if (!isAttached) {
  1600.  
  1601. this.lastCountdownTimeMs = null;
  1602.  
  1603. } else {
  1604.  
  1605. this.lastCountdownTimeMs = a;
  1606.  
  1607. const ratioDiff = currentRatio - nextRatio; // 0.144 - 0.142 = 0.002
  1608. if (ratioDiff < 0.001 && ratioDiff > -1e-6) {
  1609. // ratioDiff = 0
  1610.  
  1611. } else {
  1612. // ratioDiff = 0.002 / 0.004 ....
  1613. // OR ratioDiff < 0
  1614.  
  1615. this.__ratio__ = nextRatio;
  1616.  
  1617. this.ratio = nextRatio;
  1618. }
  1619.  
  1620. return true;
  1621. }
  1622.  
  1623. };
  1624.  
  1625. cProto._updateTimeout21_ = function (a) {
  1626. this.countdownMs = Math.max(0, this.countdownMs - (a - (this.lastCountdownTimeMs || 0)));
  1627. this.ratio = this.countdownMs / this.countdownDurationMs;
  1628. if (this.isAttached && this.countdownMs) {
  1629. this.lastCountdownTimeMs = a;
  1630. return true;
  1631. } else {
  1632. this.lastCountdownTimeMs = null;
  1633. if (this.isAttached) {
  1634. ("auto" === this.hostElement.style.width && this.setContainerWidth(), this.slideDown())
  1635. }
  1636. }
  1637. }
  1638.  
  1639.  
  1640. // temporarily removed; buggy for playback
  1641. /*
  1642. cProto.updateTimeout = async function (a) {
  1643.  
  1644. let ret = this._updateTimeout21_(a);
  1645. while (ret) {
  1646. let a = await new Promise(resolve => {
  1647. this.rafId = requestAnimationFrame(resolve)
  1648. }); // could be never resolve
  1649. ret = this._updateTimeout21_(a);
  1650. }
  1651.  
  1652. };
  1653. */
  1654.  
  1655.  
  1656. }
  1657.  
  1658. cProto.attached77 = cProto.attached
  1659.  
  1660. cProto.attached = function () {
  1661. fp(this.hostElement || this);
  1662. return this.attached77();
  1663. }
  1664.  
  1665. for (const elm of document.getElementsByTagName(tag)) {
  1666. fp(elm);
  1667. }
  1668.  
  1669.  
  1670. }
  1671.  
  1672. };
  1673.  
  1674.  
  1675. function onReady() {
  1676. let tmObserver = new MutationObserver(() => {
  1677.  
  1678. let p = document.getElementById('items'); // fast
  1679. if (!p) return;
  1680. let q = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer'); // check
  1681.  
  1682. if (q) {
  1683. tmObserver.disconnect();
  1684. tmObserver.takeRecords();
  1685. tmObserver = null;
  1686. Promise.resolve(q).then((q) => {
  1687. // confirm Promis.resolve() is resolveable
  1688. // execute main without direct blocking
  1689. main(q);
  1690. })
  1691. }
  1692.  
  1693. });
  1694.  
  1695. tmObserver.observe(document.body || document.documentElement, {
  1696. childList: true,
  1697. subtree: true
  1698. });
  1699.  
  1700. }
  1701.  
  1702. Promise.resolve().then(() => {
  1703.  
  1704. if (document.readyState !== 'loading') {
  1705. onReady();
  1706. } else {
  1707. window.addEventListener("DOMContentLoaded", onReady, false);
  1708. }
  1709.  
  1710. });
  1711.  
  1712. })({ Promise, requestAnimationFrame, IntersectionObserver });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址