Handoff: Status hardening and real-YouTube UI fixes¶
Date¶
2026-07-12
Summary¶
A correctness and hardening batch across the in-player controls, the audio-mode artwork overlay, and the popup playback-status channel, driven by behavior seen on real YouTube (not just the hermetic fixtures). It closes a CPU-runaway and a reconciler hot loop in the content script, makes the per-tab status honest and hard to poison, collapses a double <video>.src write on the circuit breaker, and fixes the audio-mode artwork collapsing to a black rectangle on the real player layout. No hard invariant changed: logged-out only, credentialless ANDROID_VR, PlayerHandle as the sole <video>.src writer, fail-open to native playback, and the page-world trust boundary all hold.
Key changes¶
CPU-runaway coalescing + reconciler hot-loop (entrypoints/content.ts)¶
- The two in-player
MutationObservers (the player-subtree observer and the document observer that watches for player replacement) now feed rAF-coalesced schedulers (createCoalescedFrameScheduler), so a burst of YouTube mutations does at most one reconcile and one player-relookup per frame instead of one per record. Both observers disconnect entirely while the tab is hidden (visibilitychange/pagehide/pageshow), and the reconcile body no-ops when the observers are inactive. reconcileInPlayerControlsno longer anchors itsinsertBeforeon a node it is about to move. The insertion anchor resolves to the native settings gear, else the first native (non-managed) control, else null (append). Anchoring on the status region or one of our own buttons madeinsertBeforethrow once that node was detached into the fragment, and under the observer that throw became a reconcile -> throw -> mutation hot loop whenever the settings gear was absent.
Honest per-tab status (entrypoints/content.ts, entrypoints/background.ts, src/shared/status.ts, entrypoints/main-world.ts)¶
- Active-only-on-attach: the page world emits
activeonly afterPlayerHandle.attach()succeeds; every unsupported or failed path emitsfallback(with a reason) ordisabled, so the stored status never claims audio-only on a video that fell back. - Content-owned ordering (unforgeable): the
yta:statusDOM event is observable and forgeable by arbitrary page JS, so its ordering provenance is generated in the isolated content script and never trusted from the event.generationis a content-owned counter that begins a new value each time the forwardedvideoIdchanges (a same-operationfetching→activekeeps one generation; a new video supersedes the old), and it ignores any generation the event carries — so a hostile page cannot forge a huge generation to freeze the popup. Only the display fields (status,reason,videoId) come from the event; a forgedvideoIdis caught by the resolver's URL cross-check, and a momentary forgedstatusis superseded by the next genuine report. (An earlier nonce-stamping attempt was dropped: the nonce rides in the same page-observable event, so a page could read it from a legitimate event and replay it.) - Monotonic, poison-resistant
runStart:nextStatusRunStart()replacesDate.now(). It returnsmax(Date.now(), previous + 1)over the last epoch persisted in per-tabsessionStorage['__yta_run_epoch__'], so a full reload is always strictly later even across a same-millisecond collision or a system-clock rollback. The stored value is validated (a safe non-negative integer no more than a day pastnow) so the origin-shared page cannot freeze ordering with a poisoned value such as1e308; it falls back toDate.now()whensessionStoragethrows. - Conditional staleness + strict revive:
background.ts:tabs.onUpdatedmarks an entry stale only when the newshouldMarkStale(entry, changeInfo)agrees, i.e. aloadingreload or a URL whose video id differs from the entry's. A same-video URL rewrite (YouTube appending&t=/listparams during playback) no longer stales, which previously stranded the popup onconnecting.supersedesis strict ongenerationwhile an entry is stale (an equal-tuple straggler from the unloaded old document cannot clear the stale flag) and non-strict otherwise (a same-operationfetching->activetransition still lands). - Popup pause-gate:
popup/App.tsx:PlaybackHeronever rendersactivewhen the audio-only preference is off or the extension is paused, since both flip their signal immediately, before the page world'sdisabledstatus round-trips back.
Circuit-breaker single-write (src/shared/player.ts)¶
- When the dormant
srcguard trips the breaker (a third-party write exceedsmaxReassertions),openCircuit({ skipSrc: true })restores the originalsrcdescriptor without writing the snapshot back, and the guarded setter then performs the page's own write once. This avoids the double write (extension snapshot, then the page value) that could fight native playback on teardown.
Audio artwork real-YouTube mount (src/shared/artwork.ts)¶
- The overlay now mounts into the player root (
.html5-video-player/#movie_player) and is inserted directly after the video container, so it paints above the video but below the control chrome. It is no longer appended as the media container's last child. On real YouTube the<video>'s immediate.html5-video-containerwrapper is a zero-height positioning box, so aninset:0overlay mounted there collapsed to nothing, which was the audio-mode black rectangle. It falls back to the<video>'s direct parent on other layouts (fixtures). The mount is not gated on a non-zeroclientHeight, because the wrapper is often zero-height at attach time andinset:0on the player root resolves once layout settles.
Native in-player control styling (entrypoints/content.ts)¶
- The audio-only glyph is a headphones icon rendered white (the previous teal fill is gone; teal survives only on the focus-visible outline and the active state). Buttons use a padded SVG viewBox
-12 -12 48 48with a full-size SVG so the glyph sits at native icon weight (~24px in a 48px button) and scales cleanly in theater and fullscreen, where YouTube enlarges its controls. Verified on real YouTube in default and theater layouts.
Artwork localhost DCE (src/shared/artwork.ts)¶
- The bench-only
http://127.0.0.1/http://localhostthumbnail allowance inisSafeArtworkUrlis now__BENCH__-gated, so a production build only ever loadshttps:artwork and the fixture host strings are dead-code-eliminated. This mirrors the same guard inplayer.ts:isSafeMediaUrl.
Docs updated¶
docs/specs/SPEC-012-holistic-ux-foundations.md: corrected the status-channel section (content-owned generation ordering, monotonic/poison-resistantrunStart, display-only event trust boundary), documentedshouldMarkStaleand the strict-on-stale supersede, corrected thetabs.onUpdatedbehavior, and described the artwork player-root mount plus the__BENCH__-gated thumbnail allowance.docs/adrs/0007-popup-playback-status-delivery.md: rewrote the SPA-staleness and trust-boundary reasoning to match the shipped code (content-owned, videoId-driven generation — not trusted from the page-observable event; conditionalshouldMarkStale; thenextStatusRunStartepoch).docs/adrs/0008-audio-mode-artwork-and-egress.md: corrected the overlay mount from "the media container's last child" to the player-root / after-video-container mount, with the zero-height black-rectangle explanation.
Verification¶
Covered by the existing gate (npm run validate: typecheck, lint, format:check, unit + coverage, build MV2, web-ext lint, build MV3). The relevant unit coverage for this batch:
tests/unit/status.test.ts:shouldMarkStale(loading reload, different-video stale, same-video kept live) and the stale-strictreduceStatusUpdate/supersedespaths.tests/unit/in-player-mount.test.ts:nextStatusRunStartmonotonicity and poisoned-epoch recovery, the rAF-coalesced reconcile scheduler, the gear-absent "noinsertBeforehot loop" stability case, andbuildStatusUpdateMessageignoring any page-supplied generation.tests/unit/artwork.test.ts: the real-YouTube player-root / after-video mount versus the fixture last-child fallback.
Follow-ups still open¶
- Adblock parser gap (task #69):
background.ts:filterPlayerResponseprunes ads from the bufferedPOST /youtubei/v1/playerresponse body, but the page world's ownfetch+Response.json()download path (main-world.ts:handleDownloadRequest,activateEnhancements) parses the player response without the same ad-field pruning. Reconcile the two so both paths see a consistently pruned response. - Options UI polish (task #72): raise the equalizer-band control contrast and fix the diagnostics panel scrollbars in the options surface.
- ANDROID_VR ad-free-video finding: the credentialless
ANDROID_VRplayer response is ad-free and returns an itag-18 progressive stream plus adaptive video formats. That gives a possible future path to ad-free normal (video) playback on fallback-eligible videos, not just audio-only. Recorded as a finding to evaluate against the logged-out and credentialless invariants before any use.