Greasy Fork is available in English.

YouTube CPU Tamer – Hybrid Edition (Improved)

Reduce CPU load on YouTube using hybrid DOMMutation + AnimationFrame strategy with dynamic switching and delay correction

  1. // ==UserScript==
  2. // @name YouTube CPU Tamer – Hybrid Edition (Improved)
  3. // @name:ja YouTube CPU負荷軽減スクリプト – ハイブリッド方式(改良版)
  4. // @name:en YouTube CPU Tamer – Hybrid Edition (Improved)
  5. // @name:zh-CN YouTube CPU减负脚本 – 混合策略(改进版)
  6. // @name:zh-TW YouTube CPU負載減輕工具 – 混合策略(改良版)
  7. // @name:ko YouTube CPU 부하 감소 스크립트 – 하이브리드 방식(개선판)
  8. // @name:fr Réducteur de charge CPU YouTube – Édition Hybride (Améliorée)
  9. // @name:es Reductor de carga de CPU para YouTube – Edición Híbrida (Mejorada)
  10. // @name:de YouTube CPU-Last-Reduzierer – Hybrid-Edition (Verbessert)
  11. // @name:pt-BR Redutor de uso da CPU no YouTube – Edição Híbrida (Aprimorada)
  12. // @name:ru Снижение нагрузки на CPU в YouTube – Гибридная версия (Улучшенная)
  13. // @version 4.01
  14. // @description Reduce CPU load on YouTube using hybrid DOMMutation + AnimationFrame strategy with dynamic switching and delay correction
  15. // @description:ja DOM変化とrequestAnimationFrameを組み合わせたハイブリッド戦略でYouTubeのCPU負荷を大幅軽減!遅延補正&動的切替も搭載。
  16. // @description:en Reduce CPU load on YouTube using hybrid DOMMutation + AnimationFrame strategy with dynamic switching and delay correction
  17. // @description:zh-CN 使用混合DOMMutation和requestAnimationFrame策略动态切换并校正延迟,降低YouTube的CPU负载
  18. // @description:zh-TW 採用混合DOMMutation與requestAnimationFrame策略,動態切換並修正延遲,降低YouTube的CPU負載
  19. // @description:ko DOM 변화 감지 + 애니메이션 프레임 전략으로 YouTube CPU 부하 감소, 지연 보정 및 동적 전환 포함
  20. // @description:fr Réduisez la charge CPU de YouTube avec une stratégie hybride DOMMutation + AnimationFrame, avec commutation dynamique et correction du délai
  21. // @description:es Reduce la carga de CPU en YouTube mediante una estrategia híbrida de DOMMutation y AnimationFrame, con conmutación dinámica y corrección de retrasos
  22. // @description:de Reduzieren Sie die CPU-Last von YouTube mit einer hybriden DOMMutation + AnimationFrame-Strategie mit dynamischem Wechsel und Verzögerungskorrektur
  23. // @description:pt-BR Reduza o uso da CPU no YouTube com uma estratégia híbrida DOMMutation + AnimationFrame com troca dinâmica e correção de atraso
  24. // @description:ru Снижение нагрузки на CPU в YouTube с помощью гибридной стратегии DOMMutation + requestAnimationFrame с динамическим переключением и коррекцией задержки
  25. // @namespace https://github.com/koyasi777/youtube-cpu-tamer-hybrid
  26. // @author koyasi777
  27. // @match https://www.youtube.com/*
  28. // @match https://www.youtube.com/embed/*
  29. // @match https://www.youtube-nocookie.com/embed/*
  30. // @match https://music.youtube.com/*
  31. // @run-at document-start
  32. // @grant none
  33. // @inject-into page
  34. // @license MIT
  35. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  36. // @homepageURL https://github.com/koyasi777/youtube-cpu-tamer-hybrid
  37. // @supportURL https://github.com/koyasi777/youtube-cpu-tamer-hybrid/issues
  38. // ==/UserScript==
  39.  
  40. (() => {
  41. "use strict";
  42.  
  43. /**
  44. * Guard – avoid double‑instantiation on SPA navigation
  45. */
  46. const FLAG = "__yt_cpu_tamer_hybrid_running__";
  47. if (window[FLAG]) return;
  48. window[FLAG] = true;
  49.  
  50. /*************************************************************************
  51. * Helpers
  52. *************************************************************************/
  53. const nextAnimationFrame = () => new Promise(r => requestAnimationFrame(r));
  54.  
  55. const waitForDocReady = async () => {
  56. while (!document.documentElement || !document.head) {
  57. await nextAnimationFrame();
  58. }
  59. };
  60.  
  61. /**
  62. * A thin extended‑Promise that exposes resolve()/reject() – convenient for
  63. * bridging observer + rAF based triggers without extra closures.
  64. */
  65. const PromiseExt = (() => {
  66. let _res, _rej;
  67. const shim = (r, j) => {
  68. _res = r;
  69. _rej = j;
  70. };
  71. return class extends Promise {
  72. constructor(cb = shim) {
  73. super(cb);
  74. if (cb === shim) {
  75. /** @type {(value?: unknown) => void} */
  76. // @ts-ignore – dynamically injected
  77. this.resolve = _res;
  78. /** @type {(reason?: any) => void} */
  79. // @ts-ignore
  80. this.reject = _rej;
  81. }
  82. }
  83. };
  84. })();
  85.  
  86. /*************************************************************************
  87. * Main
  88. *************************************************************************/
  89. const setup = async () => {
  90. await waitForDocReady();
  91.  
  92. /***** 1. Create helper iframe that owns lightweight timers *****/
  93. const FRAME_ID = "yt-cpu-tamer-timer-frame";
  94. let frame = document.getElementById(FRAME_ID);
  95. if (!frame) {
  96. frame = document.createElement("iframe");
  97. frame.id = FRAME_ID;
  98. frame.style.display = "none";
  99. // Allow both same‑origin and script execution so that callbacks routed
  100. // through the iframe don’t hit the Chrome sandbox error.
  101. frame.sandbox = "allow-same-origin allow-scripts";
  102. // Use srcdoc to keep the iframe same‑origin (& blank) in all browsers.
  103. frame.srcdoc = "<!doctype html><title>yt-cpu-tamer</title>";
  104. document.documentElement.appendChild(frame);
  105. }
  106. // Wait until the inner window is ready.
  107. while (!frame.contentWindow) {
  108. await nextAnimationFrame();
  109. }
  110.  
  111. const {
  112. requestAnimationFrame: frameRAF,
  113. setTimeout: frameSetTimeout,
  114. setInterval: frameSetInterval,
  115. clearTimeout: frameClearTimeout,
  116. clearInterval: frameClearInterval
  117. } = /** @type {Window & typeof globalThis} */ (frame.contentWindow);
  118.  
  119. /***** 2. Trigger generator – rAF when visible, MutationObserver otherwise *****/
  120. const dummy = document.createElement("div");
  121. dummy.style.display = "none";
  122. document.documentElement.appendChild(dummy);
  123.  
  124. /** @returns {(cb: () => void) => Promise<void>} */
  125. const makeHybridTrigger = () => {
  126. if (document.visibilityState === "visible") {
  127. return cb => {
  128. const p = new PromiseExt();
  129. requestAnimationFrame(p.resolve);
  130. return p.then(cb);
  131. };
  132. } else {
  133. return cb => {
  134. const p = new PromiseExt();
  135. const MO = new MutationObserver(() => {
  136. MO.disconnect();
  137. p.resolve();
  138. });
  139. MO.observe(dummy, { attributes: true });
  140. dummy.setAttribute("data-yt-cpu-tamer", Math.random().toString(36));
  141. return p.then(cb);
  142. };
  143. }
  144. };
  145.  
  146. /** @type {(cb: () => void) => Promise<void>} */
  147. let currentTrigger = makeHybridTrigger();
  148. document.addEventListener("visibilitychange", () => {
  149. currentTrigger = makeHybridTrigger();
  150. });
  151.  
  152. /***** 3. Timer patching *****/
  153. const activeTimeouts = new Set();
  154. const activeIntervals = new Set();
  155.  
  156. /**
  157. * Wrap native timer so that:
  158. * – scheduling is done with *iframe* timers (very cheap)
  159. * – execution is throttled by currentTrigger
  160. * – callback runs in the *main* window realm (fn.apply(window,…))
  161. */
  162. const makeTimer = (nativeTimer, pool) => {
  163. return function patchedTimer(fn, delay = 0, ...args) {
  164. if (typeof fn !== "function") return nativeTimer(fn, delay, ...args);
  165. const id = nativeTimer(() => {
  166. currentTrigger(() => fn.apply(window, args));
  167. }, delay);
  168. pool.add(id);
  169. return id;
  170. };
  171. };
  172.  
  173. const makeClear = (nativeClear, pool) => id => {
  174. if (pool.has(id)) pool.delete(id);
  175. nativeClear(id);
  176. };
  177.  
  178. /**
  179. * Apply / re‑apply the patches (re‑applied on yt‑navigate‑finish).
  180. */
  181. const patchTimers = () => {
  182. window.setTimeout = makeTimer(frameSetTimeout, activeTimeouts);
  183. window.setInterval = makeTimer(frameSetInterval, activeIntervals);
  184. window.clearTimeout = makeClear(frameClearTimeout, activeTimeouts);
  185. window.clearInterval = makeClear(frameClearInterval, activeIntervals);
  186.  
  187. // Align Function.prototype.toString() so that devtools show native code
  188. const mirrorToString = (patched, native) => {
  189. try {
  190. patched.toString = native.toString.bind(native);
  191. } catch {/* ignore */}
  192. };
  193. mirrorToString(window.setTimeout, frameSetTimeout);
  194. mirrorToString(window.setInterval, frameSetInterval);
  195. mirrorToString(window.clearTimeout, frameClearTimeout);
  196. mirrorToString(window.clearInterval, frameClearInterval);
  197.  
  198. console.log("[YouTube CPU Tamer – Hybrid Edition] Timers patched");
  199. };
  200.  
  201. // Initial patch (DOMContentLoaded OR immediate if already interactive).
  202. if (document.readyState === "loading") {
  203. document.addEventListener("DOMContentLoaded", patchTimers, { once: true });
  204. } else {
  205. patchTimers();
  206. }
  207.  
  208. /***** 4. Re‑patch on SPA navigations *****/
  209. window.addEventListener("yt-navigate-finish", () => {
  210. console.log("[YouTube CPU Tamer] yt-navigate-finish – re‑applying patch");
  211. patchTimers();
  212. });
  213. };
  214.  
  215. setup().catch(err => console.error("[YouTube CPU Tamer] setup failed", err));
  216. })();