YouTube 超快聊天

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

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

  1. // ==UserScript==
  2. // @name YouTube Super Fast Chat
  3. // @version 0.11.0
  4. // @license MIT
  5. // @name:ja YouTube スーパーファーストチャット
  6. // @name:zh-TW YouTube 超快聊天
  7. // @name:zh-CN YouTube 超快聊天
  8. // @icon https://github.com/cyfung1031/userscript-supports/raw/main/icons/super-fast-chat.png
  9. // @namespace UserScript
  10. // @match https://www.youtube.com/live_chat*
  11. // @author CY Fung
  12. // @require https://greasyfork.org/scripts/465819-api-for-customelements-in-youtube/code/API%20for%20CustomElements%20in%20YouTube.js?version=1215280
  13. // @run-at document-start
  14. // @grant none
  15. // @unwrap
  16. // @allFrames true
  17. // @inject-into page
  18. //
  19. // @description Ultimate Performance Boost for YouTube Live Chats
  20. // @description:ja YouTubeのライブチャットの究極のパフォーマンスブースト
  21. // @description:zh-TW YouTube直播聊天的終極性能提升
  22. // @description:zh-CN YouTube直播聊天的终极性能提升
  23. //
  24. // ==/UserScript==
  25.  
  26. ((__CONTEXT__) => {
  27. 'use strict';
  28.  
  29. const ENABLE_REDUCED_MAXITEMS_FOR_FLUSH = true;
  30. const MAX_ITEMS_FOR_TOTAL_DISPLAY = 90;
  31. const MAX_ITEMS_FOR_FULL_FLUSH = 25;
  32.  
  33. const ENABLE_NO_SMOOTH_TRANSFORM = true;
  34. const USE_OPTIMIZED_ON_SCROLL_ITEMS = true;
  35. const USE_WILL_CHANGE_CONTROLLER = false;
  36. const ENABLE_FULL_RENDER_REQUIRED_PREFERRED = true;
  37. const ENABLE_OVERFLOW_ANCHOR_PREFERRED = true;
  38.  
  39. const FIX_SHOW_MORE_BUTTON_LOCATION = true;
  40. const FIX_INPUT_PANEL_OVERFLOW_ISSUE = true;
  41. const FIX_INPUT_PANEL_BORDER_ISSUE = true;
  42. const SET_CONTAIN_FOR_CHATROOM = true;
  43.  
  44. const FORCE_CONTENT_VISIBILITY_UNSET = true;
  45. const FORCE_WILL_CHANGE_UNSET = true;
  46.  
  47. const ENABLE_RAF_HACK_TICKERS = true; // when there is a ticker
  48. const ENABLE_RAF_HACK_DOCKED_MESSAGE = true; // TBC
  49. const ENABLE_RAF_HACK_INPUT_RENDERER = true; // TBC
  50. const ENABLE_RAF_HACK_EMOJI_PICKER = true; // when change the page of emoji picker
  51.  
  52. function dr(s) {
  53. // reserved for future use
  54. return s;
  55. // return window.deWeakJS ? window.deWeakJS(s) : s;
  56. }
  57.  
  58. // necessity of cssText3_smooth_transform_position to be checked.
  59. const cssText3_smooth_transform_position = ENABLE_NO_SMOOTH_TRANSFORM ? `
  60.  
  61. #item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer {
  62. position: static !important;
  63. }
  64.  
  65. `: '';
  66.  
  67. // fallback if dummy style fn fails
  68. const cssText4_smooth_transform_forced_props = ENABLE_NO_SMOOTH_TRANSFORM ? `
  69.  
  70. /* optional */
  71. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  72. height: auto !important;
  73. min-height: unset !important;
  74. }
  75.  
  76. #items.style-scope.yt-live-chat-item-list-renderer {
  77. transform: translateY(0px) !important;
  78. }
  79.  
  80. /* optional */
  81.  
  82. `: '';
  83.  
  84. const cssText5 = SET_CONTAIN_FOR_CHATROOM ? `
  85.  
  86. /* ------------------------------------------------------------------------------------------------------------- */
  87.  
  88. 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 {
  89. contain: layout style;
  90. }
  91.  
  92. #items.style-scope.yt-live-chat-item-list-renderer {
  93. contain: layout paint style;
  94. }
  95.  
  96. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  97. contain: style;
  98. }
  99.  
  100. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  101. contain: size style;
  102. }
  103.  
  104. #contents.style-scope.yt-live-chat-item-list-renderer, #chat.style-scope.yt-live-chat-renderer, img.style-scope.yt-img-shadow[width][height] {
  105. contain: size layout paint style;
  106. }
  107.  
  108. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label], .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label] > #container {
  109. contain: layout paint style;
  110. }
  111.  
  112. 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 {
  113. contain: layout style;
  114. }
  115.  
  116. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  117. contain: layout paint style;
  118. }
  119.  
  120. /* ------------------------------------------------------------------------------------------------------------- */
  121.  
  122. ` : '';
  123.  
  124. const cssText6b_show_more_button = FIX_SHOW_MORE_BUTTON_LOCATION ? `
  125.  
  126. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer{
  127. top: 4px;
  128. transition-property: top;
  129. bottom: unset;
  130. }
  131.  
  132. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer[disabled]{
  133. top: -42px;
  134. }
  135.  
  136. `: '';
  137.  
  138. const cssText6c_input_panel_overflow = FIX_INPUT_PANEL_OVERFLOW_ISSUE ? `
  139.  
  140. #input-panel #picker-buttons yt-live-chat-icon-toggle-button-renderer#product-picker {
  141. contain: layout style;
  142. }
  143.  
  144. #chat.yt-live-chat-renderer ~ #panel-pages.yt-live-chat-renderer {
  145. overflow: visible;
  146. }
  147.  
  148. `: '';
  149.  
  150. const cssText6d_input_panel_border = FIX_INPUT_PANEL_BORDER_ISSUE ? `
  151.  
  152. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer:not(:empty) {
  153. --yt-live-chat-action-panel-top-border: none;
  154. }
  155.  
  156. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer.iron-selected > *:first-child {
  157. border-top: 1px solid var(--yt-live-chat-panel-pages-border-color);
  158. }
  159.  
  160. html #panel-pages.yt-live-chat-renderer {
  161. border-top: 0;
  162. border-bottom: 0;
  163. }
  164.  
  165. `: '';
  166.  
  167. const cssText7b_content_visibility_unset = FORCE_CONTENT_VISIBILITY_UNSET ? `
  168.  
  169. img,
  170. yt-img-shadow[height][width],
  171. yt-img-shadow {
  172. content-visibility: visible !important;
  173. }
  174.  
  175. ` : '';
  176.  
  177. const cssText7c_will_change_unset = FORCE_WILL_CHANGE_UNSET ? `
  178.  
  179. /* remove YouTube constant will-change */
  180. /* constant value will slow down the performance; default auto */
  181.  
  182. /* www-player.css */
  183. html .ytp-contextmenu,
  184. html .ytp-settings-menu {
  185. will-change: unset;
  186. }
  187.  
  188. /* frequently matched elements */
  189. html .fill.yt-interaction,
  190. html .stroke.yt-interaction,
  191. html .yt-spec-touch-feedback-shape__fill,
  192. html .yt-spec-touch-feedback-shape__stroke {
  193. will-change: unset;
  194. }
  195.  
  196. /* live_chat_polymer.js */
  197. /*
  198. html .toggle-button.tp-yt-paper-toggle-button,
  199. html #primaryProgress.tp-yt-paper-progress,
  200. html #secondaryProgress.tp-yt-paper-progress,
  201. html #onRadio.tp-yt-paper-radio-button,
  202. html .fill.yt-interaction,
  203. html .stroke.yt-interaction,
  204. html .yt-spec-touch-feedback-shape__fill,
  205. html .yt-spec-touch-feedback-shape__stroke {
  206. will-change: unset;
  207. }
  208. */
  209.  
  210. /* desktop_polymer_enable_wil_icons.js */
  211. /* html .fill.yt-interaction,
  212. html .stroke.yt-interaction, */
  213. html tp-yt-app-header::before,
  214. html tp-yt-iron-list,
  215. html #items.tp-yt-iron-list > *,
  216. html #onRadio.tp-yt-paper-radio-button,
  217. html .toggle-button.tp-yt-paper-toggle-button,
  218. html ytd-thumbnail-overlay-toggle-button-renderer[use-expandable-tooltip] #label.ytd-thumbnail-overlay-toggle-button-renderer,
  219. html #items.ytd-post-multi-image-renderer,
  220. html #items.ytd-horizontal-card-list-renderer,
  221. html #items.yt-horizontal-list-renderer,
  222. html #left-arrow.yt-horizontal-list-renderer,
  223. html #right-arrow.yt-horizontal-list-renderer,
  224. html #items.ytd-video-description-infocards-section-renderer,
  225. html #items.ytd-video-description-music-section-renderer,
  226. html #chips.ytd-feed-filter-chip-bar-renderer,
  227. html #chips.yt-chip-cloud-renderer,
  228. html #items.ytd-merch-shelf-renderer,
  229. html #items.ytd-product-details-image-carousel-renderer,
  230. html ytd-video-preview,
  231. html #player-container.ytd-video-preview,
  232. html #primaryProgress.tp-yt-paper-progress,
  233. html #secondaryProgress.tp-yt-paper-progress,
  234. html ytd-miniplayer[enabled] /* ,
  235. html .yt-spec-touch-feedback-shape__fill,
  236. html .yt-spec-touch-feedback-shape__stroke */ {
  237. will-change: unset;
  238. }
  239.  
  240. /* other */
  241. .ytp-videowall-still-info-content[class],
  242. .ytp-suggestion-image[class] {
  243. will-change: unset !important;
  244. }
  245.  
  246. ` : '';
  247.  
  248.  
  249. function addCssElement() {
  250. let s = document.createElement('style');
  251. s.id = 'ewRvC';
  252. return s;
  253. }
  254.  
  255. const addCss = () => document.head.appendChild(dr(addCssElement())).textContent = `
  256.  
  257. @supports (contain: layout paint style) {
  258.  
  259. ${cssText5}
  260.  
  261. }
  262.  
  263. @supports (color: var(--general)) {
  264.  
  265. html {
  266. --yt-live-chat-item-list-renderer-padding: 0px 0px;
  267. }
  268.  
  269. ${cssText3_smooth_transform_position}
  270.  
  271. ${cssText7c_will_change_unset}
  272.  
  273. ${cssText7b_content_visibility_unset}
  274.  
  275. yt-live-chat-item-list-renderer:not([allow-scroll]) #item-scroller.yt-live-chat-item-list-renderer {
  276. overflow-y: scroll;
  277. padding-right: 0;
  278. }
  279.  
  280. ${cssText4_smooth_transform_forced_props}
  281.  
  282. yt-icon[icon="down_arrow"] > *, yt-icon-button#show-more > * {
  283. pointer-events: none !important;
  284. }
  285.  
  286. #continuations, #continuations * {
  287. contain: strict;
  288. position: fixed;
  289. top: 2px;
  290. height: 1px;
  291. width: 2px;
  292. height: 1px;
  293. visibility: collapse;
  294. }
  295.  
  296. ${cssText6b_show_more_button}
  297.  
  298. ${cssText6d_input_panel_border}
  299.  
  300. ${cssText6c_input_panel_overflow}
  301.  
  302. }
  303.  
  304.  
  305. @supports (overflow-anchor: auto) {
  306.  
  307. .no-anchor * {
  308. overflow-anchor: none;
  309. }
  310. .no-anchor > item-anchor {
  311. overflow-anchor: auto;
  312. }
  313.  
  314. item-anchor {
  315.  
  316. height:1px;
  317. width: 100%;
  318. transform: scaleY(0.00001);
  319. transform-origin:0 0;
  320. contain: strict;
  321. opacity:0;
  322. display:flex;
  323. position:relative;
  324. flex-shrink:0;
  325. flex-grow:0;
  326. margin-bottom:0;
  327. overflow:hidden;
  328. box-sizing:border-box;
  329. visibility: visible;
  330. content-visibility: visible;
  331. contain-intrinsic-size: auto 1px;
  332. pointer-events:none !important;
  333.  
  334. }
  335.  
  336. #item-scroller.style-scope.yt-live-chat-item-list-renderer[class] {
  337. overflow-anchor: initial !important; /* whenever ENABLE_OVERFLOW_ANCHOR or not */
  338. }
  339.  
  340. html item-anchor {
  341.  
  342. height: 1px;
  343. width: 1px;
  344. top: auto;
  345. left: auto;
  346. right: auto;
  347. bottom: auto;
  348. transform: translateY(-1px);
  349. position: absolute;
  350. z-index: -1;
  351.  
  352. }
  353.  
  354. }
  355.  
  356. @supports (color: var(--pre-rendering)) {
  357.  
  358. @keyframes dontRenderAnimation {
  359. 0% {
  360. background-position-x: 3px;
  361. }
  362. 100% {
  363. background-position-x: 4px;
  364. }
  365. }
  366.  
  367. /*html[dont-render-enabled] */ .dont-render{
  368.  
  369. visibility: collapse !important;
  370. transform: scale(0.01) !important;
  371. transform: scale(0.00001) !important;
  372. transform: scale(0.0000001) !important;
  373. transform-origin: 0 0 !important;
  374. z-index:-1 !important;
  375. contain: strict !important;
  376. box-sizing: border-box !important;
  377.  
  378. height: 1px !important;
  379. height: 0.1px !important;
  380. height: 0.01px !important;
  381. height: 0.0001px !important;
  382. height: 0.000001px !important;
  383.  
  384. animation: dontRenderAnimation 1ms linear 80ms 1 normal forwards !important;
  385.  
  386. }
  387.  
  388. }
  389.  
  390. `;
  391.  
  392.  
  393. const { IntersectionObserver } = __CONTEXT__;
  394.  
  395. /** @type {globalThis.PromiseConstructor} */
  396. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  397.  
  398. if (!IntersectionObserver) return console.error("Your browser does not support IntersectionObserver.\nPlease upgrade to the latest version.")
  399.  
  400. const assertor = (f) => f() || console.assert(false, f + "");
  401.  
  402. const fnIntegrity = (f, d) => {
  403. if (!f || typeof f !== 'function') {
  404. console.warn('f is not a function', f);
  405. return;
  406. }
  407. let p = f + "", s = 0, j = -1, w = 0;
  408. for (let i = 0, l = p.length; i < l; i++) {
  409. const t = p[i];
  410. if (((t >= 'a' && t <= 'z') || (t >= 'A' && t <= 'Z'))) {
  411. if (j < i - 1) w++;
  412. j = i;
  413. } else {
  414. s++;
  415. }
  416. }
  417. let itz = `${f.length}.${s}.${w}`;
  418. if (!d) {
  419. console.log(itz);
  420. return null;
  421. } else {
  422. return itz === d;
  423. }
  424. }
  425.  
  426.  
  427. let ENABLE_FULL_RENDER_REQUIRED_CAPABLE = false;
  428. const isContainSupport = CSS.supports('contain', 'layout paint style');
  429. if (!isContainSupport) {
  430. console.warn("Your browser does not support css property 'contain'.\nPlease upgrade to the latest version.".trim());
  431. } else {
  432.  
  433. ENABLE_FULL_RENDER_REQUIRED_CAPABLE = true; // mainly for Chromium-based browsers
  434.  
  435. }
  436.  
  437.  
  438.  
  439. let ENABLE_OVERFLOW_ANCHOR_CAPABLE = false;
  440. const isOverflowAnchorSupport = CSS.supports('overflow-anchor', 'auto');
  441. if (!isOverflowAnchorSupport) {
  442. console.warn("Your browser does not support css property 'overflow-anchor'.\nPlease upgrade to the latest version.".trim());
  443. } else {
  444.  
  445. ENABLE_OVERFLOW_ANCHOR_CAPABLE = true; // mainly for Chromium-based browsers
  446.  
  447. }
  448.  
  449.  
  450. const ENABLE_OVERFLOW_ANCHOR = ENABLE_OVERFLOW_ANCHOR_PREFERRED && ENABLE_OVERFLOW_ANCHOR_CAPABLE && ENABLE_NO_SMOOTH_TRANSFORM;
  451.  
  452. const NOT_FIREFOX = !CSS.supports('-moz-appearance', 'none'); // 1. Firefox does not have the flicking issue; 2. Firefox's OVERFLOW_ANCHOR does not work very well as chromium.
  453.  
  454. const ENABLE_FULL_RENDER_REQUIRED = ENABLE_FULL_RENDER_REQUIRED_PREFERRED && ENABLE_FULL_RENDER_REQUIRED_CAPABLE && ENABLE_OVERFLOW_ANCHOR && ENABLE_NO_SMOOTH_TRANSFORM && NOT_FIREFOX;
  455.  
  456.  
  457.  
  458. const win = this instanceof Window ? this : window;
  459.  
  460. // Create a unique key for the script and check if it is already running
  461. const hkey_script = 'mchbwnoasqph';
  462. if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  463. win[hkey_script] = true;
  464.  
  465. const cleanContext = async (win) => {
  466. const waitFn = requestAnimationFrame; // shall have been binded to window
  467. try {
  468. let mx = 16; // MAX TRIAL
  469. const frameId = 'vanillajs-iframe-v1';
  470. /** @type {HTMLIFrameElement | null} */
  471. let frame = document.getElementById(frameId);
  472. let removeIframeFn = null;
  473. if (!frame) {
  474. frame = document.createElement('iframe');
  475. frame.id = 'vanillajs-iframe-v1';
  476. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  477. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  478. n.appendChild(frame);
  479. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  480. const root = document.documentElement;
  481. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  482. removeIframeFn = (setTimeout) => {
  483. const removeIframeOnDocumentReady = (e) => {
  484. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  485. win = null;
  486. setTimeout(() => {
  487. n.remove();
  488. n = null;
  489. }, 200);
  490. }
  491. if (document.readyState !== 'loading') {
  492. removeIframeOnDocumentReady();
  493. } else {
  494. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  495. }
  496. }
  497. }
  498. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  499. const fc = frame.contentWindow;
  500. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  501. const { requestAnimationFrame, setTimeout, cancelAnimationFrame } = fc;
  502. const res = { requestAnimationFrame, setTimeout, cancelAnimationFrame };
  503. for (let k in res) res[k] = res[k].bind(win); // necessary
  504. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  505. return res;
  506. } catch (e) {
  507. console.warn(e);
  508. return null;
  509. }
  510. };
  511.  
  512. cleanContext(win).then(__CONTEXT__ => {
  513. if (!__CONTEXT__) return null;
  514.  
  515.  
  516. const { requestAnimationFrame, setTimeout, cancelAnimationFrame } = __CONTEXT__;
  517.  
  518.  
  519. class RAFHub {
  520. constructor() {
  521. /** @type {number} */
  522. this.startAt = 8170;
  523. /** @type {number} */
  524. this.counter = 0;
  525. /** @type {number} */
  526. this.rid = 0;
  527. /** @type {Map<number, FrameRequestCallback>} */
  528. this.funcs = new Map();
  529. const funcs = this.funcs;
  530. /** @type {FrameRequestCallback} */
  531. this.bCallback = this.mCallback.bind(this);
  532. this.pClear = () => funcs.clear();
  533. }
  534. /** @param {DOMHighResTimeStamp} highResTime */
  535. mCallback(highResTime) {
  536. this.rid = 0;
  537. Promise.resolve().then(this.pClear);
  538. this.funcs.forEach(func => Promise.resolve(highResTime).then(func).catch(console.warn));
  539. }
  540. /** @param {FrameRequestCallback} f */
  541. request(f) {
  542. if (this.counter > 1e9) this.counter = 9;
  543. let cid = this.startAt + (++this.counter);
  544. this.funcs.set(cid, f);
  545. if (this.rid === 0) this.rid = requestAnimationFrame(this.bCallback);
  546. return cid;
  547. }
  548. /** @param {number} cid */
  549. cancel(cid) {
  550. cid = +cid;
  551. if (cid > 0) {
  552. if (cid <= this.startAt) {
  553. return cancelAnimationFrame(cid);
  554. }
  555. if (this.rid > 0) {
  556. this.funcs.delete(cid);
  557. if (this.funcs.size === 0) {
  558. cancelAnimationFrame(this.rid);
  559. this.rid = 0;
  560. }
  561. }
  562. }
  563. }
  564. }
  565.  
  566.  
  567. ENABLE_FULL_RENDER_REQUIRED && (() => {
  568.  
  569. document.addEventListener('animationstart', (evt) => {
  570.  
  571. if (evt.animationName === 'dontRenderAnimation') {
  572. evt.target.classList.remove('dont-render');
  573. if (scrollChatFn) scrollChatFn();
  574. }
  575. }, true);
  576.  
  577. const f = (elm) => {
  578. if (elm && elm.nodeType === 1) {
  579. elm.classList.add('dont-render');
  580. }
  581. }
  582.  
  583. Node.prototype.__appendChild931__ = function (a) {
  584. a = dr(a);
  585. if (this.id === 'items' && this.classList.contains('yt-live-chat-item-list-renderer')) {
  586. if (a && a.nodeType === 1) f(a);
  587. else if (a instanceof DocumentFragment) {
  588. for (let n = a.firstChild; n; n = n.nextSibling) {
  589. f(n);
  590. }
  591. }
  592. }
  593. }
  594.  
  595. Node.prototype.__appendChild932__ = function () {
  596. this.__appendChild931__.apply(this, arguments);
  597. return Node.prototype.appendChild.apply(this, arguments);
  598. }
  599.  
  600.  
  601. })();
  602.  
  603. // let delayedAppendParentWS = new WeakSet();
  604. // let delayedAppendOperations = [];
  605. // let commonAppendParentStackSet = new Set();
  606.  
  607. // let firstVisibleItemDetected = false; // deprecated
  608.  
  609. const sp7 = Symbol();
  610.  
  611.  
  612. let dt0 = Date.now() - 2000;
  613. const dateNow = () => Date.now() - dt0;
  614. // let lastScroll = 0;
  615. // let lastLShow = 0;
  616. let lastWheel = 0;
  617. let lastMouseDown = 0;
  618. let lastMouseUp = 0;
  619. let currentMouseDown = false;
  620. let lastTouchDown = 0;
  621. let lastTouchUp = 0;
  622. let currentTouchDown = false;
  623. let lastUserInteraction = 0;
  624.  
  625. let scrollChatFn = null;
  626.  
  627. const proxyHelperFn = (dummy) => ({
  628.  
  629. get(target, prop) {
  630. return (prop in dummy) ? dummy[prop] : prop === sp7 ? target : target[prop];
  631. },
  632. set(target, prop, value) {
  633. if (!(prop in dummy)) {
  634. target[prop] = value;
  635. }
  636. return true;
  637. },
  638. has(target, prop) {
  639. return (prop in target);
  640. },
  641. deleteProperty(target, prop) {
  642. return true;
  643. },
  644. ownKeys(target) {
  645. return Object.keys(target);
  646. },
  647. defineProperty(target, key, descriptor) {
  648. return Object.defineProperty(target, key, descriptor);
  649. },
  650. getOwnPropertyDescriptor(target, key) {
  651. return Object.getOwnPropertyDescriptor(target, key);
  652. },
  653.  
  654. });
  655.  
  656. const tickerContainerSetAttribute = function (attrName, attrValue) { // ensure '14.30000001%'.toFixed(1)
  657.  
  658. let yd = (this.__dataHost || (this.inst || 0).__dataHost).__data;
  659.  
  660. if (arguments.length === 2 && attrName === 'style' && yd && attrValue) {
  661.  
  662. // let v = yd.containerStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
  663. let v = `${attrValue}`;
  664. // conside a ticker is 101px width
  665. // 1% = 1.01px
  666. // 0.2% = 0.202px
  667.  
  668.  
  669. const ratio1 = (yd.ratio * 100);
  670. if (ratio1 > -1) { // avoid NaN
  671.  
  672. // countdownDurationMs
  673. // 600000 - 0.2% <1% = 6s> <0.2% = 1.2s>
  674. // 300000 - 0.5% <1% = 3s> <0.5% = 1.5s>
  675. // 150000 - 1% <1% = 1.5s>
  676. // 75000 - 2% <1% =0.75s > <2% = 1.5s>
  677. // 30000 - 5% <1% =0.3s > <5% = 1.5s>
  678.  
  679. // 99px * 5% = 4.95px
  680.  
  681. // 15000 - 10% <1% =0.15s > <10% = 1.5s>
  682.  
  683.  
  684.  
  685.  
  686. // 1% Duration
  687.  
  688. let ratio2 = ratio1;
  689.  
  690. const ydd = yd.data;
  691. const d1 = ydd.durationSec;
  692. const d2 = ydd.fullDurationSec;
  693.  
  694. if (d1 === d2 && d1 > 1) {
  695.  
  696. if (d1 > 400) ratio2 = Math.round(ratio2 * 5) / 5; // 0.2%
  697. else if (d1 > 200) ratio2 = Math.round(ratio2 * 2) / 2; // 0.5%
  698. else if (d1 > 100) ratio2 = Math.round(ratio2 * 1) / 1; // 1%
  699. else if (d1 > 50) ratio2 = Math.round(ratio2 * 0.5) / 0.5; // 2%
  700. else if (d1 > 25) ratio2 = Math.round(ratio2 * 0.2) / 0.2; // 5% (max => 99px * 5% = 4.95px)
  701. else ratio2 = Math.round(ratio2 * 0.2) / 0.2;
  702.  
  703. } else {
  704. ratio2 = Math.round(ratio2 * 5) / 5; // 0.2% (min)
  705. }
  706.  
  707. // ratio2 = Math.round(ratio2 * 5) / 5;
  708. ratio2 = ratio2.toFixed(1);
  709. v = v.replace(`${ratio1}%`, `${ratio2}%`).replace(`${ratio1}%`, `${ratio2}%`);
  710.  
  711. if (yd.__style_last__ === v) return;
  712. yd.__style_last__ = v;
  713. // do not consider any delay here.
  714. // it shall be inside the looping for all properties changes. all the css background ops are in the same microtask.
  715.  
  716. }
  717.  
  718. HTMLElement.prototype.setAttribute.call(dr(this), attrName, v);
  719.  
  720.  
  721. } else {
  722. HTMLElement.prototype.setAttribute.apply(dr(this), arguments);
  723. }
  724.  
  725. };
  726.  
  727. const fxOperator = (proto, propertyName) => {
  728. let propertyDescriptorGetter = null;
  729. try {
  730. propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
  731. } catch (e) { }
  732. return typeof propertyDescriptorGetter === 'function' ? (e) => {
  733. try {
  734.  
  735. return propertyDescriptorGetter.call(dr(e));
  736. } catch (e) { }
  737. return e[propertyName];
  738. } : (e) => e[propertyName];
  739. };
  740.  
  741. const nodeParent = fxOperator(Node.prototype, 'parentNode');
  742. // const nFirstElem = fxOperator(HTMLElement.prototype, 'firstElementChild');
  743. const nPrevElem = fxOperator(HTMLElement.prototype, 'previousElementSibling');
  744. const nNextElem = fxOperator(HTMLElement.prototype, 'nextElementSibling');
  745. const nLastElem = fxOperator(HTMLElement.prototype, 'lastElementChild');
  746.  
  747.  
  748. /* globals WeakRef:false */
  749.  
  750. /** @type {(o: Object | null) => WeakRef | null} */
  751. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  752.  
  753. /** @type {(wr: Object | null) => Object | null} */
  754. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  755.  
  756. const watchUserCSS = () => {
  757.  
  758. // if (!CSS.supports('contain-intrinsic-size', 'auto var(--wsr94)')) return;
  759.  
  760. const getElemFromWR = (nr) => {
  761. const n = kRef(nr);
  762. if (n && n.isConnected) return n;
  763. return null;
  764. }
  765.  
  766. const clearContentVisibilitySizing = () => {
  767. Promise.resolve().then(() => {
  768.  
  769. let btnShowMoreWR = mWeakRef(document.querySelector('#show-more[disabled]'));
  770.  
  771. let lastVisibleItemWR = null;
  772. for (const elm of document.querySelectorAll('[wSr93]')) {
  773. if (elm.getAttribute('wSr93') === 'visible') lastVisibleItemWR = mWeakRef(elm);
  774. elm.setAttribute('wSr93', '');
  775. // custom CSS property --wsr94 not working when attribute wSr93 removed
  776. }
  777. requestAnimationFrame(() => {
  778. const btnShowMore = getElemFromWR(btnShowMoreWR); btnShowMoreWR = null;
  779. if (btnShowMore) btnShowMore.click();
  780. else {
  781. // would not work if switch it frequently
  782. const lastVisibleItem = getElemFromWR(lastVisibleItemWR); lastVisibleItemWR = null;
  783. if (lastVisibleItem) {
  784.  
  785. Promise.resolve()
  786. .then(() => lastVisibleItem.scrollIntoView())
  787. .then(() => lastVisibleItem.scrollIntoView(false))
  788. .then(() => lastVisibleItem.scrollIntoView({ behavior: "instant", block: "end", inline: "nearest" }))
  789. .catch(e => { }) // break the chain when method not callable
  790.  
  791. }
  792. }
  793. });
  794.  
  795. });
  796.  
  797. }
  798.  
  799. const mutObserver = new MutationObserver((mutations) => {
  800. for (const mutation of mutations) {
  801. if ((mutation.addedNodes || 0).length >= 1) {
  802. for (const addedNode of mutation.addedNodes) {
  803. if (addedNode.nodeName === 'STYLE') {
  804. clearContentVisibilitySizing();
  805. return;
  806. }
  807. }
  808. }
  809. if ((mutation.removedNodes || 0).length >= 1) {
  810. for (const removedNode of mutation.removedNodes) {
  811. if (removedNode.nodeName === 'STYLE') {
  812. clearContentVisibilitySizing();
  813. return;
  814. }
  815. }
  816. }
  817. }
  818. });
  819.  
  820. mutObserver.observe(document.documentElement, {
  821. childList: true,
  822. subtree: false
  823. });
  824. mutObserver.observe(document.head, {
  825. childList: true,
  826. subtree: false
  827. });
  828. mutObserver.observe(document.body, {
  829. childList: true,
  830. subtree: false
  831. });
  832.  
  833. }
  834.  
  835. const setupStyle = (m1, m2) => {
  836. if (!ENABLE_NO_SMOOTH_TRANSFORM) return;
  837.  
  838. const dummy1v = {
  839. transform: '',
  840. height: '',
  841. minHeight: '',
  842. paddingBottom: '',
  843. paddingTop: ''
  844. };
  845.  
  846. const dummyStyleFn = (k) => (function () { const style = this[sp7]; return style[k](...arguments); });
  847. for (const k of ['toString', 'getPropertyPriority', 'getPropertyValue', 'item', 'removeProperty', 'setProperty']) {
  848. dummy1v[k] = dummyStyleFn(k);
  849. }
  850.  
  851. const dummy1p = proxyHelperFn(dummy1v);
  852. const sp1v = new Proxy(m1.style, dummy1p);
  853. const sp2v = new Proxy(m2.style, dummy1p);
  854. Object.defineProperty(m1, 'style', { get() { return sp1v }, set() { }, enumerable: true, configurable: true });
  855. Object.defineProperty(m2, 'style', { get() { return sp2v }, set() { }, enumerable: true, configurable: true });
  856. m1.removeAttribute("style");
  857. m2.removeAttribute("style");
  858.  
  859. }
  860.  
  861.  
  862. class WillChangeController {
  863. constructor(itemScroller, willChangeValue) {
  864. this.element = itemScroller;
  865. this.counter = 0;
  866. this.active = false;
  867. this.willChangeValue = willChangeValue;
  868. }
  869.  
  870. beforeOper() {
  871. if (!this.active) {
  872. this.active = true;
  873. this.element.style.willChange = this.willChangeValue;
  874. }
  875. this.counter++;
  876. }
  877.  
  878. afterOper() {
  879. const c = this.counter;
  880. requestAnimationFrame(() => {
  881. if (c === this.counter) {
  882. this.active = false;
  883. this.element.style.willChange = '';
  884. }
  885. });
  886. }
  887.  
  888. release() {
  889. const element = this.element;
  890. this.element = null;
  891. this.counter = 1e16;
  892. this.active = false;
  893. try {
  894. element.style.willChange = '';
  895. } catch (e) { }
  896. }
  897.  
  898. }
  899.  
  900.  
  901. const rafHub = (ENABLE_RAF_HACK_TICKERS || ENABLE_RAF_HACK_DOCKED_MESSAGE || ENABLE_RAF_HACK_INPUT_RENDERER || ENABLE_RAF_HACK_EMOJI_PICKER) ? new RAFHub() : null;
  902.  
  903.  
  904.  
  905. const { lcRendererElm, visObserver } = (() => {
  906.  
  907.  
  908.  
  909. let lcRendererWR = null;
  910.  
  911. const lcRendererElm = () => {
  912. let lcRenderer = kRef(lcRendererWR);
  913. if (!lcRenderer || !lcRenderer.isConnected) {
  914. lcRenderer = document.querySelector('yt-live-chat-item-list-renderer.yt-live-chat-renderer');
  915. lcRendererWR = lcRenderer ? mWeakRef(lcRenderer) : null;
  916. }
  917. return lcRenderer;
  918. };
  919.  
  920.  
  921. let hasFirstShowMore = false;
  922.  
  923. const visObserverFn = (entry) => {
  924.  
  925. const target = entry.target;
  926. if (!target) return;
  927. // if(target.classList.contains('dont-render')) return;
  928. let isVisible = entry.isIntersecting === true && entry.intersectionRatio > 0.5;
  929. // const h = entry.boundingClientRect.height;
  930. /*
  931. if (h < 16) { // wrong: 8 (padding/margin); standard: 32; test: 16 or 20
  932. // e.g. under fullscreen. the element created but not rendered.
  933. target.setAttribute('wSr93', '');
  934. return;
  935. }
  936. */
  937. if (isVisible) {
  938. // target.style.setProperty('--wsr94', h + 'px');
  939. target.setAttribute('wSr93', 'visible');
  940. if (nNextElem(target) === null) {
  941.  
  942. // firstVisibleItemDetected = true;
  943. /*
  944. if (dateNow() - lastScroll < 80) {
  945. lastLShow = 0;
  946. lastScroll = 0;
  947. Promise.resolve().then(clickShowMore);
  948. } else {
  949. lastLShow = dateNow();
  950. }
  951. */
  952. // lastLShow = dateNow();
  953. } else if (!hasFirstShowMore) { // should more than one item being visible
  954. // implement inside visObserver to ensure there is sufficient delay
  955. hasFirstShowMore = true;
  956. requestAnimationFrame(() => {
  957. // foreground page
  958. // page visibly ready -> load the latest comments at initial loading
  959. const lcRenderer = lcRendererElm();
  960. if (lcRenderer) {
  961. (lcRenderer.inst || lcRenderer).scrollToBottom_();
  962. }
  963. });
  964. }
  965. }
  966. else if (target.getAttribute('wSr93') === 'visible') { // ignore target.getAttribute('wSr93') === '' to avoid wrong sizing
  967.  
  968. // target.style.setProperty('--wsr94', h + 'px');
  969. target.setAttribute('wSr93', 'hidden');
  970. } // note: might consider 0 < entry.intersectionRatio < 0.5 and target.getAttribute('wSr93') === '' <new last item>
  971.  
  972. }
  973.  
  974.  
  975.  
  976. const visObserver = new IntersectionObserver((entries) => {
  977.  
  978. for (const entry of entries) {
  979.  
  980. Promise.resolve(entry).then(visObserverFn);
  981.  
  982. }
  983.  
  984. }, {
  985. // root: HTMLElement.prototype.closest.call(m2, '#item-scroller.yt-live-chat-item-list-renderer'), // nullable
  986. rootMargin: "0px",
  987. threshold: [0.05, 0.95],
  988. });
  989.  
  990.  
  991. return { lcRendererElm, visObserver }
  992.  
  993.  
  994. })();
  995.  
  996. const { setupMutObserver } = (() => {
  997.  
  998. const mutFn = (items) => {
  999. for (let node = nLastElem(items); node !== null; node = nPrevElem(node)) {
  1000. if (node.hasAttribute('wSr93')) break;
  1001. node.setAttribute('wSr93', '');
  1002. visObserver.observe(node);
  1003. }
  1004. }
  1005.  
  1006. const mutObserver = new MutationObserver((mutations) => {
  1007. const items = (mutations[0] || 0).target;
  1008. if (!items) return;
  1009. mutFn(items);
  1010. });
  1011.  
  1012. const setupMutObserver = (m2) => {
  1013. scrollChatFn = null;
  1014. mutObserver.disconnect();
  1015. mutObserver.takeRecords();
  1016. if (m2) {
  1017. if (typeof m2.__appendChild932__ === 'function') {
  1018. if (typeof m2.appendChild === 'function') m2.appendChild = m2.__appendChild932__;
  1019. if (typeof m2.__shady_native_appendChild === 'function') m2.__shady_native_appendChild = m2.__appendChild932__;
  1020. }
  1021. mutObserver.observe(m2, {
  1022. childList: true,
  1023. subtree: false
  1024. });
  1025. mutFn(m2);
  1026.  
  1027.  
  1028. if (ENABLE_OVERFLOW_ANCHOR) {
  1029.  
  1030. let items = m2;
  1031. let addedAnchor = false;
  1032. if (items) {
  1033. if (items.nextElementSibling === null) {
  1034. items.classList.add('no-anchor');
  1035. addedAnchor = true;
  1036. items.parentNode.appendChild(dr(document.createElement('item-anchor')));
  1037. }
  1038. }
  1039.  
  1040.  
  1041.  
  1042. if (addedAnchor) {
  1043. nodeParent(m2).classList.add('no-anchor'); // required
  1044. }
  1045.  
  1046. }
  1047.  
  1048. // let div = document.createElement('div');
  1049. // div.id = 'qwcc';
  1050. // HTMLElement.prototype.appendChild.call(document.querySelector('yt-live-chat-item-list-renderer'), div )
  1051. // bufferRegion =div;
  1052.  
  1053. // buffObserver.takeRecords();
  1054. // buffObserver.disconnect();
  1055. // buffObserver.observe(div, {
  1056. // childList: true,
  1057. // subtree: false
  1058. // })
  1059.  
  1060.  
  1061.  
  1062. }
  1063. }
  1064.  
  1065. return { setupMutObserver };
  1066.  
  1067.  
  1068.  
  1069. })();
  1070.  
  1071. const setupEvents = () => {
  1072.  
  1073.  
  1074. let scrollCount = 0;
  1075.  
  1076. const passiveCapture = typeof IntersectionObserver === 'function' ? { capture: true, passive: true } : true;
  1077.  
  1078.  
  1079. const delayFlushActiveItemsAfterUserActionK_ = () => {
  1080.  
  1081. const lcRenderer = lcRendererElm();
  1082. if (lcRenderer) {
  1083. const cnt = (lcRenderer.inst || lcRenderer);
  1084. if (!cnt.hasUserJustInteracted11_) return;
  1085. if (cnt.atBottom && cnt.allowScroll && cnt.activeItems_.length >= 1 && cnt.hasUserJustInteracted11_()) {
  1086. cnt.delayFlushActiveItemsAfterUserAction11_ && cnt.delayFlushActiveItemsAfterUserAction11_();
  1087. }
  1088. }
  1089.  
  1090. }
  1091.  
  1092. document.addEventListener('scroll', (evt) => {
  1093. if (!evt || !evt.isTrusted) return;
  1094. // lastScroll = dateNow();
  1095. if (++scrollCount > 1e9) scrollCount = 9;
  1096. }, passiveCapture); // support contain => support passive
  1097.  
  1098. let lastScrollCount = -1;
  1099. document.addEventListener('wheel', (evt) => {
  1100. if (!evt || !evt.isTrusted) return;
  1101. if (lastScrollCount === scrollCount) return;
  1102. lastScrollCount = scrollCount;
  1103. lastWheel = dateNow();
  1104. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1105. }, passiveCapture); // support contain => support passive
  1106.  
  1107. document.addEventListener('mousedown', (evt) => {
  1108. if (!evt || !evt.isTrusted) return;
  1109. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1110. lastMouseDown = dateNow();
  1111. currentMouseDown = true;
  1112. lastUserInteraction = lastMouseDown;
  1113. }, passiveCapture);
  1114.  
  1115. document.addEventListener('pointerdown', (evt) => {
  1116. if (!evt || !evt.isTrusted) return;
  1117. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1118. lastMouseDown = dateNow();
  1119. currentMouseDown = true;
  1120. lastUserInteraction = lastMouseDown;
  1121. }, passiveCapture);
  1122.  
  1123. document.addEventListener('click', (evt) => {
  1124. if (!evt || !evt.isTrusted) return;
  1125. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1126. lastMouseDown = lastMouseUp = dateNow();
  1127. currentMouseDown = false;
  1128. lastUserInteraction = lastMouseDown;
  1129. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1130. }, passiveCapture);
  1131.  
  1132. document.addEventListener('tap', (evt) => {
  1133. if (!evt || !evt.isTrusted) return;
  1134. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1135. lastMouseDown = lastMouseUp = dateNow();
  1136. currentMouseDown = false;
  1137. lastUserInteraction = lastMouseDown;
  1138. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1139. }, passiveCapture);
  1140.  
  1141.  
  1142. document.addEventListener('mouseup', (evt) => {
  1143. if (!evt || !evt.isTrusted) return;
  1144. if (currentMouseDown) {
  1145. lastMouseUp = dateNow();
  1146. currentMouseDown = false;
  1147. lastUserInteraction = lastMouseUp;
  1148. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1149. }
  1150. }, passiveCapture);
  1151.  
  1152.  
  1153. document.addEventListener('pointerup', (evt) => {
  1154. if (!evt || !evt.isTrusted) return;
  1155. if (currentMouseDown) {
  1156. lastMouseUp = dateNow();
  1157. currentMouseDown = false;
  1158. lastUserInteraction = lastMouseUp;
  1159. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1160. }
  1161. }, passiveCapture);
  1162.  
  1163. document.addEventListener('touchstart', (evt) => {
  1164. if (!evt || !evt.isTrusted) return;
  1165. lastTouchDown = dateNow();
  1166. currentTouchDown = true;
  1167. lastUserInteraction = lastTouchDown;
  1168. }, passiveCapture);
  1169.  
  1170. document.addEventListener('touchmove', (evt) => {
  1171. if (!evt || !evt.isTrusted) return;
  1172. lastTouchDown = dateNow();
  1173. currentTouchDown = true;
  1174. lastUserInteraction = lastTouchDown;
  1175. }, passiveCapture);
  1176.  
  1177. document.addEventListener('touchend', (evt) => {
  1178. if (!evt || !evt.isTrusted) return;
  1179. if (currentTouchDown) {
  1180. lastTouchUp = dateNow();
  1181. currentTouchDown = false;
  1182. lastUserInteraction = lastTouchUp;
  1183. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1184. }
  1185. }, passiveCapture);
  1186.  
  1187. document.addEventListener('touchcancel', (evt) => {
  1188. if (!evt || !evt.isTrusted) return;
  1189. if (currentTouchDown) {
  1190. lastTouchUp = dateNow();
  1191. currentTouchDown = false;
  1192. lastUserInteraction = lastTouchUp;
  1193. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1194. }
  1195. }, passiveCapture);
  1196.  
  1197.  
  1198. }
  1199.  
  1200.  
  1201.  
  1202. const getProto = (element) => {
  1203. if (element) {
  1204. const cnt = element.inst || element;
  1205. return cnt.constructor.prototype || null;
  1206. }
  1207. return null;
  1208. }
  1209.  
  1210.  
  1211. customYtElements.onRegistryReady(() => {
  1212.  
  1213.  
  1214.  
  1215.  
  1216. customElements.whenDefined('yt-live-chat-item-list-renderer').then(() => {
  1217.  
  1218.  
  1219. const tag = "yt-live-chat-item-list-renderer"
  1220. const dummy = document.createElement(tag);
  1221.  
  1222.  
  1223. const cProto = getProto(dummy);
  1224. if (!cProto || !cProto.attached) {
  1225. console.warn(`proto.attached for ${tag} is unavailable.`);
  1226. return;
  1227. }
  1228.  
  1229.  
  1230.  
  1231. console.groupCollapsed("YouTube Super Fast Check - yt-live-chat-item-list-renderer hacks");
  1232.  
  1233. const mclp = cProto;
  1234. try {
  1235. assertor(() => typeof mclp.scrollToBottom_ === 'function');
  1236. assertor(() => typeof mclp.flushActiveItems_ === 'function');
  1237. assertor(() => typeof mclp.canScrollToBottom_ === 'function');
  1238. assertor(() => typeof mclp.setAtBottom === 'function');
  1239. assertor(() => typeof mclp.scrollToBottom66_ === 'undefined');
  1240. assertor(() => typeof mclp.flushActiveItems66_ === 'undefined');
  1241. } catch (e) { }
  1242.  
  1243.  
  1244. try {
  1245. assertor(() => typeof mclp.attached === 'function');
  1246. assertor(() => typeof mclp.detached === 'function');
  1247. assertor(() => typeof mclp.canScrollToBottom_ === 'function');
  1248. assertor(() => typeof mclp.isSmoothScrollEnabled_ === 'function');
  1249. assertor(() => typeof mclp.maybeResizeScrollContainer_ === 'function');
  1250. assertor(() => typeof mclp.refreshOffsetContainerHeight_ === 'function');
  1251. assertor(() => typeof mclp.smoothScroll_ === 'function');
  1252. assertor(() => typeof mclp.resetSmoothScroll_ === 'function');
  1253. } catch (e) { }
  1254.  
  1255. mclp.__intermediate_delay__ = null;
  1256.  
  1257. let mzk = 0;
  1258. let myk = 0;
  1259. let mlf = 0;
  1260. let myw = 0;
  1261. let mzt = 0;
  1262. let zarr = null;
  1263. let mlg = 0;
  1264.  
  1265. if ((mclp.clearList || 0).length === 0) {
  1266. assertor(() => fnIntegrity(mclp.clearList, '0.106.50'));
  1267. mclp.clearList66 = mclp.clearList;
  1268. mclp.clearList = function () {
  1269. mzk++;
  1270. myk++;
  1271. mlf++;
  1272. myw++;
  1273. mzt++;
  1274. mlg++;
  1275. zarr = null;
  1276. this.__intermediate_delay__ = null;
  1277. this.clearList66();
  1278. };
  1279. console.log("clearList", "OK");
  1280. } else {
  1281. console.log("clearList", "NG");
  1282. }
  1283.  
  1284.  
  1285. let onListRendererAttachedDone = false;
  1286.  
  1287. function setList(itemOffset, items){
  1288.  
  1289. const isFirstTime = onListRendererAttachedDone === false;
  1290.  
  1291. if (isFirstTime) {
  1292. onListRendererAttachedDone = true;
  1293. Promise.resolve().then(watchUserCSS);
  1294. addCss();
  1295. setupEvents();
  1296. }
  1297.  
  1298. setupStyle(itemOffset, items);
  1299.  
  1300. setupMutObserver(items);
  1301. }
  1302.  
  1303. mclp.attached419 = async function () {
  1304.  
  1305. let maxTrial = 16;
  1306. while (!this.$ || !this.$['item-scroller'] || !this.$['item-offset'] || !this.$['items']) {
  1307. if (--maxTrial < 0) return;
  1308. await new Promise(requestAnimationFrame);
  1309. }
  1310.  
  1311.  
  1312. if (!this.$) {
  1313. console.warn("!this.$");
  1314. return;
  1315. }
  1316. if (!this.$) return;
  1317. /** @type {HTMLElement | null} */
  1318. const itemScroller = this.$['item-scroller'];
  1319. /** @type {HTMLElement | null} */
  1320. const itemOffset = this.$['item-offset'];
  1321. /** @type {HTMLElement | null} */
  1322. const items = this.$['items'];
  1323.  
  1324. if (!itemScroller || !itemOffset || !items) {
  1325. console.warn("items.parentNode !== itemOffset");
  1326. return;
  1327. }
  1328.  
  1329. if (nodeParent(items) !== itemOffset) {
  1330.  
  1331. console.warn("items.parentNode !== itemOffset");
  1332. return;
  1333. }
  1334.  
  1335.  
  1336. if (items.id !== 'items' || itemOffset.id !== "item-offset") {
  1337.  
  1338. console.warn("id incorrect");
  1339. return;
  1340. }
  1341.  
  1342. const isTargetItems = HTMLElement.prototype.matches.call(items, '#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer')
  1343.  
  1344. if(!isTargetItems){
  1345. console.warn("!isTargetItems");
  1346. return;
  1347. }
  1348.  
  1349. setList(itemOffset, items);
  1350.  
  1351. }
  1352.  
  1353. mclp.attached331 = mclp.attached;
  1354. mclp.attached = function () {
  1355. this.attached419 && this.attached419();
  1356. return this.attached331();
  1357. }
  1358.  
  1359. mclp.detached331 = mclp.detached;
  1360.  
  1361. mclp.detached = function () {
  1362. setupMutObserver();
  1363. return this.detached331();
  1364. }
  1365.  
  1366. let t31_items = document.querySelector('#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer');
  1367. let t31_itemOffset = t31_items ? nodeParent(t31_items) : null;
  1368.  
  1369. if (t31_items && t31_itemOffset) {
  1370. setList(t31_itemOffset, t31_items);
  1371. }
  1372.  
  1373. if ((mclp.async || 0).length === 2 && (mclp.cancelAsync || 0).length === 1) {
  1374.  
  1375. assertor(() => fnIntegrity(mclp.async, '2.24.15'));
  1376. assertor(() => fnIntegrity(mclp.cancelAsync, '1.15.8'));
  1377.  
  1378. /** @type {Map<number, any>} */
  1379. const aMap = new Map();
  1380. let count = 6150;
  1381. mclp.async66 = mclp.async;
  1382. mclp.async = function (e, f) {
  1383. // ensure the previous operation is done
  1384. // .async is usually after the time consuming functions like flushActiveItems_ and scrollToBottom_
  1385. const hasF = arguments.length === 2;
  1386. const stack = new Error().stack;
  1387. const isFlushAsync = stack.indexOf('flushActiveItems_') >= 0;
  1388. if (count > 1e9) count = 6159;
  1389. const resId = ++count;
  1390. aMap.set(resId, e);
  1391. (this.__intermediate_delay__ || Promise.resolve()).then(rk => {
  1392. const rp = aMap.get(resId);
  1393. if (typeof rp !== 'function') {
  1394. return;
  1395. }
  1396. let cancelCall = false;
  1397. if (isFlushAsync) {
  1398. if (rk < 0) {
  1399. cancelCall = true;
  1400. } else if (rk === 2 && arguments[0] === this.maybeScrollToBottom_) {
  1401. cancelCall = true;
  1402. }
  1403. }
  1404. if (cancelCall) {
  1405. aMap.delete(resId);
  1406. } else {
  1407. const asyncEn = function () {
  1408. aMap.delete(resId);
  1409. return rp.apply(this, arguments);
  1410. };
  1411. aMap.set(resId, hasF ? this.async66(asyncEn, f) : this.async66(asyncEn));
  1412. }
  1413. });
  1414.  
  1415. return resId;
  1416. }
  1417.  
  1418. mclp.cancelAsync66 = mclp.cancelAsync66;
  1419. mclp.cancelAsync = function (resId) {
  1420. if (resId <= 6150) {
  1421. this.cancelAsync66(resId);
  1422. } else if (aMap.has(resId)) {
  1423. const rp = aMap.get(resId);
  1424. aMap.delete(resId);
  1425. if (typeof rp !== 'function') {
  1426. this.cancelAsync66(rp);
  1427. }
  1428. }
  1429. }
  1430.  
  1431. console.log("async", "OK");
  1432. } else {
  1433. console.log("async", "NG");
  1434. }
  1435.  
  1436.  
  1437.  
  1438. if ((mclp.showNewItems_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM) {
  1439.  
  1440. assertor(() => fnIntegrity(mclp.showNewItems_, '0.170.79'));
  1441. mclp.showNewItems66_ = mclp.showNewItems_;
  1442.  
  1443. mclp.showNewItems77_ = async function () {
  1444. if (myk > 1e9) myk = 9;
  1445. let tid = ++myk;
  1446.  
  1447. await new Promise(requestAnimationFrame);
  1448.  
  1449. if (tid !== myk) {
  1450. return;
  1451. }
  1452.  
  1453. const cnt = this;
  1454.  
  1455. await Promise.resolve();
  1456. cnt.showNewItems66_();
  1457.  
  1458. await Promise.resolve();
  1459.  
  1460. }
  1461.  
  1462. mclp.showNewItems_ = function () {
  1463.  
  1464. const cnt = this;
  1465. cnt.__intermediate_delay__ = new Promise(resolve => {
  1466. cnt.showNewItems77_().then(() => {
  1467. resolve();
  1468. });
  1469. });
  1470. }
  1471.  
  1472. console.log("showNewItems_", "OK");
  1473. } else {
  1474. console.log("showNewItems_", "NG");
  1475. }
  1476.  
  1477.  
  1478.  
  1479.  
  1480. if ((mclp.flushActiveItems_ || 0).length === 0) {
  1481.  
  1482. assertor(() => fnIntegrity(mclp.flushActiveItems_, '0.137.81'));
  1483.  
  1484. let contensWillChangeController = null;
  1485.  
  1486. mclp.flushActiveItems66_ = mclp.flushActiveItems_;
  1487.  
  1488. mclp.flushActiveItems77_ = async function () {
  1489. try {
  1490.  
  1491. const cnt = this;
  1492. if (mlf > 1e9) mlf = 9;
  1493. let tid = ++mlf;
  1494. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1495. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  1496.  
  1497. // 4 times to maxItems to avoid frequent trimming.
  1498. // 1 ... 10 ... 20 ... 30 ... 40 ... 50 ... 60 => 16 ... 20 ... 30 ..... 60 ... => 16
  1499.  
  1500. this.activeItems_.length > this.data.maxItemsToDisplay * 4 && this.data.maxItemsToDisplay > 4 && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay - 1);
  1501. if (cnt.canScrollToBottom_()) {
  1502. let immd = cnt.__intermediate_delay__;
  1503. await new Promise(requestAnimationFrame);
  1504. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1505. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  1506.  
  1507. mlf++;
  1508. if (mlg > 1e9) mlg = 9;
  1509. ++mlg;
  1510.  
  1511. const oMaxItemsToDisplay = this.data.maxItemsToDisplay;
  1512. const reducedMaxItemsToDisplay = MAX_ITEMS_FOR_FULL_FLUSH;
  1513. let changeMaxItemsToDisplay = false;
  1514. if (ENABLE_REDUCED_MAXITEMS_FOR_FLUSH && this.activeItems_.length > this.data.maxItemsToDisplay) {
  1515. if (this.data.maxItemsToDisplay > reducedMaxItemsToDisplay) {
  1516. // as all the rendered chats are already "outdated"
  1517. // all old chats shall remove and reduced number of few chats will be rendered
  1518. // then restore to the original number
  1519. changeMaxItemsToDisplay = true;
  1520. this.data.maxItemsToDisplay = reducedMaxItemsToDisplay;
  1521. }
  1522. this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  1523. // console.log('changeMaxItemsToDisplay 01', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  1524. }
  1525.  
  1526. // it is found that it will render all stacked chats after switching back from background
  1527. // to avoid lagging in popular livestream with massive chats, trim first before rendering.
  1528. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  1529.  
  1530.  
  1531.  
  1532. const items = (cnt.$ || 0).items;
  1533.  
  1534. if (USE_WILL_CHANGE_CONTROLLER) {
  1535. if (contensWillChangeController && contensWillChangeController.element !== items) {
  1536. contensWillChangeController.release();
  1537. contensWillChangeController = null;
  1538. }
  1539. if (!contensWillChangeController) contensWillChangeController = new WillChangeController(items, 'contents');
  1540. }
  1541. const wcController = contensWillChangeController;
  1542. cnt.__intermediate_delay__ = Promise.all([cnt.__intermediate_delay__ || null, immd || null]);
  1543. wcController && wcController.beforeOper();
  1544. await Promise.resolve();
  1545. const len1 = cnt.activeItems_.length;
  1546. cnt.flushActiveItems66_();
  1547. const len2 = cnt.activeItems_.length;
  1548. let bAsync = len1 !== len2;
  1549. await Promise.resolve();
  1550. if (wcController) {
  1551. if (bAsync) {
  1552. cnt.async(() => {
  1553. wcController.afterOper();
  1554. });
  1555. } else {
  1556. wcController.afterOper();
  1557. }
  1558. }
  1559. if (changeMaxItemsToDisplay) {
  1560. if (this.data.maxItemsToDisplay === reducedMaxItemsToDisplay) {
  1561. this.data.maxItemsToDisplay = oMaxItemsToDisplay;
  1562. // console.log('changeMaxItemsToDisplay 02', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  1563. }
  1564. }
  1565.  
  1566.  
  1567. if (!ENABLE_NO_SMOOTH_TRANSFORM) {
  1568.  
  1569.  
  1570. const ff = () => {
  1571.  
  1572. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1573. // if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1574. if (!cnt.atBottom && cnt.allowScroll && cnt.hasUserJustInteracted11_ && !cnt.hasUserJustInteracted11_()) {
  1575. cnt.scrollToBottom_();
  1576.  
  1577. Promise.resolve().then(() => {
  1578.  
  1579. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  1580. if (!cnt.canScrollToBottom_()) cnt.scrollToBottom_();
  1581. });
  1582.  
  1583.  
  1584. }
  1585. }
  1586.  
  1587. ff();
  1588.  
  1589.  
  1590. Promise.resolve().then(ff);
  1591.  
  1592. // requestAnimationFrame(ff);
  1593. } else if (true) { // it might not be sticky to bottom when there is a full refresh.
  1594.  
  1595. const knt = cnt;
  1596. if (!scrollChatFn) {
  1597. const cnt = knt;
  1598. const f = () => {
  1599. const itemScroller = cnt.itemScroller;
  1600. if (!itemScroller || itemScroller.isConnected === false || cnt.isAttached === false) return;
  1601. if (!cnt.atBottom) {
  1602. cnt.scrollToBottom_();
  1603. } else if (itemScroller.scrollTop === 0) { // not yet interacted by user; cannot stick to bottom
  1604. itemScroller.scrollTop = itemScroller.scrollHeight;
  1605. }
  1606. };
  1607. scrollChatFn = () => Promise.resolve().then(f).then(f);
  1608. }
  1609.  
  1610. if (!ENABLE_FULL_RENDER_REQUIRED) scrollChatFn();
  1611. }
  1612.  
  1613.  
  1614. return 1;
  1615. } else {
  1616. // cnt.flushActiveItems66_();
  1617. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  1618. return 2;
  1619. }
  1620. } catch (e) {
  1621. console.warn(e);
  1622. }
  1623. }
  1624.  
  1625. mclp.flushActiveItems_ = function () {
  1626. const cnt = this;
  1627.  
  1628. if (arguments.length !== 0 || !cnt.activeItems_ || !cnt.canScrollToBottom_) return cnt.flushActiveItems66_.apply(this, arguments);
  1629.  
  1630. if (cnt.activeItems_.length === 0) {
  1631. cnt.__intermediate_delay__ = null;
  1632. return;
  1633. }
  1634.  
  1635. const cntData = ((cnt || 0).data || 0);
  1636. if (cntData.maxItemsToDisplay > MAX_ITEMS_FOR_TOTAL_DISPLAY) cntData.maxItemsToDisplay = MAX_ITEMS_FOR_TOTAL_DISPLAY;
  1637.  
  1638. // ignore previous __intermediate_delay__ and create a new one
  1639. cnt.__intermediate_delay__ = new Promise(resolve => {
  1640. cnt.flushActiveItems77_().then(rt => {
  1641. if (rt === 1) resolve(1); // success, scroll to bottom
  1642. else if (rt === 2) resolve(2); // success, trim
  1643. else resolve(-1); // skip
  1644. });
  1645. });
  1646.  
  1647. }
  1648. console.log("flushActiveItems_", "OK");
  1649. } else {
  1650. console.log("flushActiveItems_", "NG");
  1651. }
  1652.  
  1653. mclp.delayFlushActiveItemsAfterUserAction11_ = async function () {
  1654. try {
  1655. if (mlg > 1e9) mlg = 9;
  1656. const tid = ++mlg;
  1657. const keepTrialCond = () => this.atBottom && this.allowScroll && (tid === mlg) && this.isAttached === true && this.activeItems_.length >= 1 && (this.hostElement || 0).isConnected === true;
  1658. const runCond = () => this.canScrollToBottom_();
  1659. if (!keepTrialCond()) return;
  1660. if (runCond()) return this.flushActiveItems_() | 1; // avoid return promise
  1661. await new Promise(r => setTimeout(r, 80));
  1662. if (!keepTrialCond()) return;
  1663. if (runCond()) return this.flushActiveItems_() | 1;
  1664. await new Promise(requestAnimationFrame);
  1665. if (runCond()) return this.flushActiveItems_() | 1;
  1666. } catch (e) {
  1667. console.warn(e);
  1668. }
  1669. }
  1670.  
  1671. if ((mclp.atBottomChanged_ || 0).length === 1) {
  1672. // note: if the scrolling is too frequent, the show more visibility might get wrong.
  1673. assertor(() => fnIntegrity(mclp.atBottomChanged_, '1.75.39'));
  1674.  
  1675. const querySelector = HTMLElement.prototype.querySelector;
  1676. const U = (element) => ({
  1677. querySelector: (selector) => querySelector.call(element, selector)
  1678. });
  1679.  
  1680. let qid = 0;
  1681. mclp.atBottomChanged_ = function (a) {
  1682. let tid = ++qid;
  1683. var b = this;
  1684. a ? this.hideShowMoreAsync_ || (this.hideShowMoreAsync_ = this.async(function () {
  1685. if (tid !== qid) return;
  1686. U(b.hostElement).querySelector("#show-more").style.visibility = "hidden"
  1687. }, 200)) : (this.hideShowMoreAsync_ && this.cancelAsync(this.hideShowMoreAsync_),
  1688. this.hideShowMoreAsync_ = null,
  1689. U(this.hostElement).querySelector("#show-more").style.visibility = "visible")
  1690. }
  1691.  
  1692. console.log("atBottomChanged_", "OK");
  1693. } else {
  1694. console.log("atBottomChanged_", "NG");
  1695. }
  1696.  
  1697. if ((mclp.onScrollItems_ || 0).length === 1) {
  1698.  
  1699. assertor(() => fnIntegrity(mclp.onScrollItems_, '1.17.9'));
  1700. mclp.onScrollItems66_ = mclp.onScrollItems_;
  1701. mclp.onScrollItems77_ = async function (evt) {
  1702. if (myw > 1e9) myw = 9;
  1703. let tid = ++myw;
  1704.  
  1705. await new Promise(requestAnimationFrame);
  1706.  
  1707. if (tid !== myw) {
  1708. return;
  1709. }
  1710.  
  1711. const cnt = this;
  1712.  
  1713. await Promise.resolve();
  1714. if (USE_OPTIMIZED_ON_SCROLL_ITEMS) {
  1715. await Promise.resolve().then(() => {
  1716. this.ytRendererBehavior.onScroll(evt);
  1717. }).then(() => {
  1718. if (this.canScrollToBottom_()) {
  1719. const hasUserJustInteracted = this.hasUserJustInteracted11_ ? this.hasUserJustInteracted11_() : true;
  1720. if (hasUserJustInteracted) {
  1721. // only when there is an user action
  1722. this.setAtBottom();
  1723. return 1;
  1724. }
  1725. } else {
  1726. // no message inserting
  1727. this.setAtBottom();
  1728. return 1;
  1729. }
  1730. }).then((r) => {
  1731.  
  1732. if (this.activeItems_.length) {
  1733.  
  1734. if (this.canScrollToBottom_()) {
  1735. this.flushActiveItems_();
  1736. return 1 && r;
  1737. } else if (this.atBottom && this.allowScroll && (this.hasUserJustInteracted11_ && this.hasUserJustInteracted11_())) {
  1738. // delayed due to user action
  1739. this.delayFlushActiveItemsAfterUserAction11_ && this.delayFlushActiveItemsAfterUserAction11_();
  1740. return 0;
  1741. }
  1742. }
  1743. }).then((r) => {
  1744. if (r) {
  1745. // ensure setAtBottom is correctly set
  1746. this.setAtBottom();
  1747. }
  1748. });
  1749. } else {
  1750. cnt.onScrollItems66_(evt);
  1751. }
  1752.  
  1753. await Promise.resolve();
  1754.  
  1755. }
  1756.  
  1757. mclp.onScrollItems_ = function (evt) {
  1758.  
  1759. const cnt = this;
  1760. cnt.__intermediate_delay__ = new Promise(resolve => {
  1761. cnt.onScrollItems77_(evt).then(() => {
  1762. resolve();
  1763. });
  1764. });
  1765. }
  1766. console.log("onScrollItems_", "OK");
  1767. } else {
  1768. console.log("onScrollItems_", "NG");
  1769. }
  1770.  
  1771. if ((mclp.handleLiveChatActions_ || 0).length === 1) {
  1772.  
  1773. assertor(() => fnIntegrity(mclp.handleLiveChatActions_, '1.31.17'));
  1774. mclp.handleLiveChatActions66_ = mclp.handleLiveChatActions_;
  1775.  
  1776. mclp.handleLiveChatActions77_ = async function (arr) {
  1777. if (typeof (arr || 0).length !== 'number') {
  1778. this.handleLiveChatActions66_(arr);
  1779. return;
  1780. }
  1781. if (mzt > 1e9) mzt = 9;
  1782. let tid = ++mzt;
  1783.  
  1784. if (zarr === null) zarr = arr;
  1785. else Array.prototype.push.apply(zarr, arr);
  1786. arr = null;
  1787.  
  1788. await new Promise(requestAnimationFrame);
  1789.  
  1790. if (tid !== mzt || zarr === null) {
  1791. return;
  1792. }
  1793.  
  1794. const carr = zarr;
  1795. zarr = null;
  1796.  
  1797. await Promise.resolve();
  1798. this.handleLiveChatActions66_(carr);
  1799. await Promise.resolve();
  1800.  
  1801. }
  1802.  
  1803. mclp.handleLiveChatActions_ = function (arr) {
  1804.  
  1805. const cnt = this;
  1806. cnt.__intermediate_delay__ = new Promise(resolve => {
  1807. cnt.handleLiveChatActions77_(arr).then(() => {
  1808. resolve();
  1809. });
  1810. });
  1811. }
  1812. console.log("handleLiveChatActions_", "OK");
  1813. } else {
  1814. console.log("handleLiveChatActions_", "NG");
  1815. }
  1816.  
  1817. mclp.hasUserJustInteracted11_ = () => {
  1818. const t = dateNow();
  1819. return (t - lastWheel < 80) || currentMouseDown || currentTouchDown || (t - lastUserInteraction < 80);
  1820. }
  1821.  
  1822. if ((mclp.canScrollToBottom_ || 0).length === 0) {
  1823.  
  1824. assertor(() => fnIntegrity(mclp.canScrollToBottom_, '0.9.5'));
  1825.  
  1826. mclp.canScrollToBottom_ = function () {
  1827. return this.atBottom && this.allowScroll && !this.hasUserJustInteracted11_();
  1828. }
  1829.  
  1830. console.log("canScrollToBottom_", "OK");
  1831. } else {
  1832. console.log("canScrollToBottom_", "NG");
  1833. }
  1834.  
  1835. if (ENABLE_NO_SMOOTH_TRANSFORM) {
  1836.  
  1837. mclp.isSmoothScrollEnabled_ = function () {
  1838. return false;
  1839. }
  1840.  
  1841. mclp.maybeResizeScrollContainer_ = function () {
  1842. //
  1843. }
  1844.  
  1845. mclp.refreshOffsetContainerHeight_ = function () {
  1846. //
  1847. }
  1848.  
  1849. mclp.smoothScroll_ = function () {
  1850. //
  1851. }
  1852.  
  1853. mclp.resetSmoothScroll_ = function () {
  1854. //
  1855. }
  1856. console.log("ENABLE_NO_SMOOTH_TRANSFORM", "OK");
  1857. } else {
  1858. console.log("ENABLE_NO_SMOOTH_TRANSFORM", "NG");
  1859. }
  1860.  
  1861. console.groupEnd();
  1862.  
  1863. });
  1864.  
  1865.  
  1866.  
  1867.  
  1868.  
  1869.  
  1870. const fp = (renderer) => {
  1871. const cnt = renderer.inst || renderer;
  1872. assertor(() => typeof (cnt || 0).is === 'string');
  1873. assertor(() => ((cnt || 0).hostElement || 0).nodeType === 1);
  1874. const container = (cnt.$ || 0).container;
  1875. if (container) {
  1876. assertor(() => (container || 0).nodeType === 1);
  1877. assertor(() => typeof container.setAttribute === 'function');
  1878. container.setAttribute = tickerContainerSetAttribute;
  1879. } else {
  1880. console.warn(`"container" does not exist in ${cnt.is}`);
  1881. }
  1882. };
  1883.  
  1884. const tags = ["yt-live-chat-ticker-paid-message-item-renderer", "yt-live-chat-ticker-paid-sticker-item-renderer",
  1885. "yt-live-chat-ticker-renderer", "yt-live-chat-ticker-sponsor-item-renderer"];
  1886.  
  1887.  
  1888. Promise.all(tags.map(tag => customElements.whenDefined(tag))).then(() => {
  1889.  
  1890. console.groupCollapsed("YouTube Super Fast Check - yt-live-chat-ticker-... hacks");
  1891. console.log("[Begin]");
  1892.  
  1893. for (const tag of tags) {
  1894. const dummy = document.createElement(tag);
  1895.  
  1896. const cProto = getProto(dummy);
  1897. if (!cProto || !cProto.attached) {
  1898. console.warn(`proto.attached for ${tag} is unavailable.`);
  1899. continue;
  1900. }
  1901.  
  1902. cProto.attached77 = cProto.attached;
  1903.  
  1904. cProto.attached = function () {
  1905. fp(this.hostElement || this);
  1906. return this.attached77();
  1907. }
  1908.  
  1909. for (const elm of document.getElementsByTagName(tag)) {
  1910. fp(elm);
  1911. }
  1912.  
  1913. if (ENABLE_RAF_HACK_TICKERS && rafHub !== null) {
  1914.  
  1915. // cancelable - this.rafId < isAnimationPausedChanged >
  1916.  
  1917. let doHack = false;
  1918.  
  1919. if (typeof cProto.startCountdown === 'function' && typeof cProto.updateTimeout === 'function' && typeof cProto.isAnimationPausedChanged === 'function') {
  1920.  
  1921. console.log('startCountdown', typeof cProto.startCountdown)
  1922. console.log('updateTimeout', typeof cProto.updateTimeout)
  1923. console.log('isAnimationPausedChanged', typeof cProto.isAnimationPausedChanged)
  1924.  
  1925. doHack = fnIntegrity(cProto.startCountdown, '2.66.37') && fnIntegrity(cProto.updateTimeout, '1.76.45') && fnIntegrity(cProto.isAnimationPausedChanged, '2.56.30')
  1926.  
  1927. }
  1928. if (doHack) {
  1929.  
  1930. cProto.startCountdown = function (a, b) {
  1931. // console.log('cProto.startCountdown', tag) // yt-live-chat-ticker-sponsor-item-renderer
  1932. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  1933. b = void 0 === b ? 0 : b;
  1934. void 0 !== a && (this.countdownMs = 1E3 * a,
  1935. this.countdownDurationMs = b ? 1E3 * b : this.countdownMs,
  1936. this.ratio = 1,
  1937. this.lastCountdownTimeMs || this.isAnimationPaused || (this.lastCountdownTimeMs = performance.now(),
  1938. this.rafId = rafHub.request(this.boundUpdateTimeout37_)))
  1939. };
  1940.  
  1941. cProto.updateTimeout = function (a) {
  1942. // console.log('cProto.updateTimeout', tag) // yt-live-chat-ticker-sponsor-item-renderer
  1943. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  1944. this.countdownMs = Math.max(0, this.countdownMs - (a - (this.lastCountdownTimeMs || 0)));
  1945. this.ratio = this.countdownMs / this.countdownDurationMs;
  1946. this.isAttached && this.countdownMs ? (this.lastCountdownTimeMs = a,
  1947. this.rafId = rafHub.request(this.boundUpdateTimeout37_)) : (this.lastCountdownTimeMs = null,
  1948. this.isAttached && ("auto" === this.hostElement.style.width && this.setContainerWidth(),
  1949. this.slideDown()))
  1950. };
  1951.  
  1952. cProto.isAnimationPausedChanged = function (a, b) {
  1953. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  1954. a ? rafHub.cancel(this.rafId) : !a && b && (a = this.lastCountdownTimeMs || 0,
  1955. this.detlaSincePausedSecs && (a = (this.lastCountdownTimeMs || 0) + 1E3 * this.detlaSincePausedSecs,
  1956. this.detlaSincePausedSecs = 0),
  1957. this.boundUpdateTimeout37_(a),
  1958. this.lastCountdownTimeMs = window.performance.now())
  1959. };
  1960.  
  1961. console.log('RAF_HACK_TICKERS', tag, "OK")
  1962. } else {
  1963.  
  1964. console.log('RAF_HACK_TICKERS', tag, "NG")
  1965. }
  1966.  
  1967. }
  1968.  
  1969. }
  1970. console.log("[End]");
  1971. console.groupEnd();
  1972.  
  1973.  
  1974. })
  1975.  
  1976.  
  1977.  
  1978. if (ENABLE_RAF_HACK_INPUT_RENDERER && rafHub !== null) {
  1979.  
  1980.  
  1981. customElements.whenDefined("yt-live-chat-message-input-renderer").then(() => {
  1982.  
  1983.  
  1984. console.groupCollapsed("YouTube Super Fast Check - yt-live-chat-message-input-renderer hacks");
  1985. console.log("[Begin]");
  1986. (() => {
  1987.  
  1988. const tag = "yt-live-chat-message-input-renderer"
  1989. const dummy = document.createElement(tag);
  1990.  
  1991. const cProto = getProto(dummy);
  1992. if (!cProto || !cProto.attached) {
  1993. console.warn(`proto.attached for ${tag} is unavailable.`);
  1994. return;
  1995. }
  1996.  
  1997. let doHack = false;
  1998. if (typeof cProto.handleTimeout === 'function' && typeof cProto.updateTimeout === 'function') {
  1999.  
  2000. // not cancellable
  2001. console.log('handleTimeout', typeof cProto.handleTimeout)
  2002. console.log('updateTimeout', typeof cProto.updateTimeout)
  2003.  
  2004. doHack = fnIntegrity(cProto.handleTimeout, '1.27.16') && fnIntegrity(cProto.updateTimeout, '1.50.33');
  2005.  
  2006. }
  2007.  
  2008. if (doHack) {
  2009.  
  2010. cProto.handleTimeout = function (a) {
  2011. console.log('cProto.handleTimeout', tag)
  2012. if (!this.boundUpdateTimeout38_) this.boundUpdateTimeout38_ = this.updateTimeout.bind(this);
  2013. this.timeoutDurationMs = this.timeoutMs = a;
  2014. this.countdownRatio = 1;
  2015. 0 === this.lastTimeoutTimeMs && rafHub.request(this.boundUpdateTimeout38_)
  2016. };
  2017. cProto.updateTimeout = function (a) {
  2018. console.log('cProto.updateTimeout', tag)
  2019. if (!this.boundUpdateTimeout38_) this.boundUpdateTimeout38_ = this.updateTimeout.bind(this);
  2020. this.lastTimeoutTimeMs && (this.timeoutMs = Math.max(0, this.timeoutMs - (a - this.lastTimeoutTimeMs)),
  2021. this.countdownRatio = this.timeoutMs / this.timeoutDurationMs);
  2022. this.isAttached && this.timeoutMs ? (this.lastTimeoutTimeMs = a,
  2023. rafHub.request(this.boundUpdateTimeout38_)) : this.lastTimeoutTimeMs = 0
  2024. };
  2025.  
  2026. console.log('RAF_HACK_INPUT_RENDERER', tag, "OK")
  2027. } else {
  2028.  
  2029. console.log('RAF_HACK_INPUT_RENDERER', tag, "NG")
  2030. }
  2031.  
  2032. })();
  2033.  
  2034. console.log("[End]");
  2035.  
  2036. console.groupEnd();
  2037.  
  2038.  
  2039. })
  2040.  
  2041.  
  2042.  
  2043. }
  2044.  
  2045.  
  2046. if (ENABLE_RAF_HACK_EMOJI_PICKER && rafHub !== null) {
  2047.  
  2048.  
  2049. customElements.whenDefined("yt-emoji-picker-renderer").then(() => {
  2050.  
  2051. console.groupCollapsed("YouTube Super Fast Check - yt-emoji-picker-renderer hacks");
  2052. console.log("[Begin]");
  2053. (() => {
  2054.  
  2055. const tag = "yt-emoji-picker-renderer"
  2056. const dummy = document.createElement(tag);
  2057.  
  2058. const cProto = getProto(dummy);
  2059. if (!cProto || !cProto.attached) {
  2060. console.warn(`proto.attached for ${tag} is unavailable.`);
  2061. return;
  2062. }
  2063.  
  2064. let doHack = false;
  2065. if (typeof cProto.animateScroll_ === 'function') {
  2066.  
  2067. // not cancellable
  2068. console.log('animateScroll_', typeof cProto.animateScroll_)
  2069.  
  2070. doHack = fnIntegrity(cProto.animateScroll_, '1.102.49')
  2071.  
  2072. }
  2073.  
  2074. if (doHack) {
  2075.  
  2076. const querySelector = HTMLElement.prototype.querySelector;
  2077. const U = (element) => ({
  2078. querySelector: (selector) => querySelector.call(element, selector)
  2079. });
  2080.  
  2081. cProto.animateScroll_ = function (a) {
  2082. // console.log('cProto.animateScroll_', tag) // yt-emoji-picker-renderer
  2083. if (!this.boundAnimateScroll39_) this.boundAnimateScroll39_ = this.animateScroll_.bind(this);
  2084. this.lastAnimationTime_ || (this.lastAnimationTime_ = a);
  2085. a -= this.lastAnimationTime_;
  2086. 200 > a ? (U(this.hostElement).querySelector("#categories").scrollTop = this.animationStart_ + (this.animationEnd_ - this.animationStart_) * a / 200,
  2087. rafHub.request(this.boundAnimateScroll39_)) : (null != this.animationEnd_ && (U(this.hostElement).querySelector("#categories").scrollTop = this.animationEnd_),
  2088. this.animationEnd_ = this.animationStart_ = null,
  2089. this.lastAnimationTime_ = 0);
  2090. this.updateButtons_()
  2091. }
  2092.  
  2093. console.log('ENABLE_RAF_HACK_EMOJI_PICKER', tag, "OK")
  2094. } else {
  2095.  
  2096. console.log('ENABLE_RAF_HACK_EMOJI_PICKER', tag, "NG")
  2097. }
  2098.  
  2099. })();
  2100.  
  2101. console.log("[End]");
  2102.  
  2103. console.groupEnd();
  2104. });
  2105. }
  2106.  
  2107.  
  2108.  
  2109.  
  2110. if (ENABLE_RAF_HACK_DOCKED_MESSAGE && rafHub !== null) {
  2111.  
  2112.  
  2113. customElements.whenDefined("yt-live-chat-docked-message").then(() => {
  2114.  
  2115. console.groupCollapsed("YouTube Super Fast Check - yt-live-chat-docked-message hacks");
  2116. console.log("[Begin]");
  2117. (() => {
  2118.  
  2119. const tag = "yt-live-chat-docked-message"
  2120. const dummy = document.createElement(tag);
  2121.  
  2122. const cProto = getProto(dummy);
  2123. if (!cProto || !cProto.attached) {
  2124. console.warn(`proto.attached for ${tag} is unavailable.`);
  2125. return;
  2126. }
  2127.  
  2128. let doHack = false;
  2129. if (typeof cProto.detached === 'function' && typeof cProto.checkIntersections === 'function' && typeof cProto.onDockableMessagesChanged === 'function' && typeof cProto.boundCheckIntersections === 'undefined') {
  2130.  
  2131. // cancelable - this.intersectRAF <detached>
  2132. // yt-live-chat-docked-message
  2133. // boundCheckIntersections <-> checkIntersections
  2134. // onDockableMessagesChanged
  2135. // this.intersectRAF = window.requestAnimationFrame(this.boundCheckIntersections);
  2136.  
  2137. console.log('detached', typeof cProto.detached)
  2138. console.log('checkIntersections', typeof cProto.checkIntersections)
  2139. console.log('onDockableMessagesChanged', typeof cProto.onDockableMessagesChanged)
  2140.  
  2141. doHack = fnIntegrity(cProto.detached, '0.32.22') && fnIntegrity(cProto.checkIntersections, '0.128.85') && fnIntegrity(cProto.onDockableMessagesChanged, '0.20.11')
  2142.  
  2143. }
  2144.  
  2145. if (doHack) {
  2146.  
  2147. cProto.checkIntersections = function () {
  2148. console.log('cProto.checkIntersections', tag)
  2149. if (this.dockableMessages.length) {
  2150. this.intersectRAF = rafHub.request(this.boundCheckIntersections);
  2151. var a = this.dockableMessages[0]
  2152. , b = this.hostElement.getBoundingClientRect();
  2153. a = a.getBoundingClientRect();
  2154. var c = a.top - b.top
  2155. , d = 8 >= c;
  2156. c = 8 >= c - this.hostElement.clientHeight;
  2157. if (d) {
  2158. for (var e; d;) {
  2159. e = this.dockableMessages.shift();
  2160. d = this.dockableMessages[0];
  2161. if (!d)
  2162. break;
  2163. d = d.getBoundingClientRect();
  2164. c = d.top - b.top;
  2165. var f = 8 >= c;
  2166. if (8 >= c - a.height)
  2167. if (f)
  2168. a = d;
  2169. else
  2170. return;
  2171. d = f
  2172. }
  2173. this.dock(e)
  2174. } else
  2175. c && this.dockedItem && this.clear()
  2176. } else
  2177. this.intersectRAF = 0
  2178. }
  2179.  
  2180. cProto.onDockableMessagesChanged = function () {
  2181. // console.log('cProto.onDockableMessagesChanged', tag) // yt-live-chat-docked-message
  2182. this.dockableMessages.length && !this.intersectRAF && (this.intersectRAF = rafHub.request(this.boundCheckIntersections))
  2183. }
  2184.  
  2185. cProto.detached = function () {
  2186. this.intersectRAF && rafHub.cancel(this.intersectRAF)
  2187. }
  2188.  
  2189. console.log('ENABLE_RAF_HACK_DOCKED_MESSAGE', tag, "OK")
  2190. } else {
  2191.  
  2192. console.log('ENABLE_RAF_HACK_DOCKED_MESSAGE', tag, "NG")
  2193. }
  2194.  
  2195. })();
  2196.  
  2197. console.log("[End]");
  2198.  
  2199. console.groupEnd();
  2200.  
  2201. });
  2202.  
  2203. }
  2204.  
  2205.  
  2206.  
  2207. });
  2208.  
  2209.  
  2210.  
  2211.  
  2212.  
  2213. });
  2214.  
  2215. })({ IntersectionObserver });