YouTube EXPERIMENT_FLAGS Tamer

Adjust EXPERIMENT_FLAGS

Old: v1.1.1 - 2023-09-18 - blobURL for clean context in WebKit
New: v1.2.0 - 2023-09-19 - Change from setInterval to ytConfigHacks

  • --- /tmp/diffy20250804-3629661-qpml5e 2025-08-04 18:21:05.723291455 +0000
  • +++ /tmp/diffy20250804-3629661-2tpqe6 2025-08-04 18:21:05.723291455 +0000
  • @@ -2,7 +2,7 @@
  • // @name YouTube EXPERIMENT_FLAGS Tamer
  • // @namespace UserScripts
  • // @match https://www.youtube.com/*
  • -// @version 1.1.1
  • +// @version 1.2.0
  • // @license MIT
  • // @author CY Fung
  • // @icon https://github.com/cyfung1031/userscript-supports/raw/main/icons/yt-engine.png
  • @@ -12,6 +12,7 @@
  • // @run-at document-start
  • // @allFrames true
  • // @inject-into page
  • +// @require https://gf.zukizuki.org/scripts/475632-ytconfighacks/code/ytConfigHacks.js?version=1252599
  • // ==/UserScript==
  • ((__CONTEXT__) => {
  • @@ -68,7 +69,6 @@
  • // kevlar_tuner_default_comments_delay
  • // kevlar_tuner_run_default_comments_delay
  • - let settled = null;
  • // cinematic feature is no longer an experimential feature.
  • // It has been officially implemented.
  • // To disable cinematics, the user shall use other userscripts or just turn off the option in the video options.
  • @@ -369,166 +369,36 @@
  • }
  • - const all_live_chat_flags = `live_chat_banner_expansion_fix
  • - live_chat_enable_mod_view
  • - live_chat_enable_qna_banner_overflow_menu_actions
  • - live_chat_enable_qna_channel
  • - live_chat_enable_send_button_in_slow_mode
  • - live_chat_filter_emoji_suggestions
  • - live_chat_increased_min_height
  • - live_chat_over_playlist
  • - live_chat_web_enable_command_handler
  • - live_chat_web_use_emoji_manager_singleton
  • - live_chat_whole_message_clickable`.trim().split(/\s+/)
  • -
  • - const hLooper = ((fn) => {
  • -
  • - let nativeFnLoaded = false;
  • - let kc1 = 0;
  • -
  • - const setIntervalW = setInterval;
  • - const clearIntervalW = clearInterval;
  • - let microDisconnectFn = null;
  • - let fStopLooper = false;
  • - const looperFn = () => {
  • - if (fStopLooper) return;
  • - let config_ = null;
  • - let EXPERIMENT_FLAGS = null;
  • - try {
  • - config_ = yt.config_ || ytcfg.data_;
  • - } catch (e) { }
  • - if (!config_) return;
  • - EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS || 0;
  • -
  • - if (EXPERIMENT_FLAGS) {
  • -
  • -
  • - if (!settled) {
  • - settled = {
  • - use_maintain_stable_list: getSettingValue(ENABLE_EXPERIMENT_FLAGS_MAINTAIN_STABLE_LIST),
  • - use_maintain_reuse_components: getSettingValue(ENABLE_EXPERIMENT_FLAGS_MAINTAIN_REUSE_COMPONENTS),
  • - use_defer_detach: getSettingValue(ENABLE_EXPERIMENT_FLAGS_DEFER_DETACH),
  • - }
  • - if (settled.use_maintain_stable_list) Promise.resolve().then(() => console.debug("use_maintain_stable_list"));
  • - if (settled.use_maintain_reuse_components) Promise.resolve().then(() => console.debug("use_maintain_reuse_components"));
  • - if (settled.use_defer_detach) Promise.resolve().then(() => console.debug("use_defer_detach"));
  • - }
  • -
  • - fn(config_);
  • - }
  • -
  • - if (EXPERIMENT_FLAGS && microDisconnectFn) {
  • -
  • -
  • - let isYtLoaded = false;
  • - try {
  • - isYtLoaded = typeof ytcfg.set === 'function';
  • - } catch (e) { }
  • - if (isYtLoaded) {
  • - microDisconnectFn();
  • - }
  • -
  • - }
  • -
  • - const playerKevlar = (config_.WEB_PLAYER_CONTEXT_CONFIGS || 0).WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH || 0;
  • -
  • -
  • - if (playerKevlar && !zPlayerKevlar) {
  • - zPlayerKevlar = true;
  • -
  • - if (NO_SerializedExperiment && typeof playerKevlar.serializedExperimentFlags === 'string' && typeof playerKevlar.serializedExperimentIds === 'string') {
  • - fixSerializedExperiment(playerKevlar);
  • - }
  • -
  • -
  • - }
  • -
  • -
  • -
  • - };
  • -
  • - const controller = {
  • - start() {
  • - kc1 = setIntervalW(looperFn, 1);
  • - (async () => {
  • - while (true && !nativeFnLoaded) {
  • - looperFn();
  • - if (fStopLooper) break;
  • - await (new Promise(requestAnimationFrame));
  • - }
  • - })();
  • - looperFn();
  • - },
  • - /**
  • - *
  • - * @param {Window} __CONTEXT__
  • - */
  • - setupForCleanContext(__CONTEXT__) {
  • -
  • - const { requestAnimationFrame, setInterval, clearInterval, setTimeout, clearTimeout } = __CONTEXT__;
  • -
  • - (async () => {
  • - while (true) {
  • - looperFn();
  • - if (fStopLooper) break;
  • - await (new Promise(requestAnimationFrame));
  • - }
  • - })();
  • -
  • - let kc2 = setInterval(looperFn, 1);
  • -
  • - const marcoDisconnectFn = () => {
  • - if (fStopLooper) return;
  • - Promise.resolve().then(() => {
  • - if (kc1 || kc2) {
  • - kc1 && clearIntervalW(kc1); kc1 = 0;
  • - kc2 && clearInterval(kc2); kc2 = 0;
  • - looperFn();
  • - }
  • - fStopLooper = true;
  • - });
  • - document.removeEventListener('yt-page-data-fetched', marcoDisconnectFn, false);
  • - document.removeEventListener('yt-navigate-finish', marcoDisconnectFn, false);
  • - document.removeEventListener('spfdone', marcoDisconnectFn, false);
  • - };
  • - document.addEventListener('yt-page-data-fetched', marcoDisconnectFn, false);
  • - document.addEventListener('yt-navigate-finish', marcoDisconnectFn, false);
  • - document.addEventListener('spfdone', marcoDisconnectFn, false);
  • -
  • -
  • - function onReady() {
  • - if (!fStopLooper) {
  • - setTimeout(() => {
  • - !fStopLooper && marcoDisconnectFn();
  • - }, 1000);
  • - }
  • - }
  • -
  • - Promise.resolve().then(() => {
  • - if (document.readyState !== 'loading') {
  • - onReady();
  • - } else {
  • - window.addEventListener("DOMContentLoaded", onReady, false);
  • - }
  • - });
  • -
  • + const all_live_chat_flags = new Set([
  • + "live_chat_banner_expansion_fix",
  • + "live_chat_enable_mod_view",
  • + "live_chat_enable_qna_banner_overflow_menu_actions",
  • + "live_chat_enable_qna_channel",
  • + "live_chat_enable_send_button_in_slow_mode",
  • + "live_chat_filter_emoji_suggestions",
  • + "live_chat_increased_min_height",
  • + "live_chat_over_playlist",
  • + "live_chat_web_enable_command_handler",
  • + "live_chat_web_use_emoji_manager_singleton",
  • + "live_chat_whole_message_clickable"
  • + ]);
  • +
  • + let brc = 1000;
  • +
  • + const hExperimentFlagsFn = () => {
  • +
  • + if (brc > 4) brc = 4;
  • +
  • + const use_maintain_stable_list = getSettingValue(ENABLE_EXPERIMENT_FLAGS_MAINTAIN_STABLE_LIST);
  • + const use_maintain_reuse_components = getSettingValue(ENABLE_EXPERIMENT_FLAGS_MAINTAIN_REUSE_COMPONENTS);
  • + const use_defer_detach = getSettingValue(ENABLE_EXPERIMENT_FLAGS_DEFER_DETACH);
  • +
  • + if (use_maintain_stable_list) Promise.resolve().then(() => console.debug("use_maintain_stable_list"));
  • + if (use_maintain_reuse_components) Promise.resolve().then(() => console.debug("use_maintain_reuse_components"));
  • + if (use_defer_detach) Promise.resolve().then(() => console.debug("use_defer_detach"));
  • - nativeFnLoaded = true;
  • -
  • - microDisconnectFn = () => Promise.resolve(marcoDisconnectFn).then(setTimeout);
  • -
  • - }
  • - };
  • -
  • - return controller;
  • - })((config_) => {
  • -
  • - if (!config_.EXPERIMENT_FLAGS) return;
  • -
  • -
  • - const { use_maintain_stable_list, use_maintain_reuse_components, use_defer_detach } = settled;
  • -
  • - // i don't know why it requires to be extracted function.
  • + // I don't know why it requires to be extracted function.
  • const mex = (EXPERIMENT_FLAGS, mzFlagDetected, fEntries) => {
  • for (const [key, value] of fEntries) {
  • @@ -655,7 +525,7 @@
  • } else {
  • - if (ALLOW_ALL_LIVE_CHATS_FLAGS && all_live_chat_flags.indexOf(key) >= 0) {
  • + if (ALLOW_ALL_LIVE_CHATS_FLAGS && all_live_chat_flags.has(key)) {
  • continue;
  • /*
  • *
  • @@ -670,7 +540,7 @@
  • live_chat_web_enable_command_handler
  • live_chat_web_use_emoji_manager_singleton
  • live_chat_whole_message_clickable
  • +
  • */
  • }
  • @@ -865,7 +735,7 @@
  • // EXPERIMENT_FLAGS.kevlar_wiz_prototype_enable_all_components= true;
  • // EXPERIMENT_FLAGS.kevlar_wiz_enable_on_demand_alternative_components= true;
  • - EXPERIMENT_FLAGS.web_enable_dynamic_metadata = true;
  • + // EXPERIMENT_FLAGS.web_enable_dynamic_metadata = true;
  • EXPERIMENT_FLAGS.web_supports_animations_api = true;
  • EXPERIMENT_FLAGS.smartimation_background = true;
  • @@ -981,71 +851,86 @@
  • };
  • - setterFn(config_.EXPERIMENT_FLAGS, mzFlagDetected1);
  • + return setterFn;
  • - if (config_.EXPERIMENTS_FORCED_FLAGS) setterFn(config_.EXPERIMENTS_FORCED_FLAGS, mzFlagDetected2);
  • + };
  • - });
  • + let _setterFn = null;
  • - hLooper.start();
  • + const setupConfig = (config_) => {
  • + if (config_.EXPERIMENT_FLAGS) {
  • - const cleanContext = async (win) => {
  • - const waitFn = requestAnimationFrame; // shall have been binded to window
  • - try {
  • - let mx = 16; // MAX TRIAL
  • - const frameId = 'vanillajs-iframe-v1'
  • - let frame = document.getElementById(frameId);
  • - let removeIframeFn = null;
  • - if (!frame) {
  • - frame = document.createElement('iframe');
  • - frame.id = frameId;
  • - const blobURL = typeof webkitCancelAnimationFrame === 'function' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash
  • - frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  • - let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  • - n.appendChild(frame);
  • - while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  • - const root = document.documentElement;
  • - root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  • - if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL));
  • -
  • - removeIframeFn = (setTimeout) => {
  • - const removeIframeOnDocumentReady = (e) => {
  • - e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  • - win = null;
  • - const m = n;
  • - n = null;
  • - setTimeout(() => m.remove(), 200);
  • - }
  • - if (document.readyState !== 'loading') {
  • - removeIframeOnDocumentReady();
  • - } else {
  • - win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  • - }
  • - }
  • -
  • + const setterFn = _setterFn || (_setterFn = hExperimentFlagsFn());
  • +
  • + setterFn(config_.EXPERIMENT_FLAGS, mzFlagDetected1);
  • +
  • + if (config_.EXPERIMENTS_FORCED_FLAGS) setterFn(config_.EXPERIMENTS_FORCED_FLAGS, mzFlagDetected2);
  • + }
  • +
  • + const playerKevlar = (config_.WEB_PLAYER_CONTEXT_CONFIGS || 0).WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH || 0;
  • +
  • + if (playerKevlar && !zPlayerKevlar) {
  • + zPlayerKevlar = true;
  • +
  • + if (NO_SerializedExperiment && typeof playerKevlar.serializedExperimentFlags === 'string' && typeof playerKevlar.serializedExperimentIds === 'string') {
  • + fixSerializedExperiment(playerKevlar);
  • +
  • }
  • - while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  • - const fc = frame.contentWindow;
  • - if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  • - const { requestAnimationFrame, setInterval, setTimeout, clearInterval, clearTimeout } = fc;
  • - const res = { requestAnimationFrame, setInterval, setTimeout, clearInterval, clearTimeout };
  • - for (let k in res) res[k] = res[k].bind(win); // necessary
  • - if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  • - return res;
  • - } catch (e) {
  • - console.warn(e);
  • - return null;
  • }
  • +
  • + }
  • +
  • +
  • + const looperFn = (config_) => {
  • + if (--brc < 0) return;
  • +
  • + if (!config_) {
  • + try {
  • + config_ = yt.config_ || ytcfg.data_;
  • + } catch (e) { }
  • + }
  • + if (config_) setupConfig(config_);
  • +
  • };
  • - cleanContext(win).then(__CONTEXT__ => {
  • + const hLooperMx = () => {
  • +
  • + const eventTriggerFn = () => {
  • + if (brc > 4) brc = 4;
  • + looperFn();
  • + document.removeEventListener('yt-page-data-fetched', eventTriggerFn, false);
  • + document.removeEventListener('yt-navigate-finish', eventTriggerFn, false);
  • + document.removeEventListener('spfdone', eventTriggerFn, false);
  • + };
  • + document.addEventListener('yt-page-data-fetched', eventTriggerFn, false);
  • + document.addEventListener('yt-navigate-finish', eventTriggerFn, false);
  • + document.addEventListener('spfdone', eventTriggerFn, false);
  • +
  • + window._ytConfigHacks.add((config_) => {
  • + looperFn(config_);
  • + });
  • +
  • + function onReady() {
  • + if (brc > 4) brc = 4;
  • + looperFn();
  • + }
  • +
  • - const { requestAnimationFrame, setInterval, clearInterval, setTimeout, clearTimeout } = __CONTEXT__;
  • + Promise.resolve().then(() => {
  • + if (document.readyState !== 'loading') {
  • + onReady();
  • + } else {
  • + window.addEventListener("DOMContentLoaded", onReady, false);
  • + }
  • + });
  • +
  • + looperFn();
  • +
  • + };
  • - hLooper.setupForCleanContext(__CONTEXT__)
  • + hLooperMx();
  • - });
  • if (isMainWindow) {