fix(player): stop the page URL being stored as track artwork - #447
Merged
Conversation
`img.src` resolves against the document base URL, so the player-bar image reports `https://music.youtube.com/` whenever its `src` attribute is empty — which happens while YouTube Music swaps tracks. That value is truthy, so the existing `getAttribute('src')` fallback never ran, and it was written straight into `currentTrack.thumbnailURL`. The bad URL fetches successfully as HTML and then fails to decode, so surfaces without a fallback show a permanent loading state. `SongThumbnailView` masked it by falling back to the public video thumbnail. Gate on the literal attribute before trusting the resolved value, and reject non-image URLs at the bridge ingestion point so nothing downstream can store one. Callers already treat an empty string as "keep existing artwork".
18 tasks
httperry
added a commit
to httperry/kaset
that referenced
this pull request
Aug 22, 2026
httperry
added a commit
to httperry/kaset
that referenced
this pull request
Aug 22, 2026
Yoddikko
pushed a commit
to Yoddikko/kasetPlus
that referenced
this pull request
Aug 27, 2026
Yoddikko
added a commit
to Yoddikko/kasetPlus
that referenced
this pull request
Aug 27, 2026
…zercan#448 Cherry-picked from sozercan/kaset: - sozercan#426 Bengali romanizer mixed-script crash fix - sozercan#425 refresh Home suggestions on demand (adapted: kept fork shorts aggregation + chip-bar callers pass forceRefresh:false) - sozercan#447 stop page URL stored as track artwork - sozercan#448 eliminate cross-suite unit test flakes (WebKit cookie handling) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012LaRrBbQsc5W5agXFkYc7u
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
currentTrack.thumbnailURLis sometimes set tohttps://music.youtube.com/— the app's own page URL — instead of artwork.The observer script read the player-bar image with
thumbEl.src. That is the IDL property, which resolves against the document base URL, so an<img>whosesrcattribute is empty or missing reports the page URL rather than an empty string. YouTube Music leaves the attribute momentarily empty while swapping tracks. Because the resolved value is a non-empty (truthy) string, the existingthumbEl.getAttribute('src')fallback never ran:The value was then written straight into the track model with no validation:
The result is a URL that fetches successfully — HTTP 200, ~2 KB of HTML — and then fails to decode as an image. Any surface that renders it without a fallback sits on its loading placeholder forever, because
CachedAsyncImagehas no error branch and simply keeps showingplaceholder()when the decode returns nil.This has been masked so far:
SongThumbnailViewrecovers by falling back toi.ytimg.com/vi/<id>/hqdefault.jpgvia itsonFailurehook, which covers most thumbnails in the app.Found while testing #440, whose artwork viewer has no such fallback and so spins indefinitely. Fixed here separately because the bad data is written on
maintoday and affects every consumer ofcurrentTrack.thumbnailURL.How it was diagnosed
Temporary instrumentation in the artwork viewer logged the URL plus an independent
GETof it to the sandbox tmp directory:The instrumentation has been removed.
AI Prompt (Optional)
🤖 AI Prompt Used
AI Tool: Claude
Type of Change
Related Issues
Relates to #440
Changes Made
SingletonPlayerWebView+ObserverScript.swift: Gate on the literalsrcattribute before trusting the resolvedimg.src, so an unset image reports''instead of the document URL.MiniPlayerWebView+Coordinator.swift: AddedplaybackBridgeThumbnailURLString(from:)and applied it at the single bridge ingestion point. Rejects empty, non-http(s), hostless, and bare-/values so no non-image URL can reach the track model, whatever the DOM reports.AutoplayRecoveryTests.swift: Coverage for the document URL, empty/whitespace/nil/non-string inputs, and pass-through of real artwork andi.ytimg.comfallback URLs.Rejected input yields an empty string, which every existing caller already treats as "keep current artwork" —
keepQueueSongVisibleusesURL(string:) ?? song.thumbnailURL, andupdateTrackMetadataguardslet thumbnailURLbefore applying it or falls back to?? currentTrack.thumbnailURL. No call site needed changing.Testing
swift test --skip KasetUITests) — 2959 tests in 232 suitesVerified in a packaged build: play a track, change tracks, and confirm artwork-dependent surfaces keep real artwork instead of the page URL.
PlayerServiceQueueTestsfailed once during a full-suite run here, then passed 3/3 in isolation and 2/2 on subsequent full-suite runs, with a different subset of tests failing each time. This is the pre-existing parallel-execution flakiness in persisted state (same family as theFavoritesManagerTests/SettingsManagerTests/HistoryViewModelTestsflakes seen on other branches); nothing here touches playback persistence.Checklist
swiftlint --strict && swiftformat .— both cleanAdditional Notes
The JS change keeps using
thumbEl.srcfor the resolved absolute URL when the attribute is genuinely present, so relative artwork sources still resolve correctly. Only the empty/missing case changes behaviour.The Swift guard is deliberate defence in depth: the DOM is third-party and can change shape without notice, and the failure mode here is a permanent loading state rather than a visible error, which makes it expensive to notice.