From 122c1378d69c7ff7d034d75a7630a38112f627e4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 22 Jun 2026 18:33:24 +0200 Subject: [PATCH] fix(adblock): harden isInlinePlaybackNoAd against property locker YouTube's server-side SABR "backoff" stalls playback after a few seconds when ads are stripped. The mitigation sets `playbackContext.contentPlaybackContext.isInlinePlaybackNoAd = true` so InnerTube serves no ads (and thus no backoff). YouTube ships a "locker" script that defines this property as non-writable/non-configurable via Object.defineProperty, so the previous in-place assignment silently failed and playback aborted with an error. Instead of mutating YouTube's object, rebuild the holder chain with fresh plain objects. JSON.stringify only serializes own enumerable properties, so spreading reproduces what would be serialized while dropping the locked descriptors, letting the flag stick. Fixes #457 Co-Authored-By: Claude Opus 4.8 --- src/hooks/json-stringify.ts | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/hooks/json-stringify.ts b/src/hooks/json-stringify.ts index 1919aec5..bce3705a 100644 --- a/src/hooks/json-stringify.ts +++ b/src/hooks/json-stringify.ts @@ -18,10 +18,33 @@ function stringify( // TODO: add below to a dump-level logger // console.debug('JSON.stringify', value, replacer, space); - // @ts-expect-error TS doesn't allow optional chaining on `unknown`. See: https://github.com/microsoft/TypeScript/issues/37700 - const ctx = value?.playbackContext?.contentPlaybackContext as unknown; - if (!isPrimitive(ctx)) { - (ctx as Record).isInlinePlaybackNoAd = true; + const holder = value as Record; + const pbCtx = holder.playbackContext as Record | undefined; + const ctx = pbCtx?.contentPlaybackContext as + | Record + | undefined; + + if (!isPrimitive(ctx) && ctx!.isInlinePlaybackNoAd !== true) { + // Setting `isInlinePlaybackNoAd` tells InnerTube not to serve ads, which + // avoids the server-side SABR "backoff" that otherwise stalls playback + // after a few seconds. YouTube has shipped a "locker" script that defines + // this property as non-writable/non-configurable via Object.defineProperty, + // so a direct assignment (`ctx.isInlinePlaybackNoAd = true`) silently fails. + // + // Instead of mutating YouTube's object in place, rebuild the holder chain + // with fresh plain objects. `JSON.stringify` only serializes own enumerable + // properties, so spreading reproduces exactly what would be serialized while + // dropping any locked property descriptors, letting our flag stick. + value = { + ...holder, + playbackContext: { + ...pbCtx, + contentPlaybackContext: { + ...ctx, + isInlinePlaybackNoAd: true + } + } + }; console.info(`[JSON.stringify] Set isInlinePlaybackNoAd`); } }