YouTube 超快聊天

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

目前为 2023-08-14 提交的版本。查看 最新版本

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