YouTube 超快聊天

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

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

  1. // ==UserScript==
  2. // @name YouTube Super Fast Chat
  3. // @version 0.20.1
  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. // @match https://www.youtube.com/live_chat_replay*
  12. // @author CY Fung
  13. // @run-at document-start
  14. // @grant none
  15. // @unwrap
  16. // @allFrames true
  17. // @inject-into page
  18. //
  19. // @compatible firefox Violentmonkey
  20. // @compatible firefox Tampermonkey
  21. // @compatible firefox FireMonkey
  22. // @compatible chrome Violentmonkey
  23. // @compatible chrome Tampermonkey
  24. // @compatible opera Violentmonkey
  25. // @compatible opera Tampermonkey
  26. // @compatible safari Stay
  27. // @compatible edge Violentmonkey
  28. // @compatible edge Tampermonkey
  29. // @compatible brave Violentmonkey
  30. // @compatible brave Tampermonkey
  31. //
  32. // @description Ultimate Performance Boost for YouTube Live Chats
  33. // @description:ja YouTubeのライブチャットの究極のパフォーマンスブースト
  34. // @description:zh-TW YouTube直播聊天的終極性能提升
  35. // @description:zh-CN YouTube直播聊天的终极性能提升
  36. //
  37. // ==/UserScript==
  38.  
  39. ((__CONTEXT__) => {
  40. 'use strict';
  41.  
  42. const ENABLE_REDUCED_MAXITEMS_FOR_FLUSH = true; // TRUE to enable trimming down to 25 messages when there are too many unrendered messages
  43. const MAX_ITEMS_FOR_TOTAL_DISPLAY = 90; // By default, 250 latest messages will be displayed, but displaying 90 messages is already sufficient.
  44. const MAX_ITEMS_FOR_FULL_FLUSH = 25; // If there are too many new (stacked) messages not yet rendered, clean all and flush 25 latest messages then incrementally added back to 90 messages
  45.  
  46. const ENABLE_NO_SMOOTH_TRANSFORM = true; // Depends on whether you want the animation effect for new chat messages
  47. const USE_OPTIMIZED_ON_SCROLL_ITEMS = true; // TRUE for the majority
  48. const USE_WILL_CHANGE_CONTROLLER = false; // FALSE for the majority
  49. const ENABLE_FULL_RENDER_REQUIRED_PREFERRED = true; // In Chrome, the rendering of new chat messages could be too fast for no smooth transform. 80ms delay of displaying new messages should be sufficient for element rendering.
  50. const ENABLE_OVERFLOW_ANCHOR_PREFERRED = true; // Enable `overflow-anchor: auto` to lock the scroll list at the bottom for no smooth transform.
  51.  
  52. const FIX_SHOW_MORE_BUTTON_LOCATION = true; // When there are voting options (bottom panel), move the "show more" button to the top.
  53. const FIX_INPUT_PANEL_OVERFLOW_ISSUE = true; // When the super chat button is flicking with color, the scrollbar might come out.
  54. const FIX_INPUT_PANEL_BORDER_ISSUE = true; // No border should be allowed if there is an empty input panel.
  55. const SET_CONTAIN_FOR_CHATROOM = true; // Rendering hacks (`contain`) for chatroom elements. [ General ]
  56.  
  57. const FORCE_CONTENT_VISIBILITY_UNSET = true; // Content-visibility should be always VISIBLE for high performance and great rendering.
  58. const FORCE_WILL_CHANGE_UNSET = true; // Will-change should be always UNSET (auto) for high performance and low energy impact.
  59.  
  60. // Replace requestAnimationFrame timers with custom implementation
  61. const ENABLE_RAF_HACK_TICKERS = true; // When there is a ticker
  62. const ENABLE_RAF_HACK_DOCKED_MESSAGE = true; // To be confirmed
  63. const ENABLE_RAF_HACK_INPUT_RENDERER = true; // To be confirmed
  64. const ENABLE_RAF_HACK_EMOJI_PICKER = true; // When changing the page of the emoji picker
  65.  
  66. // Force rendering all the character subsets of the designated font(s) before messages come (Pre-Rendering of Text)
  67. const ENABLE_FONT_PRE_RENDERING_PREFERRED = 1 | 2 | 4 | 8 | 16;
  68.  
  69. // Backdrop `filter: blur(4px)` inside the iframe can extend to the whole page, causing a negative visual impact on the video you are watching.
  70. const NO_BACKDROP_FILTER_WHEN_MENU_SHOWN = true;
  71.  
  72. // Data Manipulation for Participants (Participant List)
  73. const DO_PARTICIPANT_LIST_HACKS = true; // TRUE for the majority
  74. const SHOW_PARTICIPANT_CHANGES_IN_CONSOLE = false; // Just too annoying to show them all in popular chat
  75. const CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT = true; // Only consider changes in renderable content (not concerned with the last chat message of the participants)
  76.  
  77.  
  78. const { IntersectionObserver } = __CONTEXT__;
  79.  
  80. /** @type {globalThis.PromiseConstructor} */
  81. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  82.  
  83. if (!IntersectionObserver) return console.warn("Your browser does not support IntersectionObserver.\nPlease upgrade to the latest version.")
  84.  
  85. // necessity of cssText3_smooth_transform_position to be checked.
  86. const cssText3_smooth_transform_position = ENABLE_NO_SMOOTH_TRANSFORM ? `
  87.  
  88. #item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer {
  89. position: static !important;
  90. }
  91.  
  92. `: '';
  93.  
  94. // fallback if dummy style fn fails
  95. const cssText4_smooth_transform_forced_props = ENABLE_NO_SMOOTH_TRANSFORM ? `
  96.  
  97. /* optional */
  98. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  99. height: auto !important;
  100. min-height: unset !important;
  101. }
  102.  
  103. #items.style-scope.yt-live-chat-item-list-renderer {
  104. transform: translateY(0px) !important;
  105. }
  106.  
  107. /* optional */
  108.  
  109. `: '';
  110.  
  111. const cssText5 = SET_CONTAIN_FOR_CHATROOM ? `
  112.  
  113. /* ------------------------------------------------------------------------------------------------------------- */
  114.  
  115. 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 {
  116. contain: layout style;
  117. }
  118.  
  119. #items.style-scope.yt-live-chat-item-list-renderer {
  120. contain: layout paint style;
  121. }
  122.  
  123. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  124. contain: style;
  125. }
  126.  
  127. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  128. contain: size style;
  129. }
  130.  
  131. #contents.style-scope.yt-live-chat-item-list-renderer, #chat.style-scope.yt-live-chat-renderer, img.style-scope.yt-img-shadow[width][height] {
  132. contain: size layout paint style;
  133. }
  134.  
  135. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label], .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label] > #container {
  136. contain: layout paint style;
  137. }
  138.  
  139. 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 {
  140. contain: layout style;
  141. }
  142.  
  143. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  144. contain: layout paint style;
  145. }
  146.  
  147. /* ------------------------------------------------------------------------------------------------------------- */
  148.  
  149. ` : '';
  150.  
  151. const cssText6b_show_more_button = FIX_SHOW_MORE_BUTTON_LOCATION ? `
  152.  
  153. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer{
  154. top: 4px;
  155. transition-property: top;
  156. bottom: unset;
  157. }
  158.  
  159. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer[disabled]{
  160. top: -42px;
  161. }
  162.  
  163. `: '';
  164.  
  165. const cssText6c_input_panel_overflow = FIX_INPUT_PANEL_OVERFLOW_ISSUE ? `
  166.  
  167. #input-panel #picker-buttons yt-live-chat-icon-toggle-button-renderer#product-picker {
  168. contain: layout style;
  169. }
  170.  
  171. #chat.yt-live-chat-renderer ~ #panel-pages.yt-live-chat-renderer {
  172. overflow: visible;
  173. }
  174.  
  175. `: '';
  176.  
  177. const cssText6d_input_panel_border = FIX_INPUT_PANEL_BORDER_ISSUE ? `
  178.  
  179. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer:not(:empty) {
  180. --yt-live-chat-action-panel-top-border: none;
  181. }
  182.  
  183. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer.iron-selected > *:first-child {
  184. border-top: 1px solid var(--yt-live-chat-panel-pages-border-color);
  185. }
  186.  
  187. html #panel-pages.yt-live-chat-renderer {
  188. border-top: 0;
  189. border-bottom: 0;
  190. }
  191.  
  192. `: '';
  193.  
  194. const cssText7b_content_visibility_unset = FORCE_CONTENT_VISIBILITY_UNSET ? `
  195.  
  196. img,
  197. yt-img-shadow[height][width],
  198. yt-img-shadow {
  199. content-visibility: visible !important;
  200. }
  201.  
  202. ` : '';
  203.  
  204. const cssText7c_will_change_unset = FORCE_WILL_CHANGE_UNSET ? `
  205.  
  206. /* remove YouTube constant will-change */
  207. /* constant value will slow down the performance; default auto */
  208.  
  209. /* www-player.css */
  210. html .ytp-contextmenu,
  211. html .ytp-settings-menu {
  212. will-change: unset;
  213. }
  214.  
  215. /* frequently matched elements */
  216. html .fill.yt-interaction,
  217. html .stroke.yt-interaction,
  218. html .yt-spec-touch-feedback-shape__fill,
  219. html .yt-spec-touch-feedback-shape__stroke {
  220. will-change: unset;
  221. }
  222.  
  223. /* live_chat_polymer.js */
  224. /*
  225. html .toggle-button.tp-yt-paper-toggle-button,
  226. html #primaryProgress.tp-yt-paper-progress,
  227. html #secondaryProgress.tp-yt-paper-progress,
  228. html #onRadio.tp-yt-paper-radio-button,
  229. html .fill.yt-interaction,
  230. html .stroke.yt-interaction,
  231. html .yt-spec-touch-feedback-shape__fill,
  232. html .yt-spec-touch-feedback-shape__stroke {
  233. will-change: unset;
  234. }
  235. */
  236.  
  237. /* desktop_polymer_enable_wil_icons.js */
  238. /* html .fill.yt-interaction,
  239. html .stroke.yt-interaction, */
  240. html tp-yt-app-header::before,
  241. html tp-yt-iron-list,
  242. html #items.tp-yt-iron-list > *,
  243. html #onRadio.tp-yt-paper-radio-button,
  244. html .toggle-button.tp-yt-paper-toggle-button,
  245. html ytd-thumbnail-overlay-toggle-button-renderer[use-expandable-tooltip] #label.ytd-thumbnail-overlay-toggle-button-renderer,
  246. html #items.ytd-post-multi-image-renderer,
  247. html #items.ytd-horizontal-card-list-renderer,
  248. html #items.yt-horizontal-list-renderer,
  249. html #left-arrow.yt-horizontal-list-renderer,
  250. html #right-arrow.yt-horizontal-list-renderer,
  251. html #items.ytd-video-description-infocards-section-renderer,
  252. html #items.ytd-video-description-music-section-renderer,
  253. html #chips.ytd-feed-filter-chip-bar-renderer,
  254. html #chips.yt-chip-cloud-renderer,
  255. html #items.ytd-merch-shelf-renderer,
  256. html #items.ytd-product-details-image-carousel-renderer,
  257. html ytd-video-preview,
  258. html #player-container.ytd-video-preview,
  259. html #primaryProgress.tp-yt-paper-progress,
  260. html #secondaryProgress.tp-yt-paper-progress,
  261. html ytd-miniplayer[enabled] /* ,
  262. html .yt-spec-touch-feedback-shape__fill,
  263. html .yt-spec-touch-feedback-shape__stroke */ {
  264. will-change: unset;
  265. }
  266.  
  267. /* other */
  268. .ytp-videowall-still-info-content[class],
  269. .ytp-suggestion-image[class] {
  270. will-change: unset !important;
  271. }
  272.  
  273. ` : '';
  274.  
  275. const ENABLE_FONT_PRE_RENDERING = typeof HTMLElement.prototype.append === 'function' ? (ENABLE_FONT_PRE_RENDERING_PREFERRED || 0) : 0;
  276. const cssText8_fonts_pre_render = ENABLE_FONT_PRE_RENDERING ? `
  277.  
  278. elzm-fonts {
  279. visibility: collapse;
  280. position: fixed;
  281. top: -10px;
  282. left: -10px;
  283. font-size: 10pt;
  284. line-height: 100%;
  285. width: 100px;
  286. height: 100px;
  287. transform: scale(0.1);
  288. transform: scale(0.01);
  289. transform: scale(0.001);
  290. transform-origin: 0 0;
  291. contain: strict;
  292. display: block;
  293.  
  294. user-select: none !important;
  295. pointer-events: none !important;
  296. }
  297.  
  298. elzm-fonts[id]#elzm-fonts-yk75g {
  299. user-select: none !important;
  300. pointer-events: none !important;
  301. }
  302.  
  303. elzm-font {
  304. visibility: collapse;
  305. position: absolute;
  306. line-height: 100%;
  307. width: 100px;
  308. height: 100px;
  309. contain: strict;
  310. display: block;
  311.  
  312. user-select: none !important;
  313. pointer-events: none !important;
  314. }
  315.  
  316. elzm-font::before {
  317. visibility: collapse;
  318. position: absolute;
  319. line-height: 100%;
  320. width: 100px;
  321. height: 100px;
  322. contain: strict;
  323. display: block;
  324.  
  325. content: '0aZ!@#$~^&*()_-+[]{}|;:><?\\0460\\0301\\0900\\1F00\\0370\\0102\\0100\\28EB2\\28189\\26DA0\\25A9C\\249BB\\23F61\\22E8B\\21927\\21076\\2048E\\1F6F5\\FF37\\F94F\\F0B2\\9F27\\9D9A\\9BEA\\9A6B\\98EC\\9798\\9602\\949D\\9370\\926B\\913A\\8FA9\\8E39\\8CC1\\8B26\\8983\\8804\\8696\\8511\\83BC\\828D\\8115\\7F9A\\7E5B\\7D07\\7B91\\7A2C\\78D2\\776C\\7601\\74AA\\73B9\\7265\\70FE\\6FBC\\6E88\\6D64\\6C3F\\6A9C\\6957\\67FE\\66B3\\6535\\63F2\\628E\\612F\\5FE7\\5E6C\\5CEE\\5B6D\\5A33\\58BC\\575B\\5611\\54BF\\536E\\51D0\\505D\\4F22\\4AD1\\41DB\\3B95\\3572\\2F3F\\26FD\\25A1\\2477\\208D\\1D0A\\1FB\\A1\\A3\\B4\\2CB\\60\\10C\\E22\\A5\\4E08\\B0\\627\\2500\\5E\\201C\\3C\\B7\\23\\26\\3E\\D\\20\\25EE8\\1F235\\FFD7\\FA10\\F92D\\9E8B\\9C3E\\9AE5\\98EB\\971D\\944A\\92BC\\9143\\8F52\\8DC0\\8B2D\\8973\\87E2\\8655\\84B4\\82E8\\814A\\7F77\\7D57\\7BC8\\7A17\\7851\\768C\\7511\\736C\\7166\\6F58\\6D7C\\6B85\\69DD\\6855\\667E\\64D2\\62CF\\6117\\5F6C\\5D9B\\5BBC\\598B\\57B3\\5616\\543F\\528D\\50DD\\4F57\\4093\\3395\\32B5\\31C8\\3028\\2F14\\25E4\\24D1\\2105\\2227\\A8\\2D9\\2CA\\2467\\B1\\2020\\2466\\251C\\266B\\AF\\4E91\\221E\\2464\\2266\\2207\\4E32\\25B3\\2463\\2010\\2103\\3014\\25C7\\24\\25BD\\4E18\\2460\\21D2\\2015\\2193\\4E03\\7E\\25CB\\2191\\25BC\\3D\\500D\\4E01\\25\\30F6\\2605\\266A\\40\\2B\\4E16\\7C\\A9\\4E\\21\\1F1E9\\FEE3\\F0A7\\9F3D\\9DFA\\9C3B\\9A5F\\98C8\\972A\\95B9\\94E7\\9410\\92B7\\914C\\8FE2\\8E2D\\8CAF\\8B5E\\8A02\\8869\\86E4\\8532\\83B4\\82A9\\814D\\7FFA\\7ED7\\7DC4\\7CCC\\7BC3\\7ACA\\797C\\783E\\770F\\760A\\74EF\\73E7\\72DD\\719C\\7005\\6ED8\\6DC3\\6CB2\\6A01\\68E1\\6792\\663A\\64F8\\63BC\\623B\\60FA\\5FD1\\5EA3\\5D32\\5BF5\\5AB2\\5981\\5831\\570A\\5605\\5519\\53FB\\52A2\\5110\\4FE3\\4EB8\\3127\\279C\\2650\\254B\\23E9\\207B\\1D34\\2AE\\176\\221A\\161\\200B\\300C\\4E4C\\1F921\\FF78\\FA0A\\F78A\\9EB9\\9D34\\9BD3\\9A6F\\9912\\97C6\\964E\\950C\\93E4\\92E5\\91F0\\90BB\\8F68\\8E18\\8B6C\\89F6\\889B\\874C\\8602\\84B1\\8378\\826E\\8113\\7FB1\\7EAF\\7D89\\7C20\\7AFB\\7988\\7840\\7705\\75CC\\749A\\73B3\\727F\\7113\\6FE8\\6ED6\\6DD3\\6CDA\\6BBB\\6A31\\6900\\67D9\\66A7\\655D\\6427\\630D\\61C6\\60AC\\5F78\\5E34\\5CE0\\5B80\\5A51\\590B\\57A1\\566F\\5551\\543D\\52DB\\518F\\5032\\3A17\\305C\\2749\\264A\\2567\\2476\\2139\\1EC0\\11AF\\2C8\\1AF\\E17\\2190\\2022\\2502\\2312\\2025\\50';
  326.  
  327. user-select: none !important;
  328. pointer-events: none !important;
  329. }
  330.  
  331. `: '';
  332.  
  333. const cssText9_no_backdrop_filter_when_menu_shown = NO_BACKDROP_FILTER_WHEN_MENU_SHOWN ? `
  334. tp-yt-iron-dropdown.yt-live-chat-app ytd-menu-popup-renderer {
  335. -webkit-backdrop-filter: none;
  336. backdrop-filter: none;
  337. }
  338. `: '';
  339.  
  340.  
  341. const addCss = () => `
  342.  
  343. ${cssText8_fonts_pre_render}
  344.  
  345. ${cssText9_no_backdrop_filter_when_menu_shown}
  346.  
  347. @supports (contain: layout paint style) {
  348.  
  349. ${cssText5}
  350.  
  351. }
  352.  
  353. @supports (color: var(--general)) {
  354.  
  355. html {
  356. --yt-live-chat-item-list-renderer-padding: 0px 0px;
  357. }
  358.  
  359. ${cssText3_smooth_transform_position}
  360.  
  361. ${cssText7c_will_change_unset}
  362.  
  363. ${cssText7b_content_visibility_unset}
  364.  
  365. yt-live-chat-item-list-renderer:not([allow-scroll]) #item-scroller.yt-live-chat-item-list-renderer {
  366. overflow-y: scroll;
  367. padding-right: 0;
  368. }
  369.  
  370. ${cssText4_smooth_transform_forced_props}
  371.  
  372. yt-icon[icon="down_arrow"] > *, yt-icon-button#show-more > * {
  373. pointer-events: none !important;
  374. }
  375.  
  376. #continuations, #continuations * {
  377. contain: strict;
  378. position: fixed;
  379. top: 2px;
  380. height: 1px;
  381. width: 2px;
  382. height: 1px;
  383. visibility: collapse;
  384. }
  385.  
  386. ${cssText6b_show_more_button}
  387.  
  388. ${cssText6d_input_panel_border}
  389.  
  390. ${cssText6c_input_panel_overflow}
  391.  
  392. }
  393.  
  394.  
  395. @supports (overflow-anchor: auto) {
  396.  
  397. .no-anchor * {
  398. overflow-anchor: none;
  399. }
  400. .no-anchor > item-anchor {
  401. overflow-anchor: auto;
  402. }
  403.  
  404. item-anchor {
  405.  
  406. height:1px;
  407. width: 100%;
  408. transform: scaleY(0.00001);
  409. transform-origin:0 0;
  410. contain: strict;
  411. opacity:0;
  412. display:flex;
  413. position:relative;
  414. flex-shrink:0;
  415. flex-grow:0;
  416. margin-bottom:0;
  417. overflow:hidden;
  418. box-sizing:border-box;
  419. visibility: visible;
  420. content-visibility: visible;
  421. contain-intrinsic-size: auto 1px;
  422. pointer-events:none !important;
  423.  
  424. }
  425.  
  426. #item-scroller.style-scope.yt-live-chat-item-list-renderer[class] {
  427. overflow-anchor: initial !important; /* whenever ENABLE_OVERFLOW_ANCHOR or not */
  428. }
  429.  
  430. html item-anchor {
  431.  
  432. height: 1px;
  433. width: 1px;
  434. top: auto;
  435. left: auto;
  436. right: auto;
  437. bottom: auto;
  438. transform: translateY(-1px);
  439. position: absolute;
  440. z-index: -1;
  441.  
  442. }
  443.  
  444. }
  445.  
  446. @supports (color: var(--pre-rendering)) {
  447.  
  448. @keyframes dontRenderAnimation {
  449. 0% {
  450. background-position-x: 3px;
  451. }
  452. 100% {
  453. background-position-x: 4px;
  454. }
  455. }
  456.  
  457. /*html[dont-render-enabled] */ .dont-render{
  458.  
  459. visibility: collapse !important;
  460. transform: scale(0.01) !important;
  461. transform: scale(0.00001) !important;
  462. transform: scale(0.0000001) !important;
  463. transform-origin: 0 0 !important;
  464. z-index:-1 !important;
  465. contain: strict !important;
  466. box-sizing: border-box !important;
  467.  
  468. height: 1px !important;
  469. height: 0.1px !important;
  470. height: 0.01px !important;
  471. height: 0.0001px !important;
  472. height: 0.000001px !important;
  473.  
  474. animation: dontRenderAnimation 1ms linear 80ms 1 normal forwards !important;
  475.  
  476. }
  477.  
  478. }
  479.  
  480. `;
  481.  
  482.  
  483. const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : (this instanceof Window ? this : window);
  484.  
  485. // Create a unique key for the script and check if it is already running
  486. const hkey_script = 'mchbwnoasqph';
  487. if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  488. win[hkey_script] = true;
  489.  
  490.  
  491. function dr(s) {
  492. // reserved for future use
  493. return s;
  494. // return window.deWeakJS ? window.deWeakJS(s) : s;
  495. }
  496.  
  497. const getProto = (element) => {
  498. if (element) {
  499. const cnt = element.inst || element;
  500. return cnt.constructor.prototype || null;
  501. }
  502. return null;
  503. }
  504.  
  505. const assertor = (f) => f() || console.assert(false, f + "");
  506.  
  507. const fnIntegrity = (f, d) => {
  508. if (!f || typeof f !== 'function') {
  509. console.warn('f is not a function', f);
  510. return;
  511. }
  512. let p = f + "", s = 0, j = -1, w = 0;
  513. for (let i = 0, l = p.length; i < l; i++) {
  514. const t = p[i];
  515. if (((t >= 'a' && t <= 'z') || (t >= 'A' && t <= 'Z'))) {
  516. if (j < i - 1) w++;
  517. j = i;
  518. } else {
  519. s++;
  520. }
  521. }
  522. let itz = `${f.length}.${s}.${w}`;
  523. if (!d) {
  524. console.log(itz);
  525. return null;
  526. } else {
  527. return itz === d;
  528. }
  529. }
  530.  
  531.  
  532. console.assert(MAX_ITEMS_FOR_TOTAL_DISPLAY > 0 && MAX_ITEMS_FOR_FULL_FLUSH > 0 && MAX_ITEMS_FOR_TOTAL_DISPLAY > MAX_ITEMS_FOR_FULL_FLUSH)
  533.  
  534. let ENABLE_FULL_RENDER_REQUIRED_CAPABLE = false;
  535. const isContainSupport = CSS.supports('contain', 'layout paint style');
  536. if (!isContainSupport) {
  537. console.warn("Your browser does not support css property 'contain'.\nPlease upgrade to the latest version.".trim());
  538. } else {
  539. ENABLE_FULL_RENDER_REQUIRED_CAPABLE = true;
  540. }
  541.  
  542. let ENABLE_OVERFLOW_ANCHOR_CAPABLE = false;
  543. const isOverflowAnchorSupport = CSS.supports('overflow-anchor', 'auto');
  544. if (!isOverflowAnchorSupport) {
  545. console.warn("Your browser does not support css property 'overflow-anchor'.\nPlease upgrade to the latest version.".trim());
  546. } else {
  547. ENABLE_OVERFLOW_ANCHOR_CAPABLE = true;
  548. }
  549.  
  550. const NOT_FIREFOX = !CSS.supports('-moz-appearance', 'none'); // 1. Firefox does not have the flicking issue; 2. Firefox's OVERFLOW_ANCHOR is less effective than Chromium's.
  551.  
  552. const ENABLE_OVERFLOW_ANCHOR = ENABLE_OVERFLOW_ANCHOR_PREFERRED && ENABLE_OVERFLOW_ANCHOR_CAPABLE && ENABLE_NO_SMOOTH_TRANSFORM;
  553. const ENABLE_FULL_RENDER_REQUIRED = ENABLE_FULL_RENDER_REQUIRED_PREFERRED && ENABLE_FULL_RENDER_REQUIRED_CAPABLE && ENABLE_OVERFLOW_ANCHOR && ENABLE_NO_SMOOTH_TRANSFORM && NOT_FIREFOX;
  554.  
  555.  
  556. const fxOperator = (proto, propertyName) => {
  557. let propertyDescriptorGetter = null;
  558. try {
  559. propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
  560. } catch (e) { }
  561. return typeof propertyDescriptorGetter === 'function' ? (e) => {
  562. try {
  563.  
  564. return propertyDescriptorGetter.call(dr(e));
  565. } catch (e) { }
  566. return e[propertyName];
  567. } : (e) => e[propertyName];
  568. };
  569.  
  570. const nodeParent = fxOperator(Node.prototype, 'parentNode');
  571. // const nFirstElem = fxOperator(HTMLElement.prototype, 'firstElementChild');
  572. const nPrevElem = fxOperator(HTMLElement.prototype, 'previousElementSibling');
  573. const nNextElem = fxOperator(HTMLElement.prototype, 'nextElementSibling');
  574. const nLastElem = fxOperator(HTMLElement.prototype, 'lastElementChild');
  575.  
  576.  
  577. const groupCollapsed = (text1, text2) => {
  578.  
  579. console.groupCollapsed(`%c${text1}%c${text2}`,
  580. "background-color: #010502; color: #6acafe; font-weight: 700; padding: 2px;",
  581. "background-color: #010502; color: #6ad9fe; font-weight: 300; padding: 2px;"
  582. );
  583. }
  584.  
  585.  
  586. const EVENT_KEY_ON_REGISTRY_READY = "ytI-ce-registry-created";
  587. const onRegistryReady = (callback) => {
  588. if (typeof customElements === 'undefined') {
  589. if (!('__CE_registry' in document)) {
  590. // https://github.com/webcomponents/polyfills/
  591. Object.defineProperty(document, '__CE_registry', {
  592. get() {
  593. // return undefined
  594. },
  595. set(nv) {
  596. if (typeof nv == 'object') {
  597. delete this.__CE_registry;
  598. this.__CE_registry = nv;
  599. this.dispatchEvent(new CustomEvent(EVENT_KEY_ON_REGISTRY_READY));
  600. }
  601. return true;
  602. },
  603. enumerable: false,
  604. configurable: true
  605. })
  606. }
  607. let eventHandler = (evt) => {
  608. document.removeEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
  609. const f = callback;
  610. callback = null;
  611. eventHandler = null;
  612. f();
  613. };
  614. document.addEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
  615. } else {
  616. callback();
  617. }
  618. };
  619.  
  620. const promiseForCustomYtElementsReady = new Promise(onRegistryReady);
  621.  
  622. /* globals WeakRef:false */
  623.  
  624. /** @type {(o: Object | null) => WeakRef | null} */
  625. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null);
  626.  
  627. /** @type {(wr: Object | null) => Object | null} */
  628. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  629.  
  630. const { addCssManaged } = (() => {
  631.  
  632. const addFontPreRendering = () => {
  633.  
  634. groupCollapsed("YouTube Super Fast Chat", " | Fonts Pre-Rendering");
  635.  
  636. let efsContainer = document.createElement('elzm-fonts');
  637. efsContainer.id = 'elzm-fonts-yk75g'
  638.  
  639. const arr = [];
  640. let p = document.createElement('elzm-font');
  641. arr.push(p);
  642.  
  643. if (ENABLE_FONT_PRE_RENDERING & 1) {
  644. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  645.  
  646. p = document.createElement('elzm-font');
  647. p.style.fontWeight = size;
  648. arr.push(p);
  649. }
  650. }
  651.  
  652. if (ENABLE_FONT_PRE_RENDERING & 2) {
  653. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  654.  
  655. p = document.createElement('elzm-font');
  656. p.style.fontFamily = 'Roboto';
  657. p.style.fontWeight = size;
  658. arr.push(p);
  659. }
  660. }
  661.  
  662. if (ENABLE_FONT_PRE_RENDERING & 4) {
  663. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  664.  
  665. p = document.createElement('elzm-font');
  666. p.style.fontFamily = '"YouTube Noto",Roboto,Arial,Helvetica,sans-serif';
  667. p.style.fontWeight = size;
  668. arr.push(p);
  669. }
  670. }
  671.  
  672.  
  673. if (ENABLE_FONT_PRE_RENDERING & 8) {
  674. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  675.  
  676. p = document.createElement('elzm-font');
  677. p.style.fontFamily = '"Noto",Roboto,Arial,Helvetica,sans-serif';
  678. p.style.fontWeight = size;
  679. arr.push(p);
  680. }
  681. }
  682.  
  683.  
  684. if (ENABLE_FONT_PRE_RENDERING & 16) {
  685. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  686.  
  687. p = document.createElement('elzm-font');
  688. p.style.fontFamily = 'sans-serif';
  689. p.style.fontWeight = size;
  690. arr.push(p);
  691. }
  692. }
  693.  
  694. console.log('number of elzm-font elements', arr.length);
  695.  
  696. HTMLElement.prototype.append.apply(efsContainer, arr);
  697.  
  698. (document.body || document.documentElement).appendChild(efsContainer);
  699.  
  700.  
  701. console.log('elzm-font elements have been added to the page for rendering.');
  702.  
  703. console.groupEnd();
  704.  
  705. }
  706.  
  707. let isCssAdded = false;
  708.  
  709. function addCssElement() {
  710. let s = document.createElement('style');
  711. s.id = 'ewRvC';
  712. return s;
  713. }
  714.  
  715. const addCssManaged = () => {
  716. if (!isCssAdded && document.documentElement && document.head) {
  717. isCssAdded = true;
  718. document.head.appendChild(dr(addCssElement())).textContent = addCss();
  719. if (ENABLE_FONT_PRE_RENDERING) {
  720. Promise.resolve().then(addFontPreRendering)
  721. }
  722. }
  723. }
  724.  
  725. return { addCssManaged };
  726. })();
  727.  
  728.  
  729. const { setupStyle } = (() => {
  730.  
  731. const sp7 = Symbol();
  732.  
  733. const proxyHelperFn = (dummy) => ({
  734.  
  735. get(target, prop) {
  736. return (prop in dummy) ? dummy[prop] : prop === sp7 ? target : target[prop];
  737. },
  738. set(target, prop, value) {
  739. if (!(prop in dummy)) {
  740. target[prop] = value;
  741. }
  742. return true;
  743. },
  744. has(target, prop) {
  745. return (prop in target);
  746. },
  747. deleteProperty(target, prop) {
  748. return true;
  749. },
  750. ownKeys(target) {
  751. return Object.keys(target);
  752. },
  753. defineProperty(target, key, descriptor) {
  754. return Object.defineProperty(target, key, descriptor);
  755. },
  756. getOwnPropertyDescriptor(target, key) {
  757. return Object.getOwnPropertyDescriptor(target, key);
  758. },
  759.  
  760. });
  761.  
  762. const setupStyle = (m1, m2) => {
  763. if (!ENABLE_NO_SMOOTH_TRANSFORM) return;
  764.  
  765. const dummy1v = {
  766. transform: '',
  767. height: '',
  768. minHeight: '',
  769. paddingBottom: '',
  770. paddingTop: ''
  771. };
  772.  
  773. const dummyStyleFn = (k) => (function () { const style = this[sp7]; return style[k](...arguments); });
  774. for (const k of ['toString', 'getPropertyPriority', 'getPropertyValue', 'item', 'removeProperty', 'setProperty']) {
  775. dummy1v[k] = dummyStyleFn(k);
  776. }
  777.  
  778. const dummy1p = proxyHelperFn(dummy1v);
  779. const sp1v = new Proxy(m1.style, dummy1p);
  780. const sp2v = new Proxy(m2.style, dummy1p);
  781. Object.defineProperty(m1, 'style', { get() { return sp1v }, set() { }, enumerable: true, configurable: true });
  782. Object.defineProperty(m2, 'style', { get() { return sp2v }, set() { }, enumerable: true, configurable: true });
  783. m1.removeAttribute("style");
  784. m2.removeAttribute("style");
  785.  
  786. }
  787.  
  788. return { setupStyle };
  789.  
  790. })();
  791.  
  792.  
  793. const cleanContext = async (win) => {
  794. const waitFn = requestAnimationFrame; // shall have been binded to window
  795. try {
  796. let mx = 16; // MAX TRIAL
  797. const frameId = 'vanillajs-iframe-v1';
  798. /** @type {HTMLIFrameElement | null} */
  799. let frame = document.getElementById(frameId);
  800. let removeIframeFn = null;
  801. if (!frame) {
  802. frame = document.createElement('iframe');
  803. frame.id = 'vanillajs-iframe-v1';
  804. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  805. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  806. n.appendChild(frame);
  807. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  808. const root = document.documentElement;
  809. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  810. removeIframeFn = (setTimeout) => {
  811. const removeIframeOnDocumentReady = (e) => {
  812. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  813. win = null;
  814. setTimeout(() => {
  815. n.remove();
  816. n = null;
  817. }, 200);
  818. }
  819. if (document.readyState !== 'loading') {
  820. removeIframeOnDocumentReady();
  821. } else {
  822. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  823. }
  824. }
  825. }
  826. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  827. const fc = frame.contentWindow;
  828. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  829. const { requestAnimationFrame, setTimeout, cancelAnimationFrame } = fc;
  830. const res = { requestAnimationFrame, setTimeout, cancelAnimationFrame };
  831. for (let k in res) res[k] = res[k].bind(win); // necessary
  832. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  833. return res;
  834. } catch (e) {
  835. console.warn(e);
  836. return null;
  837. }
  838. };
  839.  
  840. cleanContext(win).then(__CONTEXT__ => {
  841. if (!__CONTEXT__) return null;
  842.  
  843.  
  844. const { requestAnimationFrame, setTimeout, cancelAnimationFrame } = __CONTEXT__;
  845.  
  846.  
  847. (() => {
  848. // data manipulation
  849.  
  850. if (!DO_PARTICIPANT_LIST_HACKS) return;
  851.  
  852. class Mutex {
  853.  
  854. constructor() {
  855. this.p = Promise.resolve()
  856. }
  857.  
  858. /**
  859. * @param {(lockResolve: () => void)} f
  860. */
  861. lockWith(f) {
  862. this.p = this.p.then(() => new Promise(f).catch(console.warn))
  863. }
  864.  
  865. }
  866.  
  867.  
  868. /**
  869.  
  870. h.onParticipantsChanged = function() {
  871. this.notifyPath("participantsManager.participants")
  872. }
  873.  
  874.  
  875. at h.onParticipantsChanged (live_chat_polymer.js:8334:41)
  876. at e.<anonymous> (live_chat_polymer.js:1637:69)
  877. at e.Bb [as __shady_dispatchEvent] (webcomponents-sd.js:46:110)
  878. at k.dispatchEvent (webcomponents-sd.js:122:237)
  879. at mu (live_chat_polymer.js:1677:71)
  880. at Object.wga [as fn] (live_chat_polymer.js:1678:99)
  881. at a._propertiesChanged (live_chat_polymer.js:1726:426)
  882. at b._flushProperties (live_chat_polymer.js:1597:200)
  883. at a._invalidateProperties (live_chat_polymer.js:1718:69)
  884. at a.notifyPath (live_chat_polymer.js:1741:182)
  885.  
  886. */
  887.  
  888.  
  889. const beforeParticipantsMap = new WeakMap();
  890.  
  891. const { notifyPath7081 } = (() => {
  892.  
  893.  
  894. const foundMap = (base, content) => {
  895.  
  896. let lastSearch = 0;
  897. let founds = base.map(baseEntry => {
  898. let search = content.indexOf(baseEntry, lastSearch);
  899. if (search < 0) return false;
  900. lastSearch = search + 1;
  901. return true;
  902. });
  903. return founds;
  904.  
  905.  
  906. }
  907.  
  908.  
  909.  
  910. const spliceIndicesFunc = (beforeParticipants, participants, idsBefore, idsAfter) => {
  911.  
  912. const handler1 = {
  913. get(target, prop, receiver) {
  914. if (prop === 'object') {
  915. return participants; // avoid memory leakage
  916. }
  917. if (prop === 'type') {
  918. return 'splice';
  919. }
  920. return target[prop];
  921. }
  922. };
  923. const releaser = () => {
  924. beforeParticipants = null;
  925. participants = null;
  926. idsBefore = null;
  927. idsAfter = null;
  928. }
  929.  
  930.  
  931. let foundsForAfter = foundMap(idsAfter, idsBefore);
  932. let foundsForBefore = foundMap(idsBefore, idsAfter);
  933.  
  934. let indexSplices = [];
  935. let contentUpdates = [];
  936. for (let i = 0, j = 0; i < foundsForBefore.length || j < foundsForAfter.length;) {
  937.  
  938. if (beforeParticipants[i] === participants[j]) {
  939. i++; j++;
  940. } else if (idsBefore[i] === idsAfter[j]) {
  941. // content changed
  942. contentUpdates.push({ indexI: i, indexJ: j })
  943. i++; j++;
  944. } else {
  945. let addedCount = 0;
  946. for (let q = j; q < foundsForAfter.length; q++) {
  947. if (foundsForAfter[q] === false) addedCount++;
  948. else break;
  949. }
  950.  
  951. let removedCount = 0;
  952. for (let q = i; q < foundsForBefore.length; q++) {
  953. if (foundsForBefore[q] === false) removedCount++;
  954. else break;
  955. }
  956. if (!addedCount && !removedCount) {
  957. throw 'ERROR(0xFF32): spliceIndicesFunc';
  958. }
  959. indexSplices.push(new Proxy({
  960. index: j,
  961. addedCount: addedCount,
  962. removed: removedCount >= 1 ? beforeParticipants.slice(i, i + removedCount) : []
  963. }, handler1));
  964.  
  965. i += removedCount;
  966. j += addedCount;
  967.  
  968. }
  969. }
  970. return { indexSplices, contentUpdates, releaser };
  971.  
  972.  
  973. }
  974.  
  975. const mutexParticipants = new Mutex();
  976.  
  977.  
  978. /*
  979.  
  980. customElements.get("yt-live-chat-participant-renderer").prototype.notifyPath=function(){ console.log(123); console.log(new Error().stack)}
  981.  
  982. VM63631:1 Error
  983. at customElements.get.notifyPath (<anonymous>:1:122)
  984. at e.forwardRendererStamperChanges_ (live_chat_polymer.js:4453:35)
  985. at e.rendererStamperApplyChangeRecord_ (live_chat_polymer.js:4451:12)
  986. at e.rendererStamperObserver_ (live_chat_polymer.js:4448:149)
  987. at Object.pu [as fn] (live_chat_polymer.js:1692:118)
  988. at ju (live_chat_polymer.js:1674:217)
  989. at a._propertiesChanged (live_chat_polymer.js:1726:122)
  990. at b._flushProperties (live_chat_polymer.js:1597:200)
  991. at a._invalidateProperties (live_chat_polymer.js:1718:69)
  992. at a.notifyPath (live_chat_polymer.js:1741:182)
  993.  
  994. */
  995.  
  996. function convertToIds(participants) {
  997. return participants.map(participant => {
  998. if (!participant || typeof participant !== 'object') {
  999. console.warn('Error(0xFA41): convertToIds', participant);
  1000. return participant; // just in case
  1001. }
  1002. let keys = Object.keys(participant);
  1003. // liveChatTextMessageRenderer
  1004. // liveChatParticipantRenderer - livestream channel owner [no authorExternalChannelId]
  1005. // liveChatPaidMessageRenderer
  1006. /*
  1007.  
  1008. 'yt-live-chat-participant-renderer' utilizes the following:
  1009. authorName.simpleText: string
  1010. authorPhoto.thumbnails: Object{url:string, width:int, height:int} []
  1011. authorBadges[].liveChatAuthorBadgeRenderer.icon.iconType: string
  1012. authorBadges[].liveChatAuthorBadgeRenderer.tooltip: string
  1013. authorBadges[].liveChatAuthorBadgeRenderer.accessibility.accessibilityData: Object{label:string}
  1014.  
  1015. */
  1016. if (keys.length !== 1) {
  1017. console.warn('Error(0xFA42): convertToIds', participant);
  1018. return participant; // just in case
  1019. }
  1020. let key = keys[0];
  1021. let renderer = (participant[key] || 0);
  1022. let authorName = (renderer.authorName || 0);
  1023. let text = `${authorName.simpleText || authorName.text}`
  1024. let res = participant; // fallback if it is not a vaild entry
  1025. if (typeof text !== 'string') {
  1026. console.warn('Error(0xFA53): convertToIds', participant);
  1027. } else {
  1028. text = `${renderer.authorExternalChannelId || 'null'}|${text || ''}`;
  1029. if (text.length > 1) res = text;
  1030. }
  1031. return res;
  1032. // return renderer?`${renderer.id}|${renderer.authorExternalChannelId}`: '';
  1033. // note: renderer.id will be changed if the user typed something to trigger the update of the participants' record.
  1034. });
  1035. }
  1036.  
  1037. const checkChangeToParticipantRendererContent = CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT ? (p1, p2) => {
  1038. // just update when content is changed.
  1039. if (p1.authorName !== p2.authorName) return true;
  1040. if (p1.authorPhoto !== p2.authorPhoto) return true;
  1041. if (p1.authorBadges !== p2.authorBadges) return true;
  1042. return false;
  1043. } : (p1, p2) => {
  1044. // keep integrity all the time.
  1045. return p1 !== p2; // always true
  1046. }
  1047.  
  1048. let uvid = 0;
  1049.  
  1050. function notifyPath7081(path) { // cnt "yt-live-chat-participant-list-renderer"
  1051.  
  1052. let stack = new Error().stack;
  1053. if (path !== "participantsManager.participants" || stack.indexOf('.onParticipantsChanged') < 0) {
  1054. return this.__notifyPath5036__.apply(this, arguments);
  1055. }
  1056.  
  1057. if (uvid > 1e8) uvid = uvid % 100;
  1058. let tid = ++uvid;
  1059.  
  1060. const cnt = this; // "yt-live-chat-participant-list-renderer"
  1061. mutexParticipants.lockWith(lockResolve => {
  1062.  
  1063. if (tid !== uvid) {
  1064. lockResolve();
  1065. return;
  1066. }
  1067.  
  1068. const participants = cnt.participantsManager.participants.slice(0);
  1069. const beforeParticipants = beforeParticipantsMap.get(cnt) || [];
  1070. beforeParticipantsMap.set(cnt, participants);
  1071.  
  1072. const resPromise = (async () => {
  1073.  
  1074. if (beforeParticipants.length === 0) {
  1075. // not error
  1076. return 0;
  1077. }
  1078.  
  1079. let countOfElements = cnt.__getAllParticipantsDOMRenderedLength__()
  1080.  
  1081. // console.log(participants.length, doms.length) // different if no requestAnimationFrame
  1082. if (beforeParticipants.length !== countOfElements) {
  1083. // there is somewrong for the cache. - sometimes happen
  1084. return 0;
  1085. }
  1086.  
  1087. const idsBefore = convertToIds(beforeParticipants);
  1088. const idsAfter = convertToIds(participants);
  1089.  
  1090. let { indexSplices, contentUpdates, releaser } = spliceIndicesFunc(beforeParticipants, participants, idsBefore, idsAfter);
  1091.  
  1092. let res = 1; // default 1 for no update
  1093.  
  1094. if (indexSplices.length >= 1) {
  1095.  
  1096.  
  1097. // let p2 = participants.slice(indexSplices[0].index, indexSplices[0].index+indexSplices[0].addedCount);
  1098. // let p1 = indexSplices[0].removed;
  1099. // console.log(indexSplices.length, indexSplices ,p1,p2, convertToIds(p1),convertToIds(p2))
  1100.  
  1101. /* folllow
  1102. a.notifyPath(c + ".splices", d);
  1103. a.notifyPath(c + ".length", b.length);
  1104. */
  1105. // stampDomArraySplices_
  1106.  
  1107.  
  1108. await new Promise(resolve => {
  1109. cnt.resolveForDOMRendering781 = resolve;
  1110.  
  1111. cnt.__notifyPath5036__("participantsManager.participants.splices", {
  1112. indexSplices
  1113. });
  1114. indexSplices = null;
  1115. releaser();
  1116. releaser = null;
  1117. cnt.__notifyPath5036__("participantsManager.participants.length",
  1118. participants.length
  1119. );
  1120.  
  1121. });
  1122.  
  1123. await Promise.resolve(0); // play safe for the change of 'length'
  1124. countOfElements = cnt.__getAllParticipantsDOMRenderedLength__();
  1125.  
  1126. let wrongSize = participants.length !== countOfElements
  1127. if (wrongSize) {
  1128. console.warn("ERROR(0xE2C3): notifyPath7081", beforeParticipants.length, participants.length, doms.length)
  1129. return 0;
  1130. }
  1131.  
  1132. res = 2 | 4;
  1133.  
  1134. } else {
  1135.  
  1136. indexSplices = null;
  1137. releaser();
  1138. releaser = null;
  1139.  
  1140. if (participants.length !== countOfElements) {
  1141. // other unhandled cases
  1142. return 0;
  1143. }
  1144.  
  1145. }
  1146.  
  1147. // participants.length === countOfElements before contentUpdates
  1148. if (contentUpdates.length >= 1) {
  1149. for (const contentUpdate of contentUpdates) {
  1150. let isChanged = checkChangeToParticipantRendererContent(beforeParticipants[contentUpdate.indexI], participants[contentUpdate.indexJ]);
  1151. if (isChanged) {
  1152. cnt.__notifyPath5036__(`participantsManager.participants[${contentUpdate.indexJ}]`);
  1153. res |= 4 | 8;
  1154. }
  1155. }
  1156. }
  1157. contentUpdates = null;
  1158.  
  1159. return res;
  1160.  
  1161.  
  1162. })();
  1163.  
  1164.  
  1165. resPromise.then(resValue => {
  1166.  
  1167. const isLogRequired = SHOW_PARTICIPANT_CHANGES_IN_CONSOLE && ((resValue === 0) || ((resValue & 4) === 4));
  1168. isLogRequired && groupCollapsed("Participant List Change", `tid = ${tid}; res = ${resValue}`);
  1169. if (resValue === 0) {
  1170. new Promise(resolve => {
  1171. cnt.resolveForDOMRendering781 = resolve;
  1172. isLogRequired && console.log("Full Refresh begins");
  1173. cnt.__notifyPath5036__("participantsManager.participants"); // full refresh
  1174. }).then(() => {
  1175. isLogRequired && console.log("Full Refresh ends");
  1176. console.groupEnd();
  1177. }).then(lockResolve);
  1178. return;
  1179. }
  1180.  
  1181. const delayLockResolve = (resValue & 4) === 4;
  1182.  
  1183. if (delayLockResolve) {
  1184. isLogRequired && console.log(`Number of participants (before): ${beforeParticipants.length}`);
  1185. isLogRequired && console.log(`Number of participants (after): ${participants.length}`);
  1186. isLogRequired && console.log(`Total number of rendered participants: ${cnt.__getAllParticipantsDOMRenderedLength__()}`);
  1187. isLogRequired && console.log(`Participant Renderer Content Updated: ${(resValue & 8) === 8}`);
  1188. isLogRequired && console.groupEnd();
  1189. // requestAnimationFrame is required to avoid particiant update during DOM changing (stampDomArraySplices_)
  1190. // mutex lock with requestAnimationFrame can also disable participants update in background
  1191. requestAnimationFrame(lockResolve);
  1192. } else {
  1193. lockResolve();
  1194. }
  1195.  
  1196. });
  1197.  
  1198. });
  1199.  
  1200.  
  1201. }
  1202.  
  1203. return { notifyPath7081 };
  1204.  
  1205. })();
  1206.  
  1207.  
  1208. const onRegistryReadyForDataManipulation = () => {
  1209.  
  1210. function dummy5035(a, b, c) { }
  1211. function dummy411(a, b, c) { }
  1212.  
  1213. customElements.whenDefined("yt-live-chat-participant-list-renderer").then(() => {
  1214.  
  1215. if (!DO_PARTICIPANT_LIST_HACKS) return;
  1216.  
  1217. const tag = "yt-live-chat-participant-list-renderer"
  1218. const cProto = getProto(document.createElement(tag));
  1219. if (!cProto || typeof cProto.attached !== 'function') {
  1220. // for _registered, proto.attached shall exist when the element is defined.
  1221. // for controller extraction, attached shall exist when instance creates.
  1222. console.warn(`proto.attached for ${tag} is unavailable.`);
  1223. return;
  1224. }
  1225.  
  1226.  
  1227. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-participant-list-renderer hacks");
  1228.  
  1229. const canDoReplacement = (() => {
  1230. if (typeof cProto.__notifyPath5035__ === 'function' && cProto.__notifyPath5035__.name !== 'dummy5035') {
  1231. console.warn('YouTube Live Chat Tamer is running.');
  1232. return;
  1233. }
  1234.  
  1235. if (typeof cProto.__attached411__ === 'function' && cProto.__attached411__.name !== 'dummy411') {
  1236. console.warn('YouTube Live Chat Tamer is running.');
  1237. return;
  1238. }
  1239.  
  1240. cProto.__notifyPath5035__ = dummy5035 // just to against Live Chat Tamer
  1241. cProto.__attached411__ = dummy411 // just to against Live Chat Tamer
  1242.  
  1243. if (typeof cProto.flushRenderStamperComponentBindings_ !== 'function' || cProto.flushRenderStamperComponentBindings_.length !== 0) {
  1244. console.warn("ERROR(0xE355): cProto.flushRenderStamperComponentBindings_ not found");
  1245. return;
  1246. }
  1247.  
  1248. if (typeof cProto.flushRenderStamperComponentBindings66_ === 'function') {
  1249. console.warn("ERROR(0xE356): cProto.flushRenderStamperComponentBindings66_");
  1250. return;
  1251. }
  1252.  
  1253. if (typeof cProto.__getAllParticipantsDOMRenderedLength__ === 'function') {
  1254. console.warn("ERROR(0xE357): cProto.__getAllParticipantsDOMRenderedLength__");
  1255. return;
  1256. }
  1257. return true;
  1258. })();
  1259.  
  1260. console.log(`Data Manipulation Boost = ${canDoReplacement}`);
  1261.  
  1262.  
  1263. assertor(() => fnIntegrity(cProto.attached, '0.32.22')) // just warning
  1264. assertor(() => fnIntegrity(cProto.flushRenderStamperComponentBindings_, '0.385.233')) // just warning
  1265.  
  1266.  
  1267. cProto.flushRenderStamperComponentBindings66_ = cProto.flushRenderStamperComponentBindings_;
  1268.  
  1269. cProto.flushRenderStamperComponentBindings_ = function () {
  1270. // console.log('flushRenderStamperComponentBindings_')
  1271. this.flushRenderStamperComponentBindings66_();
  1272. if (this.resolveForDOMRendering781) {
  1273. this.resolveForDOMRendering781();
  1274. this.resolveForDOMRendering781 = null;
  1275. }
  1276. }
  1277.  
  1278. cProto.__getAllParticipantsDOMRenderedLength__ = function () {
  1279. const container = this.$.participants;
  1280. return HTMLElement.prototype.querySelectorAll.call(container, 'yt-live-chat-participant-renderer').length;
  1281. }
  1282.  
  1283. const onPageElements = [...document.querySelectorAll('yt-live-chat-participant-list-renderer:not(.n9fJ3)')];
  1284.  
  1285. cProto.__attached412__ = cProto.attached;
  1286. const fpPList = function (hostElement) {
  1287. const cnt = hostElement.inst || hostElement;
  1288. if (beforeParticipantsMap.has(cnt)) return;
  1289. hostElement.classList.add('n9fJ3');
  1290.  
  1291. assertor(() => (cnt.__dataEnabled === true && cnt.__dataReady === true));
  1292. if (typeof cnt.notifyPath !== 'function' || typeof cnt.__notifyPath5036__ !== 'undefined') {
  1293. console.warn("ERROR(0xE318): yt-live-chat-participant-list-renderer")
  1294. return;
  1295. }
  1296.  
  1297. groupCollapsed("Participant List attached", "");
  1298. // cnt.$.participants.appendChild = cnt.$.participants.__shady_native_appendChild = function(){
  1299. // console.log(123, 'appendChild');
  1300. // return HTMLElement.prototype.appendChild.apply(this, arguments)
  1301. // }
  1302.  
  1303. // cnt.$.participants.insertBefore =cnt.$.participants.__shady_native_insertBefore = function(){
  1304. // console.log(123, 'insertBefore');
  1305. // return HTMLElement.prototype.insertBefore.apply(this, arguments)
  1306. // }
  1307.  
  1308. cnt.__notifyPath5036__ = cnt.notifyPath
  1309. const participants = ((cnt.participantsManager || 0).participants || 0);
  1310. assertor(() => (participants.length > -1 && typeof participants.slice === 'function'));
  1311. console.log(`initial number of participants: ${participants.length}`);
  1312. const newParticipants = (participants.length >= 1 && typeof participants.slice === 'function') ? participants.slice(0) : [];
  1313. beforeParticipantsMap.set(cnt, newParticipants);
  1314. cnt.notifyPath = notifyPath7081;
  1315. console.log(`CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT = ${CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT}`);
  1316. console.groupEnd();
  1317. }
  1318. cProto.attached = function () {
  1319. fpPList(this.hostElement || this);
  1320. this.__attached412__.apply(this, arguments);
  1321. };
  1322.  
  1323. console.groupEnd();
  1324.  
  1325. if (onPageElements.length >= 1) {
  1326. for (const s of onPageElements) {
  1327. if ((s.inst || s).isAttached === true) {
  1328. fpPList(s);
  1329. }
  1330. }
  1331. }
  1332.  
  1333. });
  1334.  
  1335. };
  1336.  
  1337. promiseForCustomYtElementsReady.then(onRegistryReadyForDataManipulation);
  1338.  
  1339.  
  1340. })();
  1341.  
  1342. IntersectionObserver && (() => {
  1343.  
  1344. // dom manipulation
  1345.  
  1346. class RAFHub {
  1347. constructor() {
  1348. /** @type {number} */
  1349. this.startAt = 8170;
  1350. /** @type {number} */
  1351. this.counter = 0;
  1352. /** @type {number} */
  1353. this.rid = 0;
  1354. /** @type {Map<number, FrameRequestCallback>} */
  1355. this.funcs = new Map();
  1356. const funcs = this.funcs;
  1357. /** @type {FrameRequestCallback} */
  1358. this.bCallback = this.mCallback.bind(this);
  1359. this.pClear = () => funcs.clear();
  1360. }
  1361. /** @param {DOMHighResTimeStamp} highResTime */
  1362. mCallback(highResTime) {
  1363. this.rid = 0;
  1364. Promise.resolve().then(this.pClear);
  1365. this.funcs.forEach(func => Promise.resolve(highResTime).then(func).catch(console.warn));
  1366. }
  1367. /** @param {FrameRequestCallback} f */
  1368. request(f) {
  1369. if (this.counter > 1e9) this.counter = 9;
  1370. let cid = this.startAt + (++this.counter);
  1371. this.funcs.set(cid, f);
  1372. if (this.rid === 0) this.rid = requestAnimationFrame(this.bCallback);
  1373. return cid;
  1374. }
  1375. /** @param {number} cid */
  1376. cancel(cid) {
  1377. cid = +cid;
  1378. if (cid > 0) {
  1379. if (cid <= this.startAt) {
  1380. return cancelAnimationFrame(cid);
  1381. }
  1382. if (this.rid > 0) {
  1383. this.funcs.delete(cid);
  1384. if (this.funcs.size === 0) {
  1385. cancelAnimationFrame(this.rid);
  1386. this.rid = 0;
  1387. }
  1388. }
  1389. }
  1390. }
  1391. }
  1392.  
  1393.  
  1394. const rafHub = (ENABLE_RAF_HACK_TICKERS || ENABLE_RAF_HACK_DOCKED_MESSAGE || ENABLE_RAF_HACK_INPUT_RENDERER || ENABLE_RAF_HACK_EMOJI_PICKER) ? new RAFHub() : null;
  1395.  
  1396.  
  1397. let dt0 = Date.now() - 2000;
  1398. const dateNow = () => Date.now() - dt0;
  1399. // let lastScroll = 0;
  1400. // let lastLShow = 0;
  1401. let lastWheel = 0;
  1402. let lastMouseDown = 0;
  1403. let lastMouseUp = 0;
  1404. let currentMouseDown = false;
  1405. let lastTouchDown = 0;
  1406. let lastTouchUp = 0;
  1407. let currentTouchDown = false;
  1408. let lastUserInteraction = 0;
  1409.  
  1410. let scrollChatFn = null;
  1411.  
  1412. ENABLE_FULL_RENDER_REQUIRED && (() => {
  1413.  
  1414. document.addEventListener('animationstart', (evt) => {
  1415.  
  1416. if (evt.animationName === 'dontRenderAnimation') {
  1417. evt.target.classList.remove('dont-render');
  1418. if (scrollChatFn) scrollChatFn();
  1419. }
  1420.  
  1421. }, true);
  1422.  
  1423. const f = (elm) => {
  1424. if (elm && elm.nodeType === 1) {
  1425. elm.classList.add('dont-render');
  1426. }
  1427. }
  1428.  
  1429. Node.prototype.__appendChild931__ = function (a) {
  1430. a = dr(a);
  1431. if (this.id === 'items' && this.classList.contains('yt-live-chat-item-list-renderer')) {
  1432. if (a && a.nodeType === 1) f(a);
  1433. else if (a instanceof DocumentFragment) {
  1434. for (let n = a.firstChild; n; n = n.nextSibling) {
  1435. f(n);
  1436. }
  1437. }
  1438. }
  1439. }
  1440.  
  1441. Node.prototype.__appendChild932__ = function () {
  1442. this.__appendChild931__.apply(this, arguments);
  1443. return Node.prototype.appendChild.apply(this, arguments);
  1444. }
  1445.  
  1446.  
  1447. })();
  1448.  
  1449.  
  1450. const watchUserCSS = () => {
  1451.  
  1452. // if (!CSS.supports('contain-intrinsic-size', 'auto var(--wsr94)')) return;
  1453.  
  1454. const getElemFromWR = (nr) => {
  1455. const n = kRef(nr);
  1456. if (n && n.isConnected) return n;
  1457. return null;
  1458. }
  1459.  
  1460. const clearContentVisibilitySizing = () => {
  1461. Promise.resolve().then(() => {
  1462.  
  1463. let btnShowMoreWR = mWeakRef(document.querySelector('#show-more[disabled]'));
  1464.  
  1465. let lastVisibleItemWR = null;
  1466. for (const elm of document.querySelectorAll('[wSr93]')) {
  1467. if (elm.getAttribute('wSr93') === 'visible') lastVisibleItemWR = mWeakRef(elm);
  1468. elm.setAttribute('wSr93', '');
  1469. // custom CSS property --wsr94 not working when attribute wSr93 removed
  1470. }
  1471. requestAnimationFrame(() => {
  1472. const btnShowMore = getElemFromWR(btnShowMoreWR); btnShowMoreWR = null;
  1473. if (btnShowMore) btnShowMore.click();
  1474. else {
  1475. // would not work if switch it frequently
  1476. const lastVisibleItem = getElemFromWR(lastVisibleItemWR); lastVisibleItemWR = null;
  1477. if (lastVisibleItem) {
  1478.  
  1479. Promise.resolve()
  1480. .then(() => lastVisibleItem.scrollIntoView())
  1481. .then(() => lastVisibleItem.scrollIntoView(false))
  1482. .then(() => lastVisibleItem.scrollIntoView({ behavior: "instant", block: "end", inline: "nearest" }))
  1483. .catch(e => { }) // break the chain when method not callable
  1484.  
  1485. }
  1486. }
  1487. });
  1488.  
  1489. });
  1490.  
  1491. }
  1492.  
  1493. const mutObserver = new MutationObserver((mutations) => {
  1494. for (const mutation of mutations) {
  1495. if ((mutation.addedNodes || 0).length >= 1) {
  1496. for (const addedNode of mutation.addedNodes) {
  1497. if (addedNode.nodeName === 'STYLE') {
  1498. clearContentVisibilitySizing();
  1499. return;
  1500. }
  1501. }
  1502. }
  1503. if ((mutation.removedNodes || 0).length >= 1) {
  1504. for (const removedNode of mutation.removedNodes) {
  1505. if (removedNode.nodeName === 'STYLE') {
  1506. clearContentVisibilitySizing();
  1507. return;
  1508. }
  1509. }
  1510. }
  1511. }
  1512. });
  1513.  
  1514. mutObserver.observe(document.documentElement, {
  1515. childList: true,
  1516. subtree: false
  1517. });
  1518. mutObserver.observe(document.head, {
  1519. childList: true,
  1520. subtree: false
  1521. });
  1522. mutObserver.observe(document.body, {
  1523. childList: true,
  1524. subtree: false
  1525. });
  1526.  
  1527. }
  1528.  
  1529.  
  1530. class WillChangeController {
  1531. constructor(itemScroller, willChangeValue) {
  1532. this.element = itemScroller;
  1533. this.counter = 0;
  1534. this.active = false;
  1535. this.willChangeValue = willChangeValue;
  1536. }
  1537.  
  1538. beforeOper() {
  1539. if (!this.active) {
  1540. this.active = true;
  1541. this.element.style.willChange = this.willChangeValue;
  1542. }
  1543. this.counter++;
  1544. }
  1545.  
  1546. afterOper() {
  1547. const c = this.counter;
  1548. requestAnimationFrame(() => {
  1549. if (c === this.counter) {
  1550. this.active = false;
  1551. this.element.style.willChange = '';
  1552. }
  1553. });
  1554. }
  1555.  
  1556. release() {
  1557. const element = this.element;
  1558. this.element = null;
  1559. this.counter = 1e16;
  1560. this.active = false;
  1561. try {
  1562. element.style.willChange = '';
  1563. } catch (e) { }
  1564. }
  1565.  
  1566. }
  1567.  
  1568.  
  1569. const { lcRendererElm, visObserver } = (() => {
  1570.  
  1571.  
  1572.  
  1573. let lcRendererWR = null;
  1574.  
  1575. const lcRendererElm = () => {
  1576. let lcRenderer = kRef(lcRendererWR);
  1577. if (!lcRenderer || !lcRenderer.isConnected) {
  1578. lcRenderer = document.querySelector('yt-live-chat-item-list-renderer.yt-live-chat-renderer');
  1579. lcRendererWR = lcRenderer ? mWeakRef(lcRenderer) : null;
  1580. }
  1581. return lcRenderer;
  1582. };
  1583.  
  1584.  
  1585. let hasFirstShowMore = false;
  1586.  
  1587. const visObserverFn = (entry) => {
  1588.  
  1589. const target = entry.target;
  1590. if (!target) return;
  1591. // if(target.classList.contains('dont-render')) return;
  1592. let isVisible = entry.isIntersecting === true && entry.intersectionRatio > 0.5;
  1593. // const h = entry.boundingClientRect.height;
  1594. /*
  1595. if (h < 16) { // wrong: 8 (padding/margin); standard: 32; test: 16 or 20
  1596. // e.g. under fullscreen. the element created but not rendered.
  1597. target.setAttribute('wSr93', '');
  1598. return;
  1599. }
  1600. */
  1601. if (isVisible) {
  1602. // target.style.setProperty('--wsr94', h + 'px');
  1603. target.setAttribute('wSr93', 'visible');
  1604. if (nNextElem(target) === null) {
  1605.  
  1606. // firstVisibleItemDetected = true;
  1607. /*
  1608. if (dateNow() - lastScroll < 80) {
  1609. lastLShow = 0;
  1610. lastScroll = 0;
  1611. Promise.resolve().then(clickShowMore);
  1612. } else {
  1613. lastLShow = dateNow();
  1614. }
  1615. */
  1616. // lastLShow = dateNow();
  1617. } else if (!hasFirstShowMore) { // should more than one item being visible
  1618. // implement inside visObserver to ensure there is sufficient delay
  1619. hasFirstShowMore = true;
  1620. requestAnimationFrame(() => {
  1621. // foreground page
  1622. // page visibly ready -> load the latest comments at initial loading
  1623. const lcRenderer = lcRendererElm();
  1624. if (lcRenderer) {
  1625. (lcRenderer.inst || lcRenderer).scrollToBottom_();
  1626. }
  1627. });
  1628. }
  1629. }
  1630. else if (target.getAttribute('wSr93') === 'visible') { // ignore target.getAttribute('wSr93') === '' to avoid wrong sizing
  1631.  
  1632. // target.style.setProperty('--wsr94', h + 'px');
  1633. target.setAttribute('wSr93', 'hidden');
  1634. } // note: might consider 0 < entry.intersectionRatio < 0.5 and target.getAttribute('wSr93') === '' <new last item>
  1635.  
  1636. }
  1637.  
  1638.  
  1639.  
  1640. const visObserver = new IntersectionObserver((entries) => {
  1641.  
  1642. for (const entry of entries) {
  1643.  
  1644. Promise.resolve(entry).then(visObserverFn);
  1645.  
  1646. }
  1647.  
  1648. }, {
  1649. // root: HTMLElement.prototype.closest.call(m2, '#item-scroller.yt-live-chat-item-list-renderer'), // nullable
  1650. rootMargin: "0px",
  1651. threshold: [0.05, 0.95],
  1652. });
  1653.  
  1654.  
  1655. return { lcRendererElm, visObserver }
  1656.  
  1657.  
  1658. })();
  1659.  
  1660. const { setupMutObserver } = (() => {
  1661.  
  1662. const mutFn = (items) => {
  1663. for (let node = nLastElem(items); node !== null; node = nPrevElem(node)) {
  1664. if (node.hasAttribute('wSr93')) break;
  1665. node.setAttribute('wSr93', '');
  1666. visObserver.observe(node);
  1667. }
  1668. }
  1669.  
  1670. const mutObserver = new MutationObserver((mutations) => {
  1671. const items = (mutations[0] || 0).target;
  1672. if (!items) return;
  1673. mutFn(items);
  1674. });
  1675.  
  1676. const setupMutObserver = (m2) => {
  1677. scrollChatFn = null;
  1678. mutObserver.disconnect();
  1679. mutObserver.takeRecords();
  1680. if (m2) {
  1681. if (typeof m2.__appendChild932__ === 'function') {
  1682. if (typeof m2.appendChild === 'function') m2.appendChild = m2.__appendChild932__;
  1683. if (typeof m2.__shady_native_appendChild === 'function') m2.__shady_native_appendChild = m2.__appendChild932__;
  1684. }
  1685. mutObserver.observe(m2, {
  1686. childList: true,
  1687. subtree: false
  1688. });
  1689. mutFn(m2);
  1690.  
  1691.  
  1692. if (ENABLE_OVERFLOW_ANCHOR) {
  1693.  
  1694. let items = m2;
  1695. let addedAnchor = false;
  1696. if (items) {
  1697. if (items.nextElementSibling === null) {
  1698. items.classList.add('no-anchor');
  1699. addedAnchor = true;
  1700. items.parentNode.appendChild(dr(document.createElement('item-anchor')));
  1701. }
  1702. }
  1703.  
  1704.  
  1705.  
  1706. if (addedAnchor) {
  1707. nodeParent(m2).classList.add('no-anchor'); // required
  1708. }
  1709.  
  1710. }
  1711.  
  1712. // let div = document.createElement('div');
  1713. // div.id = 'qwcc';
  1714. // HTMLElement.prototype.appendChild.call(document.querySelector('yt-live-chat-item-list-renderer'), div )
  1715. // bufferRegion =div;
  1716.  
  1717. // buffObserver.takeRecords();
  1718. // buffObserver.disconnect();
  1719. // buffObserver.observe(div, {
  1720. // childList: true,
  1721. // subtree: false
  1722. // })
  1723.  
  1724.  
  1725.  
  1726. }
  1727. }
  1728.  
  1729. return { setupMutObserver };
  1730.  
  1731.  
  1732.  
  1733. })();
  1734.  
  1735. const setupEvents = () => {
  1736.  
  1737.  
  1738. let scrollCount = 0;
  1739.  
  1740. const passiveCapture = typeof IntersectionObserver === 'function' ? { capture: true, passive: true } : true;
  1741.  
  1742.  
  1743. const delayFlushActiveItemsAfterUserActionK_ = () => {
  1744.  
  1745. const lcRenderer = lcRendererElm();
  1746. if (lcRenderer) {
  1747. const cnt = (lcRenderer.inst || lcRenderer);
  1748. if (!cnt.hasUserJustInteracted11_) return;
  1749. if (cnt.atBottom && cnt.allowScroll && cnt.activeItems_.length >= 1 && cnt.hasUserJustInteracted11_()) {
  1750. cnt.delayFlushActiveItemsAfterUserAction11_ && cnt.delayFlushActiveItemsAfterUserAction11_();
  1751. }
  1752. }
  1753.  
  1754. }
  1755.  
  1756. document.addEventListener('scroll', (evt) => {
  1757. if (!evt || !evt.isTrusted) return;
  1758. // lastScroll = dateNow();
  1759. if (++scrollCount > 1e9) scrollCount = 9;
  1760. }, passiveCapture); // support contain => support passive
  1761.  
  1762. let lastScrollCount = -1;
  1763. document.addEventListener('wheel', (evt) => {
  1764. if (!evt || !evt.isTrusted) return;
  1765. if (lastScrollCount === scrollCount) return;
  1766. lastScrollCount = scrollCount;
  1767. lastWheel = dateNow();
  1768. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1769. }, passiveCapture); // support contain => support passive
  1770.  
  1771. document.addEventListener('mousedown', (evt) => {
  1772. if (!evt || !evt.isTrusted) return;
  1773. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1774. lastMouseDown = dateNow();
  1775. currentMouseDown = true;
  1776. lastUserInteraction = lastMouseDown;
  1777. }, passiveCapture);
  1778.  
  1779. document.addEventListener('pointerdown', (evt) => {
  1780. if (!evt || !evt.isTrusted) return;
  1781. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1782. lastMouseDown = dateNow();
  1783. currentMouseDown = true;
  1784. lastUserInteraction = lastMouseDown;
  1785. }, passiveCapture);
  1786.  
  1787. document.addEventListener('click', (evt) => {
  1788. if (!evt || !evt.isTrusted) return;
  1789. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1790. lastMouseDown = lastMouseUp = dateNow();
  1791. currentMouseDown = false;
  1792. lastUserInteraction = lastMouseDown;
  1793. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1794. }, passiveCapture);
  1795.  
  1796. document.addEventListener('tap', (evt) => {
  1797. if (!evt || !evt.isTrusted) return;
  1798. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  1799. lastMouseDown = lastMouseUp = dateNow();
  1800. currentMouseDown = false;
  1801. lastUserInteraction = lastMouseDown;
  1802. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1803. }, passiveCapture);
  1804.  
  1805.  
  1806. document.addEventListener('mouseup', (evt) => {
  1807. if (!evt || !evt.isTrusted) return;
  1808. if (currentMouseDown) {
  1809. lastMouseUp = dateNow();
  1810. currentMouseDown = false;
  1811. lastUserInteraction = lastMouseUp;
  1812. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1813. }
  1814. }, passiveCapture);
  1815.  
  1816.  
  1817. document.addEventListener('pointerup', (evt) => {
  1818. if (!evt || !evt.isTrusted) return;
  1819. if (currentMouseDown) {
  1820. lastMouseUp = dateNow();
  1821. currentMouseDown = false;
  1822. lastUserInteraction = lastMouseUp;
  1823. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1824. }
  1825. }, passiveCapture);
  1826.  
  1827. document.addEventListener('touchstart', (evt) => {
  1828. if (!evt || !evt.isTrusted) return;
  1829. lastTouchDown = dateNow();
  1830. currentTouchDown = true;
  1831. lastUserInteraction = lastTouchDown;
  1832. }, passiveCapture);
  1833.  
  1834. document.addEventListener('touchmove', (evt) => {
  1835. if (!evt || !evt.isTrusted) return;
  1836. lastTouchDown = dateNow();
  1837. currentTouchDown = true;
  1838. lastUserInteraction = lastTouchDown;
  1839. }, passiveCapture);
  1840.  
  1841. document.addEventListener('touchend', (evt) => {
  1842. if (!evt || !evt.isTrusted) return;
  1843. if (currentTouchDown) {
  1844. lastTouchUp = dateNow();
  1845. currentTouchDown = false;
  1846. lastUserInteraction = lastTouchUp;
  1847. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1848. }
  1849. }, passiveCapture);
  1850.  
  1851. document.addEventListener('touchcancel', (evt) => {
  1852. if (!evt || !evt.isTrusted) return;
  1853. if (currentTouchDown) {
  1854. lastTouchUp = dateNow();
  1855. currentTouchDown = false;
  1856. lastUserInteraction = lastTouchUp;
  1857. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  1858. }
  1859. }, passiveCapture);
  1860.  
  1861.  
  1862. }
  1863.  
  1864.  
  1865. const onRegistryReadyForDOMOperations = () => {
  1866. let firstCheckedOnYtInit = false;
  1867.  
  1868. const mightFirstCheckOnYtInit = () => {
  1869. if (firstCheckedOnYtInit) return;
  1870. firstCheckedOnYtInit = true;
  1871.  
  1872. if (!document.body || !document.head) return;
  1873.  
  1874. if (!assertor(() => location.pathname.startsWith('/live_chat') && location.search.indexOf('continuation=') >= 0)) return;
  1875.  
  1876. addCssManaged();
  1877.  
  1878. let efsContainer = document.getElementById('elzm-fonts-yk75g');
  1879. if (efsContainer && efsContainer.parentNode !== document.body) {
  1880. document.body.appendChild(efsContainer);
  1881. }
  1882.  
  1883.  
  1884. }
  1885.  
  1886. if (!assertor(() => location.pathname.startsWith('/live_chat') && location.search.indexOf('continuation=') >= 0)) return;
  1887. // if (!assertor(() => document.getElementById('yt-masthead') === null)) return;
  1888.  
  1889. if (document.documentElement && document.head) {
  1890.  
  1891. addCssManaged();
  1892.  
  1893. }
  1894. // console.log(document.body===null)
  1895.  
  1896. customElements.whenDefined('yt-live-chat-item-list-renderer').then(() => {
  1897.  
  1898.  
  1899. const tag = "yt-live-chat-item-list-renderer"
  1900. const dummy = document.createElement(tag);
  1901.  
  1902.  
  1903. const cProto = getProto(dummy);
  1904. if (!cProto || !cProto.attached) {
  1905. console.warn(`proto.attached for ${tag} is unavailable.`);
  1906. return;
  1907. }
  1908.  
  1909.  
  1910. mightFirstCheckOnYtInit();
  1911. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-item-list-renderer hacks");
  1912. console.log("[Begin]");
  1913.  
  1914. const mclp = cProto;
  1915. try {
  1916. assertor(() => typeof mclp.scrollToBottom_ === 'function');
  1917. assertor(() => typeof mclp.flushActiveItems_ === 'function');
  1918. assertor(() => typeof mclp.canScrollToBottom_ === 'function');
  1919. assertor(() => typeof mclp.setAtBottom === 'function');
  1920. assertor(() => typeof mclp.scrollToBottom66_ === 'undefined');
  1921. assertor(() => typeof mclp.flushActiveItems66_ === 'undefined');
  1922. } catch (e) { }
  1923.  
  1924.  
  1925. try {
  1926. assertor(() => typeof mclp.attached === 'function');
  1927. assertor(() => typeof mclp.detached === 'function');
  1928. assertor(() => typeof mclp.canScrollToBottom_ === 'function');
  1929. assertor(() => typeof mclp.isSmoothScrollEnabled_ === 'function');
  1930. assertor(() => typeof mclp.maybeResizeScrollContainer_ === 'function');
  1931. assertor(() => typeof mclp.refreshOffsetContainerHeight_ === 'function');
  1932. assertor(() => typeof mclp.smoothScroll_ === 'function');
  1933. assertor(() => typeof mclp.resetSmoothScroll_ === 'function');
  1934. } catch (e) { }
  1935.  
  1936. mclp.__intermediate_delay__ = null;
  1937.  
  1938. let mzk = 0;
  1939. let myk = 0;
  1940. let mlf = 0;
  1941. let myw = 0;
  1942. let mzt = 0;
  1943. let zarr = null;
  1944. let mlg = 0;
  1945.  
  1946. if ((mclp.clearList || 0).length === 0) {
  1947. assertor(() => fnIntegrity(mclp.clearList, '0.106.50'));
  1948. mclp.clearList66 = mclp.clearList;
  1949. mclp.clearList = function () {
  1950. mzk++;
  1951. myk++;
  1952. mlf++;
  1953. myw++;
  1954. mzt++;
  1955. mlg++;
  1956. zarr = null;
  1957. this.__intermediate_delay__ = null;
  1958. this.clearList66();
  1959. };
  1960. console.log("clearList", "OK");
  1961. } else {
  1962. console.log("clearList", "NG");
  1963. }
  1964.  
  1965.  
  1966. let onListRendererAttachedDone = false;
  1967.  
  1968. function setList(itemOffset, items) {
  1969.  
  1970. const isFirstTime = onListRendererAttachedDone === false;
  1971.  
  1972. if (isFirstTime) {
  1973. onListRendererAttachedDone = true;
  1974. Promise.resolve().then(watchUserCSS);
  1975. addCssManaged();
  1976. setupEvents();
  1977. }
  1978.  
  1979. setupStyle(itemOffset, items);
  1980.  
  1981. setupMutObserver(items);
  1982. }
  1983.  
  1984. mclp.attached419 = async function () {
  1985.  
  1986. if (!this.isAttached) return;
  1987.  
  1988. let maxTrial = 16;
  1989. while (!this.$ || !this.$['item-scroller'] || !this.$['item-offset'] || !this.$['items']) {
  1990. if (--maxTrial < 0 || !this.isAttached) return;
  1991. await new Promise(requestAnimationFrame);
  1992. }
  1993.  
  1994. if (this.isAttached !== true) return;
  1995.  
  1996. if (!this.$) {
  1997. console.warn("!this.$");
  1998. return;
  1999. }
  2000. if (!this.$) return;
  2001. /** @type {HTMLElement | null} */
  2002. const itemScroller = this.$['item-scroller'];
  2003. /** @type {HTMLElement | null} */
  2004. const itemOffset = this.$['item-offset'];
  2005. /** @type {HTMLElement | null} */
  2006. const items = this.$['items'];
  2007.  
  2008. if (!itemScroller || !itemOffset || !items) {
  2009. console.warn("items.parentNode !== itemOffset");
  2010. return;
  2011. }
  2012.  
  2013. if (nodeParent(items) !== itemOffset) {
  2014.  
  2015. console.warn("items.parentNode !== itemOffset");
  2016. return;
  2017. }
  2018.  
  2019.  
  2020. if (items.id !== 'items' || itemOffset.id !== "item-offset") {
  2021.  
  2022. console.warn("id incorrect");
  2023. return;
  2024. }
  2025.  
  2026. 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')
  2027.  
  2028. if (!isTargetItems) {
  2029. console.warn("!isTargetItems");
  2030. return;
  2031. }
  2032.  
  2033. setList(itemOffset, items);
  2034.  
  2035. }
  2036.  
  2037. mclp.attached331 = mclp.attached;
  2038. mclp.attached = function () {
  2039. this.attached419 && this.attached419();
  2040. return this.attached331();
  2041. }
  2042.  
  2043. mclp.detached331 = mclp.detached;
  2044.  
  2045. mclp.detached = function () {
  2046. setupMutObserver();
  2047. return this.detached331();
  2048. }
  2049.  
  2050. const t29s = document.querySelectorAll("yt-live-chat-item-list-renderer");
  2051. for (const t29 of t29s) {
  2052. if ((t29.inst || t29).isAttached === true) {
  2053. t29.attached419();
  2054. }
  2055. }
  2056.  
  2057. if ((mclp.async || 0).length === 2 && (mclp.cancelAsync || 0).length === 1) {
  2058.  
  2059. assertor(() => fnIntegrity(mclp.async, '2.24.15'));
  2060. assertor(() => fnIntegrity(mclp.cancelAsync, '1.15.8'));
  2061.  
  2062. /** @type {Map<number, any>} */
  2063. const aMap = new Map();
  2064. let count = 6150;
  2065. mclp.async66 = mclp.async;
  2066. mclp.async = function (e, f) {
  2067. // ensure the previous operation is done
  2068. // .async is usually after the time consuming functions like flushActiveItems_ and scrollToBottom_
  2069. const hasF = arguments.length === 2;
  2070. const stack = new Error().stack;
  2071. const isFlushAsync = stack.indexOf('flushActiveItems_') >= 0;
  2072. if (count > 1e9) count = 6159;
  2073. const resId = ++count;
  2074. aMap.set(resId, e);
  2075. (this.__intermediate_delay__ || Promise.resolve()).then(rk => {
  2076. const rp = aMap.get(resId);
  2077. if (typeof rp !== 'function') {
  2078. return;
  2079. }
  2080. let cancelCall = false;
  2081. if (isFlushAsync) {
  2082. if (rk < 0) {
  2083. cancelCall = true;
  2084. } else if (rk === 2 && arguments[0] === this.maybeScrollToBottom_) {
  2085. cancelCall = true;
  2086. }
  2087. }
  2088. if (cancelCall) {
  2089. aMap.delete(resId);
  2090. } else {
  2091. const asyncEn = function () {
  2092. aMap.delete(resId);
  2093. return rp.apply(this, arguments);
  2094. };
  2095. aMap.set(resId, hasF ? this.async66(asyncEn, f) : this.async66(asyncEn));
  2096. }
  2097. });
  2098.  
  2099. return resId;
  2100. }
  2101.  
  2102. mclp.cancelAsync66 = mclp.cancelAsync;
  2103. mclp.cancelAsync = function (resId) {
  2104. if (resId <= 6150) {
  2105. this.cancelAsync66(resId);
  2106. } else if (aMap.has(resId)) {
  2107. const rp = aMap.get(resId);
  2108. aMap.delete(resId);
  2109. if (typeof rp !== 'function') {
  2110. this.cancelAsync66(rp);
  2111. }
  2112. }
  2113. }
  2114.  
  2115. console.log("async", "OK");
  2116. } else {
  2117. console.log("async", "NG");
  2118. }
  2119.  
  2120.  
  2121. if ((mclp.showNewItems_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM) {
  2122.  
  2123. assertor(() => fnIntegrity(mclp.showNewItems_, '0.170.79'));
  2124. mclp.showNewItems66_ = mclp.showNewItems_;
  2125.  
  2126. mclp.showNewItems77_ = async function () {
  2127. if (myk > 1e9) myk = 9;
  2128. let tid = ++myk;
  2129.  
  2130. await new Promise(requestAnimationFrame);
  2131.  
  2132. if (tid !== myk) {
  2133. return;
  2134. }
  2135.  
  2136. const cnt = this;
  2137.  
  2138. await Promise.resolve();
  2139. cnt.showNewItems66_();
  2140.  
  2141. await Promise.resolve();
  2142.  
  2143. }
  2144.  
  2145. mclp.showNewItems_ = function () {
  2146.  
  2147. const cnt = this;
  2148. cnt.__intermediate_delay__ = new Promise(resolve => {
  2149. cnt.showNewItems77_().then(() => {
  2150. resolve();
  2151. });
  2152. });
  2153. }
  2154.  
  2155. console.log("showNewItems_", "OK");
  2156. } else {
  2157. console.log("showNewItems_", "NG");
  2158. }
  2159.  
  2160.  
  2161. if ((mclp.flushActiveItems_ || 0).length === 0) {
  2162.  
  2163. assertor(() => fnIntegrity(mclp.flushActiveItems_, '0.137.81'));
  2164.  
  2165. let contensWillChangeController = null;
  2166.  
  2167. mclp.flushActiveItems66_ = mclp.flushActiveItems_;
  2168.  
  2169. mclp.flushActiveItems78_ = async function (tid) {
  2170. try {
  2171. const lockedMaxItemsToDisplay = this.data.maxItemsToDisplay944;
  2172. let logger = false;
  2173. const cnt = this;
  2174. let immd = cnt.__intermediate_delay__;
  2175. await new Promise(requestAnimationFrame);
  2176.  
  2177. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2178. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  2179.  
  2180. mlf++;
  2181. if (mlg > 1e9) mlg = 9;
  2182. ++mlg;
  2183.  
  2184. const tmpMaxItemsCount = this.data.maxItemsToDisplay;
  2185. const reducedMaxItemsToDisplay = MAX_ITEMS_FOR_FULL_FLUSH;
  2186. let changeMaxItemsToDisplay = false;
  2187. const activeItemsLen = this.activeItems_.length;
  2188. if (activeItemsLen > tmpMaxItemsCount && tmpMaxItemsCount > 0) {
  2189. logger = true;
  2190.  
  2191. groupCollapsed("YouTube Super Fast Chat", " | flushActiveItems78_");
  2192.  
  2193. logger && console.log('[Begin]')
  2194.  
  2195. console.log('this.activeItems_.length > N', activeItemsLen, tmpMaxItemsCount);
  2196. if (ENABLE_REDUCED_MAXITEMS_FOR_FLUSH && lockedMaxItemsToDisplay === tmpMaxItemsCount && lockedMaxItemsToDisplay !== reducedMaxItemsToDisplay) {
  2197. console.log('reduce maxitems');
  2198. if (tmpMaxItemsCount > reducedMaxItemsToDisplay) {
  2199. // as all the rendered chats are already "outdated"
  2200. // all old chats shall remove and reduced number of few chats will be rendered
  2201. // then restore to the original number
  2202. changeMaxItemsToDisplay = true;
  2203. this.data.maxItemsToDisplay = reducedMaxItemsToDisplay;
  2204. console.log(`'maxItemsToDisplay' is reduced from ${tmpMaxItemsCount} to ${reducedMaxItemsToDisplay}.`)
  2205. }
  2206. this.activeItems_.splice(0, activeItemsLen - this.data.maxItemsToDisplay);
  2207. // console.log('changeMaxItemsToDisplay 01', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  2208.  
  2209. console.log('new this.activeItems_.length > N', this.activeItems_.length);
  2210. } else {
  2211. this.activeItems_.splice(0, activeItemsLen - (tmpMaxItemsCount < 900 ? tmpMaxItemsCount : 900));
  2212.  
  2213. console.log('new this.activeItems_.length > N', this.activeItems_.length);
  2214. }
  2215. }
  2216. // it is found that it will render all stacked chats after switching back from background
  2217. // to avoid lagging in popular livestream with massive chats, trim first before rendering.
  2218. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  2219.  
  2220.  
  2221. const items = (cnt.$ || 0).items;
  2222.  
  2223. if (USE_WILL_CHANGE_CONTROLLER) {
  2224. if (contensWillChangeController && contensWillChangeController.element !== items) {
  2225. contensWillChangeController.release();
  2226. contensWillChangeController = null;
  2227. }
  2228. if (!contensWillChangeController) contensWillChangeController = new WillChangeController(items, 'contents');
  2229. }
  2230. const wcController = contensWillChangeController;
  2231. cnt.__intermediate_delay__ = Promise.all([cnt.__intermediate_delay__ || null, immd || null]);
  2232. wcController && wcController.beforeOper();
  2233. await Promise.resolve();
  2234. const len1 = cnt.activeItems_.length;
  2235. cnt.flushActiveItems66_();
  2236. const len2 = cnt.activeItems_.length;
  2237. const bAsync = len1 !== len2;
  2238. await Promise.resolve();
  2239. if (wcController) {
  2240. if (bAsync) {
  2241. cnt.async(() => {
  2242. wcController.afterOper();
  2243. });
  2244. } else {
  2245. wcController.afterOper();
  2246. }
  2247. }
  2248. if (changeMaxItemsToDisplay && this.data.maxItemsToDisplay === reducedMaxItemsToDisplay && tmpMaxItemsCount > reducedMaxItemsToDisplay) {
  2249. this.data.maxItemsToDisplay = tmpMaxItemsCount;
  2250.  
  2251. logger && console.log(`'maxItemsToDisplay' is restored from ${reducedMaxItemsToDisplay} to ${tmpMaxItemsCount}.`);
  2252. // console.log('changeMaxItemsToDisplay 02', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  2253. } else if (changeMaxItemsToDisplay) {
  2254.  
  2255. logger && console.log(`'maxItemsToDisplay' cannot be restored`, {
  2256. maxItemsToDisplay: this.data.maxItemsToDisplay,
  2257. reducedMaxItemsToDisplay,
  2258. originalMaxItemsToDisplay: tmpMaxItemsCount
  2259. });
  2260. }
  2261. logger && console.log('[End]')
  2262.  
  2263. logger && console.groupEnd();
  2264.  
  2265. if (!ENABLE_NO_SMOOTH_TRANSFORM) {
  2266.  
  2267.  
  2268. const ff = () => {
  2269.  
  2270. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2271. // if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2272. if (!cnt.atBottom && cnt.allowScroll && cnt.hasUserJustInteracted11_ && !cnt.hasUserJustInteracted11_()) {
  2273. cnt.scrollToBottom_();
  2274.  
  2275. Promise.resolve().then(() => {
  2276.  
  2277. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2278. if (!cnt.canScrollToBottom_()) cnt.scrollToBottom_();
  2279. });
  2280.  
  2281.  
  2282. }
  2283. }
  2284.  
  2285. ff();
  2286.  
  2287.  
  2288. Promise.resolve().then(ff);
  2289.  
  2290. // requestAnimationFrame(ff);
  2291. } else if (true) { // it might not be sticky to bottom when there is a full refresh.
  2292.  
  2293. const knt = cnt;
  2294. if (!scrollChatFn) {
  2295. const cnt = knt;
  2296. const f = () => {
  2297. const itemScroller = cnt.itemScroller;
  2298. if (!itemScroller || itemScroller.isConnected === false || cnt.isAttached === false) return;
  2299. if (!cnt.atBottom) {
  2300. cnt.scrollToBottom_();
  2301. } else if (itemScroller.scrollTop === 0) { // not yet interacted by user; cannot stick to bottom
  2302. itemScroller.scrollTop = itemScroller.scrollHeight;
  2303. }
  2304. };
  2305. scrollChatFn = () => Promise.resolve().then(f).then(f);
  2306. }
  2307.  
  2308. if (!ENABLE_FULL_RENDER_REQUIRED) scrollChatFn();
  2309. }
  2310.  
  2311. return 1;
  2312.  
  2313.  
  2314. } catch (e) {
  2315. console.warn(e);
  2316. }
  2317. }
  2318.  
  2319. mclp.flushActiveItems77_ = async function () {
  2320. try {
  2321.  
  2322. const cnt = this;
  2323. if (mlf > 1e9) mlf = 9;
  2324. let tid = ++mlf;
  2325. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2326. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  2327.  
  2328. // 4 times to maxItems to avoid frequent trimming.
  2329. // 1 ... 10 ... 20 ... 30 ... 40 ... 50 ... 60 => 16 ... 20 ... 30 ..... 60 ... => 16
  2330.  
  2331. const lockedMaxItemsToDisplay = this.data.maxItemsToDisplay944;
  2332. this.activeItems_.length > lockedMaxItemsToDisplay * 4 && lockedMaxItemsToDisplay > 4 && this.activeItems_.splice(0, this.activeItems_.length - lockedMaxItemsToDisplay - 1);
  2333. if (cnt.canScrollToBottom_()) {
  2334. if (cnt.mutexPromiseFA77) await cnt.mutexPromiseFA77;
  2335. if (tid !== mlf) return;
  2336. let qResolve = null;
  2337. cnt.mutexPromiseFA77 = new Promise(resolve => { qResolve = resolve; })
  2338. let res;
  2339. try {
  2340. res = await cnt.flushActiveItems78_(tid);
  2341. } catch (e) {
  2342. console.warn(e);
  2343. }
  2344. qResolve && qResolve();
  2345. return res;
  2346. } else {
  2347. // cnt.flushActiveItems66_();
  2348. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  2349. return 2;
  2350. }
  2351. } catch (e) {
  2352. console.warn(e);
  2353. }
  2354. }
  2355.  
  2356. mclp.flushActiveItems_ = function () {
  2357. const cnt = this;
  2358.  
  2359. if (arguments.length !== 0 || !cnt.activeItems_ || !cnt.canScrollToBottom_) return cnt.flushActiveItems66_.apply(this, arguments);
  2360.  
  2361. if (cnt.activeItems_.length === 0) {
  2362. cnt.__intermediate_delay__ = null;
  2363. return;
  2364. }
  2365.  
  2366. const cntData = ((cnt || 0).data || 0);
  2367. if (cntData.maxItemsToDisplay944 === undefined) {
  2368. cntData.maxItemsToDisplay944 = null;
  2369. if (cntData.maxItemsToDisplay > MAX_ITEMS_FOR_TOTAL_DISPLAY) cntData.maxItemsToDisplay = MAX_ITEMS_FOR_TOTAL_DISPLAY;
  2370. cntData.maxItemsToDisplay944 = cntData.maxItemsToDisplay || null;
  2371. }
  2372.  
  2373. // ignore previous __intermediate_delay__ and create a new one
  2374. cnt.__intermediate_delay__ = new Promise(resolve => {
  2375. cnt.flushActiveItems77_().then(rt => {
  2376. if (rt === 1) resolve(1); // success, scroll to bottom
  2377. else if (rt === 2) resolve(2); // success, trim
  2378. else resolve(-1); // skip
  2379. });
  2380. });
  2381.  
  2382. }
  2383. console.log("flushActiveItems_", "OK");
  2384. } else {
  2385. console.log("flushActiveItems_", "NG");
  2386. }
  2387.  
  2388. mclp.delayFlushActiveItemsAfterUserAction11_ = async function () {
  2389. try {
  2390. if (mlg > 1e9) mlg = 9;
  2391. const tid = ++mlg;
  2392. const keepTrialCond = () => this.atBottom && this.allowScroll && (tid === mlg) && this.isAttached === true && this.activeItems_.length >= 1 && (this.hostElement || 0).isConnected === true;
  2393. const runCond = () => this.canScrollToBottom_();
  2394. if (!keepTrialCond()) return;
  2395. if (runCond()) return this.flushActiveItems_() | 1; // avoid return promise
  2396. await new Promise(r => setTimeout(r, 80));
  2397. if (!keepTrialCond()) return;
  2398. if (runCond()) return this.flushActiveItems_() | 1;
  2399. await new Promise(requestAnimationFrame);
  2400. if (runCond()) return this.flushActiveItems_() | 1;
  2401. } catch (e) {
  2402. console.warn(e);
  2403. }
  2404. }
  2405.  
  2406. if ((mclp.atBottomChanged_ || 0).length === 1) {
  2407. // note: if the scrolling is too frequent, the show more visibility might get wrong.
  2408. assertor(() => fnIntegrity(mclp.atBottomChanged_, '1.75.39'));
  2409.  
  2410. const querySelector = HTMLElement.prototype.querySelector;
  2411. const U = (element) => ({
  2412. querySelector: (selector) => querySelector.call(element, selector)
  2413. });
  2414.  
  2415. let qid = 0;
  2416. mclp.atBottomChanged_ = function (a) {
  2417. let tid = ++qid;
  2418. var b = this;
  2419. a ? this.hideShowMoreAsync_ || (this.hideShowMoreAsync_ = this.async(function () {
  2420. if (tid !== qid) return;
  2421. U(b.hostElement).querySelector("#show-more").style.visibility = "hidden"
  2422. }, 200)) : (this.hideShowMoreAsync_ && this.cancelAsync(this.hideShowMoreAsync_),
  2423. this.hideShowMoreAsync_ = null,
  2424. U(this.hostElement).querySelector("#show-more").style.visibility = "visible")
  2425. }
  2426.  
  2427. console.log("atBottomChanged_", "OK");
  2428. } else {
  2429. console.log("atBottomChanged_", "NG");
  2430. }
  2431.  
  2432. if ((mclp.onScrollItems_ || 0).length === 1) {
  2433.  
  2434. assertor(() => fnIntegrity(mclp.onScrollItems_, '1.17.9'));
  2435. mclp.onScrollItems66_ = mclp.onScrollItems_;
  2436. mclp.onScrollItems77_ = async function (evt) {
  2437. if (myw > 1e9) myw = 9;
  2438. let tid = ++myw;
  2439.  
  2440. await new Promise(requestAnimationFrame);
  2441.  
  2442. if (tid !== myw) {
  2443. return;
  2444. }
  2445.  
  2446. const cnt = this;
  2447.  
  2448. await Promise.resolve();
  2449. if (USE_OPTIMIZED_ON_SCROLL_ITEMS) {
  2450. await Promise.resolve().then(() => {
  2451. this.ytRendererBehavior.onScroll(evt);
  2452. }).then(() => {
  2453. if (this.canScrollToBottom_()) {
  2454. const hasUserJustInteracted = this.hasUserJustInteracted11_ ? this.hasUserJustInteracted11_() : true;
  2455. if (hasUserJustInteracted) {
  2456. // only when there is an user action
  2457. this.setAtBottom();
  2458. return 1;
  2459. }
  2460. } else {
  2461. // no message inserting
  2462. this.setAtBottom();
  2463. return 1;
  2464. }
  2465. }).then((r) => {
  2466.  
  2467. if (this.activeItems_.length) {
  2468.  
  2469. if (this.canScrollToBottom_()) {
  2470. this.flushActiveItems_();
  2471. return 1 && r;
  2472. } else if (this.atBottom && this.allowScroll && (this.hasUserJustInteracted11_ && this.hasUserJustInteracted11_())) {
  2473. // delayed due to user action
  2474. this.delayFlushActiveItemsAfterUserAction11_ && this.delayFlushActiveItemsAfterUserAction11_();
  2475. return 0;
  2476. }
  2477. }
  2478. }).then((r) => {
  2479. if (r) {
  2480. // ensure setAtBottom is correctly set
  2481. this.setAtBottom();
  2482. }
  2483. });
  2484. } else {
  2485. cnt.onScrollItems66_(evt);
  2486. }
  2487.  
  2488. await Promise.resolve();
  2489.  
  2490. }
  2491.  
  2492. mclp.onScrollItems_ = function (evt) {
  2493.  
  2494. const cnt = this;
  2495. cnt.__intermediate_delay__ = new Promise(resolve => {
  2496. cnt.onScrollItems77_(evt).then(() => {
  2497. resolve();
  2498. });
  2499. });
  2500. }
  2501. console.log("onScrollItems_", "OK");
  2502. } else {
  2503. console.log("onScrollItems_", "NG");
  2504. }
  2505.  
  2506. if ((mclp.handleLiveChatActions_ || 0).length === 1) {
  2507.  
  2508. assertor(() => fnIntegrity(mclp.handleLiveChatActions_, '1.31.17'));
  2509. mclp.handleLiveChatActions66_ = mclp.handleLiveChatActions_;
  2510.  
  2511. mclp.handleLiveChatActions77_ = async function (arr) {
  2512. if (typeof (arr || 0).length !== 'number') {
  2513. this.handleLiveChatActions66_(arr);
  2514. return;
  2515. }
  2516. if (mzt > 1e9) mzt = 9;
  2517. let tid = ++mzt;
  2518.  
  2519. if (zarr === null) zarr = arr;
  2520. else Array.prototype.push.apply(zarr, arr);
  2521. arr = null;
  2522.  
  2523. await new Promise(requestAnimationFrame);
  2524.  
  2525. if (tid !== mzt || zarr === null) {
  2526. return;
  2527. }
  2528.  
  2529. const carr = zarr;
  2530. zarr = null;
  2531.  
  2532. await Promise.resolve();
  2533. this.handleLiveChatActions66_(carr);
  2534. await Promise.resolve();
  2535.  
  2536. }
  2537.  
  2538. mclp.handleLiveChatActions_ = function (arr) {
  2539.  
  2540. const cnt = this;
  2541. cnt.__intermediate_delay__ = new Promise(resolve => {
  2542. cnt.handleLiveChatActions77_(arr).then(() => {
  2543. resolve();
  2544. });
  2545. });
  2546. }
  2547. console.log("handleLiveChatActions_", "OK");
  2548. } else {
  2549. console.log("handleLiveChatActions_", "NG");
  2550. }
  2551.  
  2552. mclp.hasUserJustInteracted11_ = () => {
  2553. const t = dateNow();
  2554. return (t - lastWheel < 80) || currentMouseDown || currentTouchDown || (t - lastUserInteraction < 80);
  2555. }
  2556.  
  2557. if ((mclp.canScrollToBottom_ || 0).length === 0) {
  2558.  
  2559. assertor(() => fnIntegrity(mclp.canScrollToBottom_, '0.9.5'));
  2560.  
  2561. mclp.canScrollToBottom_ = function () {
  2562. return this.atBottom && this.allowScroll && !this.hasUserJustInteracted11_();
  2563. }
  2564.  
  2565. console.log("canScrollToBottom_", "OK");
  2566. } else {
  2567. console.log("canScrollToBottom_", "NG");
  2568. }
  2569.  
  2570. if (ENABLE_NO_SMOOTH_TRANSFORM) {
  2571.  
  2572. mclp.isSmoothScrollEnabled_ = function () {
  2573. return false;
  2574. }
  2575.  
  2576. mclp.maybeResizeScrollContainer_ = function () {
  2577. //
  2578. }
  2579.  
  2580. mclp.refreshOffsetContainerHeight_ = function () {
  2581. //
  2582. }
  2583.  
  2584. mclp.smoothScroll_ = function () {
  2585. //
  2586. }
  2587.  
  2588. mclp.resetSmoothScroll_ = function () {
  2589. //
  2590. }
  2591. console.log("ENABLE_NO_SMOOTH_TRANSFORM", "OK");
  2592. } else {
  2593. console.log("ENABLE_NO_SMOOTH_TRANSFORM", "NG");
  2594. }
  2595.  
  2596. console.log("[End]");
  2597. console.groupEnd();
  2598.  
  2599. });
  2600.  
  2601.  
  2602. const tickerContainerSetAttribute = function (attrName, attrValue) { // ensure '14.30000001%'.toFixed(1)
  2603.  
  2604. let yd = (this.__dataHost || (this.inst || 0).__dataHost || 0).__data;
  2605.  
  2606. if (arguments.length === 2 && attrName === 'style' && yd && attrValue) {
  2607.  
  2608. // let v = yd.containerStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
  2609. let v = `${attrValue}`;
  2610. // conside a ticker is 101px width
  2611. // 1% = 1.01px
  2612. // 0.2% = 0.202px
  2613.  
  2614.  
  2615. const ratio1 = (yd.ratio * 100);
  2616. if (ratio1 > -1) { // avoid NaN
  2617.  
  2618. // countdownDurationMs
  2619. // 600000 - 0.2% <1% = 6s> <0.2% = 1.2s>
  2620. // 300000 - 0.5% <1% = 3s> <0.5% = 1.5s>
  2621. // 150000 - 1% <1% = 1.5s>
  2622. // 75000 - 2% <1% =0.75s > <2% = 1.5s>
  2623. // 30000 - 5% <1% =0.3s > <5% = 1.5s>
  2624.  
  2625. // 99px * 5% = 4.95px
  2626.  
  2627. // 15000 - 10% <1% =0.15s > <10% = 1.5s>
  2628.  
  2629.  
  2630. // 1% Duration
  2631.  
  2632. let ratio2 = ratio1;
  2633.  
  2634. const ydd = yd.data;
  2635. const d1 = ydd.durationSec;
  2636. const d2 = ydd.fullDurationSec;
  2637.  
  2638. if (d1 === d2 && d1 > 1) {
  2639.  
  2640. if (d1 > 400) ratio2 = Math.round(ratio2 * 5) / 5; // 0.2%
  2641. else if (d1 > 200) ratio2 = Math.round(ratio2 * 2) / 2; // 0.5%
  2642. else if (d1 > 100) ratio2 = Math.round(ratio2 * 1) / 1; // 1%
  2643. else if (d1 > 50) ratio2 = Math.round(ratio2 * 0.5) / 0.5; // 2%
  2644. else if (d1 > 25) ratio2 = Math.round(ratio2 * 0.2) / 0.2; // 5% (max => 99px * 5% = 4.95px)
  2645. else ratio2 = Math.round(ratio2 * 0.2) / 0.2;
  2646.  
  2647. } else {
  2648. ratio2 = Math.round(ratio2 * 5) / 5; // 0.2% (min)
  2649. }
  2650.  
  2651. // ratio2 = Math.round(ratio2 * 5) / 5;
  2652. ratio2 = ratio2.toFixed(1);
  2653. v = v.replace(`${ratio1}%`, `${ratio2}%`).replace(`${ratio1}%`, `${ratio2}%`);
  2654.  
  2655. if (yd.__style_last__ === v) return;
  2656. yd.__style_last__ = v;
  2657. // do not consider any delay here.
  2658. // it shall be inside the looping for all properties changes. all the css background ops are in the same microtask.
  2659.  
  2660. }
  2661.  
  2662. HTMLElement.prototype.setAttribute.call(dr(this), attrName, v);
  2663.  
  2664.  
  2665. } else {
  2666. HTMLElement.prototype.setAttribute.apply(dr(this), arguments);
  2667. }
  2668.  
  2669. };
  2670.  
  2671.  
  2672. const fpTicker = (renderer) => {
  2673. const cnt = renderer.inst || renderer;
  2674. assertor(() => typeof (cnt || 0).is === 'string');
  2675. assertor(() => ((cnt || 0).hostElement || 0).nodeType === 1);
  2676. const container = (cnt.$ || 0).container;
  2677. if (container) {
  2678. assertor(() => (container || 0).nodeType === 1);
  2679. assertor(() => typeof container.setAttribute === 'function');
  2680. container.setAttribute = tickerContainerSetAttribute;
  2681. } else {
  2682. console.warn(`"container" does not exist in ${cnt.is}`);
  2683. }
  2684. };
  2685.  
  2686. const tags = ["yt-live-chat-ticker-paid-message-item-renderer", "yt-live-chat-ticker-paid-sticker-item-renderer",
  2687. "yt-live-chat-ticker-renderer", "yt-live-chat-ticker-sponsor-item-renderer"];
  2688.  
  2689.  
  2690. Promise.all(tags.map(tag => customElements.whenDefined(tag))).then(() => {
  2691.  
  2692. mightFirstCheckOnYtInit();
  2693. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-ticker-... hacks");
  2694. console.log("[Begin]");
  2695.  
  2696. for (const tag of tags) {
  2697. const dummy = document.createElement(tag);
  2698.  
  2699. const cProto = getProto(dummy);
  2700. if (!cProto || !cProto.attached) {
  2701. console.warn(`proto.attached for ${tag} is unavailable.`);
  2702. continue;
  2703. }
  2704.  
  2705. cProto.attached77 = cProto.attached;
  2706.  
  2707. cProto.attached = function () {
  2708. fpTicker(this.hostElement || this);
  2709. return this.attached77();
  2710. }
  2711.  
  2712. for (const elm of document.getElementsByTagName(tag)) {
  2713. if ((elm || elm.inst).isAttached === true) {
  2714. fpTicker(elm);
  2715. }
  2716. }
  2717.  
  2718. if (ENABLE_RAF_HACK_TICKERS && rafHub !== null) {
  2719.  
  2720. // cancelable - this.rafId < isAnimationPausedChanged >
  2721.  
  2722. let doHack = false;
  2723.  
  2724. if (typeof cProto.startCountdown === 'function' && typeof cProto.updateTimeout === 'function' && typeof cProto.isAnimationPausedChanged === 'function') {
  2725.  
  2726. console.log('startCountdown', typeof cProto.startCountdown)
  2727. console.log('updateTimeout', typeof cProto.updateTimeout)
  2728. console.log('isAnimationPausedChanged', typeof cProto.isAnimationPausedChanged)
  2729.  
  2730. doHack = fnIntegrity(cProto.startCountdown, '2.66.37') && fnIntegrity(cProto.updateTimeout, '1.76.45') && fnIntegrity(cProto.isAnimationPausedChanged, '2.56.30')
  2731.  
  2732. }
  2733. if (doHack) {
  2734.  
  2735. cProto.startCountdown = function (a, b) {
  2736. // console.log('cProto.startCountdown', tag) // yt-live-chat-ticker-sponsor-item-renderer
  2737. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  2738. b = void 0 === b ? 0 : b;
  2739. void 0 !== a && (this.countdownMs = 1E3 * a,
  2740. this.countdownDurationMs = b ? 1E3 * b : this.countdownMs,
  2741. this.ratio = 1,
  2742. this.lastCountdownTimeMs || this.isAnimationPaused || (this.lastCountdownTimeMs = performance.now(),
  2743. this.rafId = rafHub.request(this.boundUpdateTimeout37_)))
  2744. };
  2745.  
  2746. cProto.updateTimeout = function (a) {
  2747. // console.log('cProto.updateTimeout', tag) // yt-live-chat-ticker-sponsor-item-renderer
  2748. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  2749. this.countdownMs = Math.max(0, this.countdownMs - (a - (this.lastCountdownTimeMs || 0)));
  2750. this.ratio = this.countdownMs / this.countdownDurationMs;
  2751. this.isAttached && this.countdownMs ? (this.lastCountdownTimeMs = a,
  2752. this.rafId = rafHub.request(this.boundUpdateTimeout37_)) : (this.lastCountdownTimeMs = null,
  2753. this.isAttached && ("auto" === this.hostElement.style.width && this.setContainerWidth(),
  2754. this.slideDown()))
  2755. };
  2756.  
  2757. cProto.isAnimationPausedChanged = function (a, b) {
  2758. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  2759. a ? rafHub.cancel(this.rafId) : !a && b && (a = this.lastCountdownTimeMs || 0,
  2760. this.detlaSincePausedSecs && (a = (this.lastCountdownTimeMs || 0) + 1E3 * this.detlaSincePausedSecs,
  2761. this.detlaSincePausedSecs = 0),
  2762. this.boundUpdateTimeout37_(a),
  2763. this.lastCountdownTimeMs = window.performance.now())
  2764. };
  2765.  
  2766. console.log('RAF_HACK_TICKERS', tag, "OK")
  2767. } else if (tag === 'yt-live-chat-ticker-renderer') {
  2768.  
  2769. // no timer function to be set on yt-live-chat-ticker-renderer
  2770.  
  2771. } else {
  2772.  
  2773. console.log('RAF_HACK_TICKERS', tag, "NG")
  2774. }
  2775.  
  2776. }
  2777.  
  2778. }
  2779. console.log("[End]");
  2780. console.groupEnd();
  2781.  
  2782.  
  2783. })
  2784.  
  2785.  
  2786. if (ENABLE_RAF_HACK_INPUT_RENDERER && rafHub !== null) {
  2787.  
  2788.  
  2789. customElements.whenDefined("yt-live-chat-message-input-renderer").then(() => {
  2790.  
  2791. mightFirstCheckOnYtInit();
  2792. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-message-input-renderer hacks");
  2793. console.log("[Begin]");
  2794. (() => {
  2795.  
  2796. const tag = "yt-live-chat-message-input-renderer"
  2797. const dummy = document.createElement(tag);
  2798.  
  2799. const cProto = getProto(dummy);
  2800. if (!cProto || !cProto.attached) {
  2801. console.warn(`proto.attached for ${tag} is unavailable.`);
  2802. return;
  2803. }
  2804.  
  2805. let doHack = false;
  2806. if (typeof cProto.handleTimeout === 'function' && typeof cProto.updateTimeout === 'function') {
  2807.  
  2808. // not cancellable
  2809. console.log('handleTimeout', typeof cProto.handleTimeout)
  2810. console.log('updateTimeout', typeof cProto.updateTimeout)
  2811.  
  2812. doHack = fnIntegrity(cProto.handleTimeout, '1.27.16') && fnIntegrity(cProto.updateTimeout, '1.50.33');
  2813.  
  2814. }
  2815.  
  2816. if (doHack) {
  2817.  
  2818. cProto.handleTimeout = function (a) {
  2819. console.log('cProto.handleTimeout', tag)
  2820. if (!this.boundUpdateTimeout38_) this.boundUpdateTimeout38_ = this.updateTimeout.bind(this);
  2821. this.timeoutDurationMs = this.timeoutMs = a;
  2822. this.countdownRatio = 1;
  2823. 0 === this.lastTimeoutTimeMs && rafHub.request(this.boundUpdateTimeout38_)
  2824. };
  2825. cProto.updateTimeout = function (a) {
  2826. console.log('cProto.updateTimeout', tag)
  2827. if (!this.boundUpdateTimeout38_) this.boundUpdateTimeout38_ = this.updateTimeout.bind(this);
  2828. this.lastTimeoutTimeMs && (this.timeoutMs = Math.max(0, this.timeoutMs - (a - this.lastTimeoutTimeMs)),
  2829. this.countdownRatio = this.timeoutMs / this.timeoutDurationMs);
  2830. this.isAttached && this.timeoutMs ? (this.lastTimeoutTimeMs = a,
  2831. rafHub.request(this.boundUpdateTimeout38_)) : this.lastTimeoutTimeMs = 0
  2832. };
  2833.  
  2834. console.log('RAF_HACK_INPUT_RENDERER', tag, "OK")
  2835. } else {
  2836.  
  2837. console.log('RAF_HACK_INPUT_RENDERER', tag, "NG")
  2838. }
  2839.  
  2840. })();
  2841.  
  2842. console.log("[End]");
  2843.  
  2844. console.groupEnd();
  2845.  
  2846.  
  2847. })
  2848.  
  2849.  
  2850. }
  2851.  
  2852. if (ENABLE_RAF_HACK_EMOJI_PICKER && rafHub !== null) {
  2853.  
  2854.  
  2855. customElements.whenDefined("yt-emoji-picker-renderer").then(() => {
  2856.  
  2857. mightFirstCheckOnYtInit();
  2858. groupCollapsed("YouTube Super Fast Chat", " | yt-emoji-picker-renderer hacks");
  2859. console.log("[Begin]");
  2860. (() => {
  2861.  
  2862. const tag = "yt-emoji-picker-renderer"
  2863. const dummy = document.createElement(tag);
  2864.  
  2865. const cProto = getProto(dummy);
  2866. if (!cProto || !cProto.attached) {
  2867. console.warn(`proto.attached for ${tag} is unavailable.`);
  2868. return;
  2869. }
  2870.  
  2871. let doHack = false;
  2872. if (typeof cProto.animateScroll_ === 'function') {
  2873.  
  2874. // not cancellable
  2875. console.log('animateScroll_', typeof cProto.animateScroll_)
  2876.  
  2877. doHack = fnIntegrity(cProto.animateScroll_, '1.102.49')
  2878.  
  2879. }
  2880.  
  2881. if (doHack) {
  2882.  
  2883. const querySelector = HTMLElement.prototype.querySelector;
  2884. const U = (element) => ({
  2885. querySelector: (selector) => querySelector.call(element, selector)
  2886. });
  2887.  
  2888. cProto.animateScroll_ = function (a) {
  2889. // console.log('cProto.animateScroll_', tag) // yt-emoji-picker-renderer
  2890. if (!this.boundAnimateScroll39_) this.boundAnimateScroll39_ = this.animateScroll_.bind(this);
  2891. this.lastAnimationTime_ || (this.lastAnimationTime_ = a);
  2892. a -= this.lastAnimationTime_;
  2893. 200 > a ? (U(this.hostElement).querySelector("#categories").scrollTop = this.animationStart_ + (this.animationEnd_ - this.animationStart_) * a / 200,
  2894. rafHub.request(this.boundAnimateScroll39_)) : (null != this.animationEnd_ && (U(this.hostElement).querySelector("#categories").scrollTop = this.animationEnd_),
  2895. this.animationEnd_ = this.animationStart_ = null,
  2896. this.lastAnimationTime_ = 0);
  2897. this.updateButtons_()
  2898. }
  2899.  
  2900. console.log('ENABLE_RAF_HACK_EMOJI_PICKER', tag, "OK")
  2901. } else {
  2902.  
  2903. console.log('ENABLE_RAF_HACK_EMOJI_PICKER', tag, "NG")
  2904. }
  2905.  
  2906. })();
  2907.  
  2908. console.log("[End]");
  2909.  
  2910. console.groupEnd();
  2911. });
  2912. }
  2913.  
  2914. if (ENABLE_RAF_HACK_DOCKED_MESSAGE && rafHub !== null) {
  2915.  
  2916.  
  2917. customElements.whenDefined("yt-live-chat-docked-message").then(() => {
  2918.  
  2919. mightFirstCheckOnYtInit();
  2920. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-docked-message hacks");
  2921. console.log("[Begin]");
  2922. (() => {
  2923.  
  2924. const tag = "yt-live-chat-docked-message"
  2925. const dummy = document.createElement(tag);
  2926.  
  2927. const cProto = getProto(dummy);
  2928. if (!cProto || !cProto.attached) {
  2929. console.warn(`proto.attached for ${tag} is unavailable.`);
  2930. return;
  2931. }
  2932.  
  2933. let doHack = false;
  2934. if (typeof cProto.detached === 'function' && typeof cProto.checkIntersections === 'function' && typeof cProto.onDockableMessagesChanged === 'function' && typeof cProto.boundCheckIntersections === 'undefined') {
  2935.  
  2936. // cancelable - this.intersectRAF <detached>
  2937. // yt-live-chat-docked-message
  2938. // boundCheckIntersections <-> checkIntersections
  2939. // onDockableMessagesChanged
  2940. // this.intersectRAF = window.requestAnimationFrame(this.boundCheckIntersections);
  2941.  
  2942. console.log('detached', typeof cProto.detached)
  2943. console.log('checkIntersections', typeof cProto.checkIntersections)
  2944. console.log('onDockableMessagesChanged', typeof cProto.onDockableMessagesChanged)
  2945.  
  2946. doHack = fnIntegrity(cProto.detached, '0.32.22') && fnIntegrity(cProto.checkIntersections, '0.128.85') && fnIntegrity(cProto.onDockableMessagesChanged, '0.20.11')
  2947.  
  2948. }
  2949.  
  2950. if (doHack) {
  2951.  
  2952. cProto.checkIntersections = function () {
  2953. console.log('cProto.checkIntersections', tag)
  2954. if (this.dockableMessages.length) {
  2955. this.intersectRAF = rafHub.request(this.boundCheckIntersections);
  2956. var a = this.dockableMessages[0]
  2957. , b = this.hostElement.getBoundingClientRect();
  2958. a = a.getBoundingClientRect();
  2959. var c = a.top - b.top
  2960. , d = 8 >= c;
  2961. c = 8 >= c - this.hostElement.clientHeight;
  2962. if (d) {
  2963. for (var e; d;) {
  2964. e = this.dockableMessages.shift();
  2965. d = this.dockableMessages[0];
  2966. if (!d)
  2967. break;
  2968. d = d.getBoundingClientRect();
  2969. c = d.top - b.top;
  2970. var f = 8 >= c;
  2971. if (8 >= c - a.height)
  2972. if (f)
  2973. a = d;
  2974. else
  2975. return;
  2976. d = f
  2977. }
  2978. this.dock(e)
  2979. } else
  2980. c && this.dockedItem && this.clear()
  2981. } else
  2982. this.intersectRAF = 0
  2983. }
  2984.  
  2985. cProto.onDockableMessagesChanged = function () {
  2986. // console.log('cProto.onDockableMessagesChanged', tag) // yt-live-chat-docked-message
  2987. this.dockableMessages.length && !this.intersectRAF && (this.intersectRAF = rafHub.request(this.boundCheckIntersections))
  2988. }
  2989.  
  2990. cProto.detached = function () {
  2991. this.intersectRAF && rafHub.cancel(this.intersectRAF)
  2992. }
  2993.  
  2994. console.log('ENABLE_RAF_HACK_DOCKED_MESSAGE', tag, "OK")
  2995. } else {
  2996.  
  2997. console.log('ENABLE_RAF_HACK_DOCKED_MESSAGE', tag, "NG")
  2998. }
  2999.  
  3000. })();
  3001.  
  3002. console.log("[End]");
  3003.  
  3004. console.groupEnd();
  3005.  
  3006. });
  3007.  
  3008. }
  3009.  
  3010.  
  3011. }
  3012.  
  3013. promiseForCustomYtElementsReady.then(onRegistryReadyForDOMOperations);
  3014.  
  3015.  
  3016. })();
  3017.  
  3018.  
  3019. });
  3020.  
  3021. })({ IntersectionObserver });