// ==UserScript== // @name YouTube JS Engine Tamer // @namespace UserScripts // @version 0.18.18 // @match https://www.youtube.com/* // @match https://www.youtube-nocookie.com/embed/* // @match https://studio.youtube.com/live_chat* // @license MIT // @author CY Fung // @icon https://raw.githubusercontent.com/cyfung1031/userscript-supports/main/icons/yt-engine.png // @description To enhance YouTube performance by modifying YouTube JS Engine // @grant none // @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@7221a4efffd49d852de0074ec503d4febb99f28b/library/nextBrowserTick.min.js // @run-at document-start // @unwrap // @inject-into page // @allFrames true // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/ // @downloadURL https://update.greasyfork.cloud/scripts/473972/YouTube%20JS%20Engine%20Tamer.user.js // @updateURL https://update.greasyfork.cloud/scripts/473972/YouTube%20JS%20Engine%20Tamer.meta.js // ==/UserScript== (() => { /** @type {WeakMapConstructor} */ const WeakMap = window.WeakMapOriginal || window.WeakMap; const NATIVE_CANVAS_ANIMATION = false; // for #cinematics const FIX_schedulerInstanceInstance = 2 | 4; const FIX_yt_player = true; // DONT CHANGE const FIX_Animation_n_timeline = true; const NO_PRELOAD_GENERATE_204 = false; const ENABLE_COMPUTEDSTYLE_CACHE = true; const NO_SCHEDULING_DUE_TO_COMPUTEDSTYLE = true; const CHANGE_appendChild = true; // discussions#236759 const FIX_bind_self_this = false; // EXPERIMENTAL !!!!! this affect page switch after live ends const FIX_weakMap_weakRef = false; // EXPERIMENTAL !!!!! Might Incompatible to some userscripts (as the strong relationship is removed) const FIX_error_many_stack = true; // should be a bug caused by uBlock Origin // const FIX_error_many_stack_keepAliveDuration = 200; // ms // const FIX_error_many_stack_keepAliveDuration_check_if_n_larger_than = 8; const FIX_Iframe_NULL_SRC = false; const IGNORE_bindAnimationForCustomEffect = true; // prevent `v.bindAnimationForCustomEffect(this);` being executed const FIX_ytdExpander_childrenChanged = true; const FIX_paper_ripple_animate = true; const FIX_avoid_incorrect_video_meta = true; // omit the incorrect yt-animated-rolling-number const FIX_avoid_incorrect_video_meta_emitterBehavior = true; const FIX_doIdomRender = true; const FIX_Shady = true; // [[ 2024.04.24 ]] const MODIFY_ShadyDOM_OBJ = true; // << if MODIFY_ShadyDOM_OBJ >> const WEAKREF_ShadyDOM = true; const OMIT_ShadyDOM_EXPERIMENTAL = 1 | 0; // 1 => enable; 2 => composedPath const OMIT_ShadyDOM_settings = 0 | 0 | 0; // 1: inUse; 2: handlesDynamicScoping; 4: force // {{ PRELIM TESTING PURPOSE }} // << end >> const WEAK_REF_BINDING_CONTROL = 1 | 2; // 2 - conflict control with ShadyDOM weakref const FIX_ytAction_ = true; // ytd-app const FIX_onVideoDataChange = false; // const FIX_onClick = true; const FIX_onStateChange = true; const FIX_onLoopRangeChange = true; // const FIX_maybeUpdateFlexibleMenu = true; // ytd-menu-renderer const FIX_VideoEVENTS_v2 = true; // true might cause bug in switching page const ENABLE_discreteTasking = true; // << if ENABLE_discreteTasking >> const FIX_stampDomArray_stableList = true; const ENABLE_weakenStampReferences = true; // disabled in old browsers // << end >> const FIX_perfNow = true; // history state issue; see https://bugzilla.mozilla.org/show_bug.cgi?id=1756970 const ENABLE_ASYNC_DISPATCHEVENT = false; // problematic const FIX_Polymer_dom = true; const SCRIPTLET_REMOVE_PRUNE_propNeedles = true; // brave scriptlet related const DEBUG_removePrune = false; // true for DEBUG const FIX_XHR_REQUESTING = true; const LOG_FETCHMETA_UPDATE = false; // for DEBUG const IGNORE_bufferhealth_CHECK = false; // experimental; true will make "Stats for nerds" no info. const DENY_requestStorageAccess = true; // remove document.requestStorageAccess const DISABLE_IFRAME_requestStorageAccess = true; // no effect if DENY_requestStorageAccess is true const DISABLE_COOLDOWN_SCROLLING = true; // YT cause scroll hang in MacOS const FIX_removeChild = true; const FIX_fix_requestIdleCallback_timing = true; const HOOK_CSSPD_LEFT = true; // global css hack for style.left const FORCE_NO_REUSEABLE_ELEMENT_POOL = true; const FIX_TRANSCRIPT_SEGMENTS = true; // Based on Tabview Youtube's implementation const DO_createStampDomArrayFnE1_ = true; // added in 2025.02.11 - to improve stampDom responsiveness const DO_createStampDomArrayFnE1_noConstraintE = true; const DO_createStampDomArrayFnE1_nativeAppendD = true; const DO_createStampDomArrayFnF1_ = true; const DO_createStampDomArray_STRICT_FULFILLMENT = true; // avoid issues; can be changed to false const FIX_POPUP_UNIQUE_ID = true; // currently only for channel about popup; // ----------------------------- POPUP UNIQUE ID ISSUE ----------------------------- // example. https://www.youtube.com/channel/UCgPev1KKSCMbnNRsvN83Hag/about // first tp-yt-paper-dialog: show once the page is loaded. // second tp-yt-paper-dialog: click "...more" // third tp-yt-paper-dialog: click "... and 3 more links" // check with document.querySelectorAll('ytd-popup-container tp-yt-paper-dialog').length // currently, uniqueId is preassigned by the network resolveCommand. // so don't modify the source side, just modify the display side (popup display) via handleOpenPopupAction // other related functions e.g. handleClosePopupCommand_, getAndMaybeCreatePopup_, handleClosePopupAction_, getAndMaybeCreatePopup_ // handleOpenPopupAction -> createCacheKey // handleClosePopupAction_ -> createCacheKey // handleGetPopupOpenedAction_ -> createCacheKey // getAndMaybeCreatePopup_ -> createCacheKey // closePopup -> createCacheKey // yt-close-popup-command -> handleClosePopupCommand_ // ensurePopup_ -> getAndMaybeCreatePopup_ // yt-close-popup-action -> handleClosePopupAction_ // closePopup -> handleClosePopupAction_ // handleOpenPopupAction -> handleClosePopupAction_ // handleClosePopupCommand_ -> handleClosePopupAction_ // closeSheet -> handleClosePopupAction_("yt-sheet-view-model") // yt-open-popup-action -> handleOpenPopupAction // yt-close-popup-action -> handleClosePopupAction_ -> createCacheKey // yt-close-popup-command -> handleClosePopupCommand_ -> handleClosePopupAction_ -> createCacheKey // Experimental flag "ytpopup_disable_default_html_caching" is disabled by default. // Not sure enabling it can make GC or not (Yt Components are usually not GC-able) // ----------------------------- POPUP UNIQUE ID ISSUE ----------------------------- const FIX_DOM_IF_REPEAT = true; // semi-experimental (added in 0.17.0) const FIX_DOM_IF_TEMPLATE = true; // const FIX_DOM_REPEAT_TEMPLATE = true; // to be implemented const FIX_DOM_IFREPEAT_RenderDebouncerChange = false; // semi-experimental (added in 0.17.0) // found buggy for chat ticker sizing const DEBUG_DBR847 = false; const DEBUG_xx847 = false; const FIX_DOM_IFREPEAT_RenderDebouncerChange_SET_TO_PROPNAME = true; // default true. false might be required for future change const DEBUG_renderDebounceTs = false; const FIX_VIDEO_PLAYER_MOUSEHOVER_EVENTS = true; // avoid unnecessary reflows due to cursor moves on the web player. /* FIX_DOM_IFREPEAT_RenderDebouncerChange avoid Polymer.flush // https://www.youtube.com/s/desktop/26a583e4/jsbin/live_chat_polymer.vflset/live_chat_polymer.js var Is = function() { do { var a = window.ShadyDOM && ShadyDOM.flush(); window.ShadyCSS && window.ShadyCSS.ScopingShim && window.ShadyCSS.ScopingShim.flush(); var b = NNa() } while (a || b) }; , NNa = function() { var a = !!ts.size; ts.forEach(function(b) { try { b.flush() } catch (c) { setTimeout(function() { throw c }) } }); return a }; // why flush twice after all ts are completed? (!!ts.size => true => loop again) // this coding logic should be incorrect (mistake). */ // ----------------------------- Shortkey Keyboard Control ----------------------------- // dependency: FIX_yt_player const FIX_SHORTCUTKEYS = 2; // 0 - no fix; 1 - basic fix; 2 - advanced fix // [0] no fix - not recommended // [1] basic fix - just fix the global focus detection variable // [2] advanced fix - call the shortcut actions directly, auto foucs change, direct control of spacebar behavior, etc // (note) 0 or 1 if you find conflict with other userscripts/plugin const CHANGE_SPEEDMASTER_SPACEBAR_CONTROL = 0; // 0 - disable; 1 - force true; 2 - force false const USE_IMPROVED_PAUSERESUME_UNDER_NO_SPEEDMASTER = true; // only for SPEEDMASTER = false & FIX_SHORTCUTKEYS = 2 const PROP_OverReInclusion_AVOID = true; const PROP_OverReInclusion_DEBUGLOG = false; const PROP_OverReInclusion_LIST = new Set([ 'hostElement72', 'parentComponent72', 'localVisibilityObserver_72', 'cachedProviderNode_72', '__template72', '__templatizeOwner72', '__templateInfo72', '__dataHost72', '__CE_shadowRoot72', 'elements_72', 'ky36', 'kz62', 'm822', // To be reviewed. // chat messages 'disabled', 'allowedProps', 'filledButtonOverrides', 'openPopupConfig', 'supportsInlineActionButtons', 'allowedProps', 'dimension', 'loadTime', 'pendingPaint', 'countdownDurationMs', 'countdownMs', 'lastCountdownTimeMs', 'rafId', 'playerProgressSec', 'detlaSincePausedSecs', 'behaviorActionMap', 'selected', 'maxLikeCount', 'maxReplyCount', 'isMouseOver', 'respectLangDir', 'noEndpoints', 'objectURL', 'buttonOverrides', 'queuedMessages', 'STEP', 'BLOCK_ON', 'MIN_PROGESS', 'MAX_PROGESS', 'DISMISSED_CONTENT_KEYSPACE', 'followUpDialogPromise', 'followUpDialogPromiseResolve', 'followUpDialogPromiseReject', 'hoverJobId', 'JSC$14573_touched', // tbc 'toggleable', 'isConnected', 'scrollDistance', 'dragging', 'dragMouseStart', 'dragOffsetStart', 'containerWidthDiff', 'disableDeselectEvent', 'emojiSize', 'buttonOverride', 'shouldUseStickyPreferences', 'longPressTimeoutId', // others 'observeVisibleOption', 'observeHiddenOption', 'observePrescanOption', 'visibilityMonitorKeys', // 'filledButtonOverrides', 'openPopupConfig', 'supportsInlineActionButtons', 'observeVisibleOption', 'observeHiddenOption', 'observePrescanOption', 'visibilityMonitorKeys', // 'dimension', 'loadTime', 'pendingPaint', // 'disabled', 'allowedProps', // 'enableMssLazyLoad', 'popupContainerConfig', 'actionRouterNode', 'actionRouterIsRoot', 'actionMap', 'dynamicActionMap', // 'actionMap', // 'sharedTooltipPosition', 'sharedTooltipAnimationDelay', 'disableEmojiPickerIncrementalLoading', 'useResolveCommand', 'activeRequest', 'popoutWindowCheckIntervalId', 'supportedTooltipTargets', 'closeActionPanelTimerId', 'delayCloseActionPanelTimerId', 'tooltipTimerIds', 'queuedTooltips', 'isPopupConfigReady', 'popoutWindow', 'actionMap', 'clearTimeout', 'switchTemplateAtRegistration', 'hasUnmounted', 'switchTemplateAtRegistration', 'stopKeyboardEventPropagation', 'tangoConfiguration', 'itemIdToDockDurationMap', 'actionMap', 'emojiManager', 'inputMethodEditorActive', 'suggestionIndex', 'JSC$10745_lastSuggestionRange', 'actionMap', 'asyncHandle', 'shouldAnimateIn', 'lastFrameTimestamp', 'scrollClampRaf', 'scrollRatePixelsPerSecond', 'scrollStartTime', 'scrollStopHandle' // 'buttonOverrides', 'queuedMessages', 'clearTimeout', 'actionMap', // 'stopKeyboardEventPropagation', 'emojiSize', // 'switchTemplateAtRegistration', 'hasUnmounted', // 'buttonOverrides', 'queuedMessages', 'clearTimeout', 'actionMap', // 'isReusable', 'tangoConfiguration', // 'itemIdToDockDurationMap', 'bottomAlignMessages', 'actionMap', // */ ]); // const CAN_TUNE_VOLUMN_AFTER_RESUME_OR_PAUSE = false; // NO USE; TO BE REVIEWED // ----------------------------- Shortkey Keyboard Control ----------------------------- /* window.addEventListener('edm',()=>{ let p = [...this.onerror.errorTokens][0].token; (()=>{ console.log(p); throw new Error(p);console.log(334,p) })() }); window.addEventListener('edn',()=>{ let p = [...this.onerror.errorTokens][0].token+"X"; (()=>{ console.log(p); throw new Error(p);console.log(334,p) })() }); window.addEventListener('edr',()=>{ let p = '123'; (()=>{ console.log(p); throw new Error(p);console.log(334,p) })() }); */ // only for macOS with Chrome/Firefox 100+ const advanceLogging = typeof AbortSignal !== 'undefined' && typeof (AbortSignal || 0).timeout === 'function' && typeof navigator !== 'undefined' && /\b(Macintosh|Mac\s*OS)\b/i.test((navigator || 0).userAgent || ''); const win = this instanceof Window ? this : window; // Create a unique key for the script and check if it is already running const hkey_script = 'jswylcojvzts'; if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting win[hkey_script] = true; // const [setTimeoutX0, clearTimeoutX0] = [setTimeout, clearTimeout]; let BY_PASS_KEYBOARD_CONTROL = false; // const setImmediate = ((self || 0).jmt || 0).setImmediate; /** @type {(f: ()=>{})=>{}} */ const nextBrowserTick = (self || 0).nextBrowserTick || 0; const nextBrowserTick_ = nextBrowserTick || (f => f()); let p59 = 0; const Promise = (async () => { })().constructor; const PromiseExternal = ((resolve_, reject_) => { const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject }; return class PromiseExternal extends Promise { constructor(cb = h) { super(cb); if (cb === h) { /** @type {(value: any) => void} */ this.resolve = resolve_; /** @type {(reason?: any) => void} */ this.reject = reject_; } } }; })(); const HTMLElement_ = HTMLElement; const nativeAppendE = HTMLElement_.prototype.append; const nativeRemoveE = HTMLElement_.prototype.remove; const DocumentFragment_ = DocumentFragment; const nativeAppendD = DocumentFragment_.prototype.append; const Node_ = Node; /** @param {number} x @param {number} d */ const toFixed2 = (x, d) => { let t = x.toFixed(d); let y = `${+t}`; return y.length > t.length ? t : y; } const isChatRoomURL = location.pathname.startsWith('/live_chat'); const TRANSLATE_DEBUG = false; function getTranslate() { pLoad.then(() => { let nonce = document.querySelector('style[nonce]'); nonce = nonce ? nonce.getAttribute('nonce') : null; const st = document.createElement('style'); if (typeof nonce === 'string') st.setAttribute('nonce', nonce); st.textContent = ".yt-formatted-string-block-line{display:block;}"; let parent; if (parent = document.head) parent.appendChild(st); else if (parent = (document.body || document.documentElement)) parent.insertBefore(st, parent.firstChild); }); const snCache = new Map(); if (TRANSLATE_DEBUG) { console.log(11) } /** @type {(str: string?) => string} */ function _snippetText(str) { // str can be underfinded if (!str || typeof str !== 'string') return ''; let res = snCache.get(str); if (res === undefined) { let b = false; res = str.replace(/[\s\u3000\u200b]*[\u200b\xA0\x20\n]+[\s\u3000\u200b]*/g, (m) => { b = true; return m.includes('\n') ? '\n' : m.replace(/\u200b/g, '').replace(/[\xA0\x20]+/g, ' '); }); res = res.replace(/^[\s\u3000]+|[\u3000\s]+$/g, () => { b = true; return ''; }); if (b) { snCache.set(str, res); snCache.set(res, null); } else { res = null; snCache.set(str, null); } } return res === null ? str : res; } /** @type {(snippet: Object) => string} */ function snippetText(snippet) { let runs = snippet.runs; const n = runs.length; if (n === 1) return _snippetText(runs[0].text); let res = new Array(n); let ci = 0; for (const s of runs) { res[ci++] = _snippetText(s.text); } return res.join('\n'); } const _DEBUG_szz = (t) => t.map(x => { const tsr = x.transcriptSegmentRenderer; return ({ t: tsr.snippet.runs.map(x => x.text).join('//'), a: tsr.startMs, b: tsr.endMs }); }); const fixRuns = (runs) => { if (runs.length === 1 && runs[0]?.text?.includes('\n')) { // https://www.youtube.com/watch?v=dmHJJ5k_G-A const text = runs[0].text; const nlc = text.includes('\r\n') ? '\r\n' : text.includes('\n\r') ? '\n\r' : text.includes('\r') ? '\r' : '\n'; const s = text.split(nlc); let bi = 0; runs.length = s.length; for (const text of s) { runs[bi++] = { ...runs[0], text, ...{blockLine: true} }; } } for (const s of runs) { s.text = _snippetText(s.text); } } function translate(initialSegments) { // 2023.07.13 - fix initialSegments with transcriptSectionHeaderRenderer if (!initialSegments) return initialSegments; if (TRANSLATE_DEBUG) { console.log(12); Promise.resolve(JSON.stringify(initialSegments)).then((r) => { let obj = JSON.parse(r); console.log(7558, 1, obj) return obj; }).then(p => { let obj = _DEBUG_szz(p) console.log(7558, 2, obj) }) } //let mapRej = new WeakSet(); const n1 = initialSegments.length; if (!n1) return fRes; let n2 = 0; const fRes = new Array(n1); // ----------------------------------------------------------------------------------------- const s8 = Symbol(); { /** @type {Map} */ let cacheTexts = new Map(); // avoid duplicate with javascript object properties // /-* * @type {Map} *-/ // let mh1 = new Map(); // avoid duplicate with javascript object properties // 1: ok // 2: abandoned effect text for (const initialSegment of initialSegments) { const transcript = (initialSegment || 0).transcriptSegmentRenderer; if (!transcript) { // https://www.youtube.com/watch?v=dmHJJ5k_G-A - transcriptSectionHeaderRenderer fRes[n2++] = initialSegment; continue; } const runs = transcript.snippet.runs if (!runs || runs.length === 0) { initialSegment[s8] = true; continue; } let startMs = (+transcript.startMs || 0); //integer let endMs = (+transcript.endMs || 0); //integer if (startMs === endMs) { // effect text // https://www.youtube.com/watch?v=Ud73fm4Uoq0 //mapRej.add(initialSegment) continue; } if (endMs - startMs < 30) { continue; } const text = snippetText(transcript.snippet); const hEntry = cacheTexts.get(text); const mh1e = hEntry === undefined ? 0 : hEntry === null ? 2 : 1; if (mh1e === 2) continue; const entry = { startMs, endMs, initialSegment, text }; if (mh1e === 0) { if (/^[,.\x60\x27\x22\u200b\xA0\x20;-]*$/.test(text)) { initialSegment[s8] = true; cacheTexts.set(text, null); //effect only // https://www.youtube.com/watch?v=zLak0dxBKpM //mapRej.add(initialSegment) continue; } } else if (hEntry) { const timeDiff = entry.startMs - hEntry.endMs; let shouldMerge = false; if (timeDiff >= 0) { if (timeDiff < 25) { shouldMerge = true; } else if (timeDiff < 450 && entry.endMs - entry.startMs < 900) { shouldMerge = true; } else if (timeDiff < 150 && entry.endMs - entry.startMs > 800) { shouldMerge = true; } if (shouldMerge && hEntry.endMs <= endMs && startMs <= endMs) { // abandon the current entry. // absorbed by previous entry hEntry.endMs = entry.endMs; hEntry.initialSegment.transcriptSegmentRenderer.endMs = entry.initialSegment.transcriptSegmentRenderer.endMs; // update fRes & initialSegments as well using object reference //mapRej.add(entry.initialSegment); continue; } } else if (entry.startMs < hEntry.startMs && hEntry.startMs < entry.endMs) { // abandon the current entry. // absorbed by previous entry if (entry.endMs > hEntry.endMs) { hEntry.endMs = entry.endMs; hEntry.initialSegment.transcriptSegmentRenderer.endMs = entry.initialSegment.transcriptSegmentRenderer.endMs; // update fRes & initialSegments as well using object reference } //mapRej.add(entry.initialSegment); continue; } } //if not abandoned cacheTexts.set(text, entry); //replace the previous valid entry object if any // for (const s of runs) { // s.text = _snippetText(s.text); // } fixRuns(runs); fRes[n2++] = initialSegment; } // cacheTexts.clear(); // let GC do it. cacheTexts = null; // mh1.clear(); // let GC do it. // mh1 = null; } const si_length = fRes.length = n2; const sj_length = n1; if (si_length !== sj_length) { // for equal length, no fix is required & ignore spacing fix // collect the abandon text to become second subtitle let sj_start = 0; let invalid_sj = -1; for (let si = 0; si < si_length; si++) { const segment = fRes[si]; let transcript = segment.transcriptSegmentRenderer; if (!transcript) continue; // e.g. transcriptSectionHeaderRenderer const runs = transcript.snippet.runs; // fixRuns(runs); if (runs.length > 1 || runs[0].text.includes('\n')) continue; // skip multi lines const main_startMs = (+transcript.startMs || 0); const main_endMs = (+transcript.endMs || 0); transcript = null; /** @type {Map} */ let tMap = new Map(); // avoid duplicate with javascript object properties // assume that it is asc-ordered array of key startMs; for (let sj = sj_start; sj < sj_length; sj++) { const initialSegment = initialSegments[sj]; if (!initialSegment || initialSegment[s8]) continue; // should invalid_sj be set ? const tSegment = initialSegment.transcriptSegmentRenderer; if (!tSegment) { // https://www.youtube.com/watch?v=dmHJJ5k_G-A - transcriptSectionHeaderRenderer invalid_sj = sj; // should invalid_sj be set ? continue; } const startMs = (+tSegment.startMs || 0) const isStartValid = startMs >= main_startMs; if (!isStartValid) { invalid_sj = sj; continue; } // isStartValid must be true if (startMs > main_endMs) { sj_start = invalid_sj + 1; break; } const endMs = (+tSegment.endMs || 0) if (endMs <= main_endMs) { const mt = snippetText(tSegment.snippet); const prev = tMap.get(mt); if (endMs >= startMs) { tMap.set(mt, (prev || 0) + 1 + (endMs - startMs)); } } } if (tMap.size <= 1) continue; // no second line let rg = [...tMap.entries()]; // N x 2 2D-array [string,number][] tMap = null; // https://www.youtube.com/watch?v=Ud73fm4Uoq0 rg.sort((a, b) => b[1] - a[1]); //descending order of number let targetZ = rg[1][1]; if (targetZ > 4) { let az = 0; let fail = false; for (let idx = 2, rgl = rg.length; idx < rgl; idx++) { az += rg[idx][1]; if (az >= targetZ) { fail = true; break; } } if (!fail) { const rgA = rg[0][0]; const rgB = rg[1][0]; const isDiff = rgB.replace(/\s/g, '') !== rgA.replace(/\s/g, ''); if (isDiff && rgA === _snippetText(runs[0].text)) { if (runs[0] && runs[0].text) runs[0].blockLine = true; runs.push({ text: rgB, blockLine: true }); } } } rg = null; } TRANSLATE_DEBUG && Promise.resolve(fRes).then((r) => { let obj = r; console.log(7559, 1, obj) return obj; }).then(p => { let obj = _DEBUG_szz(p) console.log(7559, 2, obj) }); } // ----------------------------------------------------------------------------------------- snCache.clear(); return fRes; } return translate } let translateFn = null; FIX_TRANSCRIPT_SEGMENTS && !isChatRoomURL && (() => { const wmx = new WeakMap(); function fixSegments(ytObj) { let a, b; let seg = ((a = ytObj.data) == null ? void 0 : a[b = 'searchResultSegments']) || ((a = ytObj.data) == null ? void 0 : a[b = 'initialSegments']) || []; if (!seg || !a || !b || typeof (seg || 0) !== 'object' || !Number.isFinite(seg.length * 1)) return; translateFn = translateFn || getTranslate(); let cSeg; cSeg = wmx.get(seg); if (!cSeg) { let vSeg = null; try { vSeg = translateFn(seg); } catch (e) { } if (seg && typeof seg === 'object' && seg.length >= 1 && vSeg && typeof vSeg === 'object' && vSeg.length >= 1) { // console.log('translated', vSeg); cSeg = vSeg; wmx.set(seg, cSeg); wmx.set(cSeg, cSeg); } } if (cSeg && cSeg !== seg) { a[b] = cSeg; } } const dfn = Symbol(); const Object_ = Object; Object_[dfn] = Object_.defineProperties; let activation = true; Object_.defineProperties = function (obj, pds) { let segments, get_; if (activation && pds && (segments = pds.segments) && (get_ = segments.get)) { activation = false; segments.get = function () { fixSegments(this); return get_.call(this); }; } return Object_[dfn](obj, pds); }; })(); let pf31 = new PromiseExternal(); // native RAF let __requestAnimationFrame__ = typeof webkitRequestAnimationFrame === 'function' ? window.webkitRequestAnimationFrame.bind(window) : window.requestAnimationFrame.bind(window); // 1st wrapped RAF const baseRAF = (callback) => { return p59 ? __requestAnimationFrame__(callback) : __requestAnimationFrame__((hRes) => { pf31.then(() => { callback(hRes); }); }); }; // 2nd wrapped RAF window.requestAnimationFrame = baseRAF; const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0); const indr = o => insp(o).$ || o.$ || 0; const prototypeInherit = (d, b) => { const m = Object.getOwnPropertyDescriptors(b); for (const p in m) { if (!Object.getOwnPropertyDescriptor(d, p)) { Object.defineProperty(d, p, m[p]); } } }; const firstObjectKey = (obj) => { for (const key in obj) { if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') return key; } return null; }; function searchNestedObject(obj, predicate, maxDepth = 64) { // normal case: depth until 36 const result = []; const visited = new WeakSet(); function search(obj, depth) { visited.add(obj); for (const [key, value] of Object.entries(obj)) { // Recursively search nested objects and arrays if (value !== null && typeof value === 'object') { // Prevent infinite loops by checking if the object is already visited or depth exceeded if (depth + 1 <= maxDepth && !visited.has(value)) { search(value, depth + 1); } } else if (predicate(value)) { result.push([obj, key]); } } } typeof (obj || 0) === 'object' && search(obj, 0); return result; } /** @type {(o: Object | null) => WeakRef | null} */ const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); /** @type {(wr: Object | null) => Object | null} */ const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr); const isIterable = (obj) => (Symbol.iterator in Object_(obj)); if (typeof Document.prototype.requestStorageAccessFor === 'function') { if (DENY_requestStorageAccess) { // https://developer.mozilla.org/en-US/docs/Web/API/Document/requestStorageAccessFor Document.prototype.requestStorageAccessFor = undefined; console.log('[yt-js-engine-tamer]', 'requestStorageAccessFor is removed.'); } else if (DISABLE_IFRAME_requestStorageAccess && window !== top) { Document.prototype.requestStorageAccessFor = function () { return new Promise((resolve, reject) => { reject(); }); }; } } const traceStack = (stack) => { let result = new Set(); let p = new Set(); let u = '' for (const s of stack.split('\n')) { if (s.split(':').length < 3) continue; let m = /(([\w-_\.]+):\d+:\d+)[^:\r\n]*/.exec(s); if (!m) continue; p.add(m[2]); if (p.size >= 3) break; if(!u) u = m[2]; else if(p.size === 2 && u && u=== m[2]) break; result.add(s); } return [...result].join('\n'); } if (FIX_bind_self_this && !Function.prototype.bind488 && !Function.prototype.bind588) { // window.m3bb = new Set(); // const smb = Symbol(); const vmb = 'dtz02' // Symbol(); // return kThis for thisArg const vmc = 'dtz04' // Symbol(); // whether it is proxied fn const vmd = 'dtz08' // Symbol(); // self fn proxy (fn--fn) // const fnProxySelf = function (...args) { // if (args[0] === smb) return this; // const cnt = kRef(this.ref); // if (cnt) { // if (typeof cnt[this.prop] !== 'function') console.error(`this.${this.prop} is not a function. [${cnt.is || 'nil'}]`) // return cnt[this.prop](...args); // might throw error // } // } // fnProxySelf.bind588 = fnProxySelf.bind; // const pFnHandler = { // get(target, prop){ // if(prop === 'bind588') return 2; // const fnThis = target(smb); // if (fnThis && fnThis.prop && fnThis.ref) { // const cnt = kRef(fnThis.ref || null) || null; // if (cnt) { // const h = cnt[fnThis.prop]; // const v = h[prop]; // if (typeof v === 'function'){ // if(typeof h === 'function'){ // if (prop === 'call' || prop === 'bind' || prop === 'bind588' || prop === 'bind488' || prop === 'apply') { // if(h.bind588 === 1){ // const g = function(...args){ // console.log(1288, this) // return h.call(this, ...args); // }; // console.log(399, g) // return g[prop]; // // console.log(12778) // // console.log(target, target.call) // // return target[prop]; // } // // independent of this // return v; // function.bind, function.call, function.apply // } // } // console.warn('cnt[fnThis.prop][prop] is function; might rely on this', { prop, fProp: fnThis.prop, is: cnt.is, h: h }); // // return new Proxy(fnProxySelf.bind588({ prop: prop, ref: new WeakRef(cnt[fnThis.prop]) }), pFnHandler); // } // return v; // } // } // }, // set(target, prop, value) { // const fnThis = target(smb); // if (fnThis && fnThis.prop && fnThis.ref) { // const cnt = kRef(fnThis.ref || null) || null; // if (cnt) { // const h = cnt[fnThis.prop]; // if (h) { // h[prop] = value; // } else { // console.log('h is nout found', { prop, fProp: fnThis.prop, is: cnt.is, h: h }); // } // } // } // return true; // } // }; const thisConversionFn = (thisArg) => { if (!thisArg) return null; const kThis = thisArg[vmb]; if (kThis) { const ref = kThis.ref; return (ref ? kRef(ref) : null) || null; } return thisArg; } const pFnHandler2 = { get(target, prop) { if (prop === vmc) return target; return Reflect.get(target, prop); }, apply(target, thisArg, argumentsList) { thisArg = thisConversionFn(thisArg); if (thisArg) return Reflect.apply(target, thisArg, argumentsList); } } const proxySelfHandler = { get(target, prop) { if(prop === vmb) return target; const ref = target.ref; const cnt = kRef(ref); if (!cnt) return; if (typeof cnt[prop] === 'function' && !cnt[prop][vmc] && !cnt[prop][vmb]) { if (!cnt[prop][vmd]) cnt[prop][vmd] = new Proxy(cnt[prop], pFnHandler2); return cnt[prop][vmd]; } return cnt[prop]; }, set(target, prop, value) { const cnt = kRef(target.ref); if (!cnt) return true; if(value && (value[vmc] || value[vmb])){ cnt[prop] = value[vmc] || thisConversionFn(value); return true; } cnt[prop] = value; return true; } }; const weakWrap = (thisArg) => { thisArg = thisConversionFn(thisArg); if (!thisArg) { console.error('thisArg is not found'); return null; } return new Proxy({ ref: mWeakRef(thisArg) }, proxySelfHandler); } if (!window.getComputedStyle533 && typeof window.getComputedStyle === 'function') { window.getComputedStyle533 = window.getComputedStyle; window.getComputedStyle = function (a, ...args) { a = thisConversionFn(a); if (a) { return getComputedStyle533(a, ...args); } return null; } } Function._count_bind_00 = 0; // Function._count_bind_01 = 0; // let matchNativeCode = (Object+""); // let matchNativeCode1 = matchNativeCode.includes("[native code]"); // let matchNativeLen = matchNativeCode.length - Object.name.length; // const matchConstructor = (thisArg) => { // const f = `${(thisArg || 0).constructor}`; // if (f.length > 45) return true; // if (matchNativeCode1 && f.length - thisArg.constructor.name.length === matchNativeLen) { // if (f.includes('[native code]')){ // return false; // } // return true; // } // return false; // } // const acceptThis = (thisArg)=>{ // // if(!thisArg || typeof thisArg !=='object') return false; // // // if((((thisArg||0).constructor||0).name || 'XXXXXXXX').length < 3) return true; // // if(typeof thisArg.path === 'string') return true; // // if(typeof thisArg.fn === 'function') return true; // // if(typeof thisArg.id === 'string') return true; // // if(typeof thisArg.isLoaded === 'boolean') return true; // return false; // } const patchFn = (fn) => { let s = `${fn}`; if (s.length < 11 || s.includes('\n')) return false; if(s.includes('bind(this')) return true; if(s.includes('=this') && /[,\s][a-zA-Z_][a-zA-Z0-9_]*=this[;,]/.test(s) ) return true; // var a=this; // f.bind(this) return false; } Function.prototype.bind488 = Function.prototype.bind; Function.prototype.bind = function(thisArg, ...args){ if (thisConversionFn(thisArg) !== thisArg) { return this.bind488(thisArg, ...args); } if( thisArg && patchFn(this) ){ // console.log(599,`${this}`) try { // let b1 = thisArg && typeof thisArg === 'object' && typeof thisArg.isAttached === 'boolean' && !thisArg.dtz06; // ready cnt // let b2 = !b1 && thisArg && (thisArg instanceof Node) && typeof thisArg.nodeName === 'string' && !thisArg.dtz06; // dom // let b3 = !b1 && !b2 && thisArg && typeof thisArg === 'object' && typeof thisArg.is === 'string' && !thisArg.dtz06; // init stage ? // // let b4 = !b1 && !b2 && !b3 && thisArg && typeof thisArg === 'object' && !thisArg.dtz06 && matchConstructor(thisArg); // // let b5 = !b1 && !b2 && !b3 && !b4 && thisArg && typeof thisArg === 'object' && !thisArg.dtz06 && acceptThis(thisArg); // // let b5 = !b1 && !b2 && !b3 && thisArg && typeof thisArg === 'object' && !thisArg.dtz06 && !(thisArg instanceof Window); // // let b4 = false; // let b4 = !b1 && !b2 && !b3 && thisArg && !thisArg.dtz06; // // b3 = false; // // b4 = false; // // b5 = false; // if (b1 || b2 || b3 ||b4 ) { const f = this; const ps = thisArg.__proxySelf0__ || (thisArg.__proxySelf0__ = weakWrap(thisArg)); if (ps && ps[vmb]) { Function._count_bind_00++; return f.bind488(ps, ...args) } // } } catch (e) { console.warn(e) } } return this.bind488(thisArg, ...args); } Function.prototype.bind588 = 1; } if (FIX_weakMap_weakRef && !window.WeakMapOriginal && typeof window.WeakMap === 'function' && typeof WeakRef === 'function') { const WeakMapOriginal = window.WeakMapOriginal = window.WeakMap; const wm6 = new WeakMapOriginal(); const skipW = new WeakSet(); window.WeakMap = class WeakMap extends WeakMapOriginal { constructor(iterable = undefined) { super(); if (iterable && iterable[Symbol.iterator]) { for (const entry of iterable) { entry && this.set(entry[0], entry[1]); } } } delete(a) { if (!this.has(a)) return false; super.delete(a); return true; } get(a) { const p = super.get(a); if (p && typeof p === 'object' && p.deref && !skipW.has(p)) { let v = kRef(p); if (!v) { super.delete(a); } return v || undefined; } return p; } has(a) { if (!super.has(a)) return false; const p = super.get(a); if (p && typeof p === 'object' && p.deref && !skipW.has(p)) { if (!kRef(p)) { super.delete(a); return false; } } return true; } set(a, b) { let wq = b; if (b && (typeof b === 'function' || typeof b === 'object')) { if (b.deref) { skipW.add(b); wq = b; } else { wq = wm6.get(b); if (!wq) { wq = mWeakRef(b); wm6.set(b, wq); } } } super.set(a, wq); return this; } } Object.defineProperty(window.WeakMap, Symbol.toStringTag, { configurable: true, enumerable: false, value: "WeakMap", writable: false }); } const isWatchPageURL = (url) => { url = url || location; return location.pathname === '/watch' || location.pathname.startsWith('/live/') }; const isCustomElementsProvided = typeof customElements !== "undefined" && typeof (customElements || 0).whenDefined === "function"; const promiseForCustomYtElementsReady = isCustomElementsProvided ? Promise.resolve(0) : new Promise((callback) => { const EVENT_KEY_ON_REGISTRY_READY = "ytI-ce-registry-created"; if (typeof customElements === 'undefined') { if (!('__CE_registry' in document)) { // https://github.com/webcomponents/polyfills/ Object.defineProperty(document, '__CE_registry', { get() { // return undefined }, set(nv) { if (typeof nv == 'object') { delete this.__CE_registry; this.__CE_registry = nv; this.dispatchEvent(new CustomEvent(EVENT_KEY_ON_REGISTRY_READY)); } return true; }, enumerable: false, configurable: true }) } let eventHandler = (evt) => { document.removeEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false); const f = callback; callback = null; eventHandler = null; f(); }; document.addEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false); } else { callback(); } }); const whenCEDefined = isCustomElementsProvided ? (nodeName) => customElements.whenDefined(nodeName) : (nodeName) => promiseForCustomYtElementsReady.then(() => customElements.whenDefined(nodeName)); FIX_perfNow && performance.timeOrigin > 9 && (() => { if (performance.now23 || performance.now16 || typeof Performance.prototype.now !== 'function') return; const f = performance.now23 = Performance.prototype.now; let k = 0; // 0 <= k < 9998m let u = 0; let s = ((performance.timeOrigin % 7) + 1) / 7 - 1e-2 / 7; // s > 0.14 // By definition, performance.now() is mono increasing. // Fixing in YouTube.com is required to ensure performance.now() is strictly increasing. performance.now = performance.now16 = function () { /** * Bug 1842437 - When attempting to go back on youtube.com, the content remains the same * * If consecutive session history entries had history.state.entryTime set to same value, * back button doesn't work as expected. The entryTime value is coming from performance.now() * and modifying its return value slightly to make sure two close consecutive calls don't * get the same result helped with resolving the issue. */ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1756970 // see https://bugzilla.mozilla.org/show_bug.cgi?id=1842437 const v = typeof (this || 0).now23 === 'function' ? this.now23() + s : f.call(performance) + s; // v > 0.14 if (u + 0.0015 < (u = v)) k = 0; // note: hRes should be accurate to 5 µs in isolated contexts else if (k < 0.001428) k += 1e-6 / 7; // M = 10000 * m; m * 9996 = 0.001428 else { // more than 9998 consecutive calls /** * * max no. of consecutive calls * * Sample Size: 4800 * Sample Avg = 1565.375 * Sample Median = 1469.5 * Sample Max = 5660 << 7500 << 9999 * * * */ k = 0; s += 1 / 7; } return v + k; // 0 < v - M < v - M + k < v } let loggerMsg = ''; if (`${performance.now()}` === `${performance.now()}`) { const msg1 = 'performance.now is modified but performance.now() is not strictly increasing.'; const msg2 = 'performance.now cannot be modified and performance.now() is not strictly increasing.'; loggerMsg = performance.now !== performance.now16 ? msg1 : msg2; // might not able to set in Firefox } loggerMsg && console.warn(loggerMsg); })(); FIX_removeChild && (() => { if (typeof Node.prototype.removeChild === 'function' && typeof Node.prototype.removeChild062 !== 'function') { Node.prototype.removeChild062 = Node.prototype.removeChild; Node.prototype.removeChild = function (child) { if (typeof this.__shady_native_removeChild !== 'function' || ((child instanceof Node) && child.parentNode === this)) { this.removeChild062(child); } else if ((child instanceof Element) && child.is === 'tp-yt-paper-tooltip') { // tooltip bug } else { console.warn('[yt-js-engine-tamer] Node is not removed from parent', { parent: this, child: child }) } return child; } } })(); FIX_VIDEO_PLAYER_MOUSEHOVER_EVENTS && !isChatRoomURL && (() => { const [setIntervalX0, clearIntervalX0] = [setInterval, clearInterval]; // let cid = 0; let mousemoveFn = null; let mousemoveDT = 0; let mousemoveCount = 0; // let qv = false; const cif = () => { if (!mousemoveFn) return; const ct = Date.now(); if (mousemoveDT + 1200 > ct) { // avoid setTimeout delay too long without execution mousemoveFn && mousemoveFn(); } mousemoveFn = null; }; let mousemoveCId = 0; let mouseoverFn = null; HTMLElement.prototype.addEventListener4882 = HTMLElement.prototype.addEventListener; HTMLElement.prototype.addEventListener = function (a, b, c) { if (this.id == 'movie_player' && `${a}`.startsWith('mouse') && c === undefined) { const bt = `${b}`; if (bt.length >= 61 && bt.length <= 71 && bt.startsWith('function(){try{return ') && bt.includes('.apply(this,arguments)}catch(')) { b[`__$$${a}$$1926__`] = true; this[`__$$${a}$$1937__`] = (this[`__$$${a}$$1937__`] || 0) + 1; if (this[`__$$${a}$$1937__`] > 1073741823) this[`__$$${a}$$1937__`] -= 536870911; // console.log(3928, a, this[`__$$${a}$$1937__`]) if (!this[`__$$${a}$$1938__`]) { this[`__$$${a}$$1938__`] = b; if (a === 'mousemove') { this.addEventListener4882('mouseenter', (evt) => { if (mousemoveCId) return; mousemoveCId = setIntervalX0(cif, 380); }); this.addEventListener4882('mouseleave', (evt) => { clearIntervalX0(mousemoveCId); mousemoveCId = 0; }); } this.addEventListener4882(a, (evt) => { const evt_ = evt; if (!this[`__$$${a}$$1937__`]) return; if (!this[`__$$${a}$$1938__`]) return; if (a === 'mousemove') { mouseoverFn && mouseoverFn(); if (mousemoveDT + 350 > (mousemoveDT = Date.now())) { (++mousemoveCount > 1e9) && (mousemoveCount = 9); } else { mousemoveCount = 0; } const f = mousemoveFn = () => { if (f !== mousemoveFn) return; mousemoveFn = null; this[`__$$${a}$$1938__`](evt_); }; if (mousemoveCount <= 1) mousemoveFn(); } else { if (a === 'mouseout' || a === 'mouseleave') { mousemoveFn = null; mousemoveDT = 0; mousemoveCount = 0; this[`__$$${a}$$1938__`](evt_); mouseoverFn && mouseoverFn(); } else { // mouseover, mouseenter mousemoveFn = null; mousemoveDT = 0; mousemoveCount = 0; mouseoverFn && mouseoverFn(); // just in case const f = mouseoverFn = () => { if (f !== mouseoverFn) return; mouseoverFn = null; this[`__$$${a}$$1938__`](evt_); } nextBrowserTick(mouseoverFn); } } }, c); return; } else { return; } } } return this.addEventListener4882(a, b, c) } HTMLElement.prototype.removeEventListener4882 = HTMLElement.prototype.removeEventListener; HTMLElement.prototype.removeEventListener = function (a, b, c) { if (this.id == 'movie_player' && `${a}`.startsWith('mouse') && c === undefined) { if (b[`__$$${a}$$1926__`]) { b[`__$$${a}$$1926__`] = false; if (this[`__$$${a}$$1937__`]) this[`__$$${a}$$1937__`] -= 1; // console.log(3929, a, this[`__$$${a}$$1937__`], b[`__$$${a}$$1926__`]) return; } } return this.removeEventListener4882(a, b, c) } })(); FIX_DOM_IF_REPEAT && (() => { // https://www.youtube.com/s/desktop/26a583e4/jsbin/live_chat_polymer.vflset/live_chat_polymer.js // DOM-IF is still a core class of Polymer, so no polymerController is available. // Be careful of the mixture of polymer functions and native Element functions // Be careful of the coding design is different with the modern Yt elements /* function Ks(a, b, c) { if (kj && !BOa(a)) throw Error("strictTemplatePolicy: template owner not trusted"); c = c || {}; if (a.__templatizeOwner) throw Error("A