Skip to content

feat(favicons): site icons on the tabs and the new tab, kept inside the app - #100

Merged
lippdev merged 1 commit into
mainfrom
feat/favicons
Oct 21, 2026
Merged

lippdev merged 1 commit into
mainfrom
feat/favicons

Conversation

@lippdev

@lippdev lippdev commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Tabs show their site's icon. Tabs downloads the icon the page announced (or /favicon.ico when it announces none) through the tab's own session, keeps it per host in the profile (favicons/, at most 500, oldest dropped) and points the tab at the kept copy. Navigating no longer leaves the previous page's icon on the tab: it shows the new host's saved icon, or the globe.
  • The new tab uses them. Tiles draw the kept icon on a light circle (GitHub's is black), with the letter as the fallback. Favorites the app never saw load get their /favicon.ico fetched once, in the background (a failure is retried after 10 minutes, not forgotten). A site is one tile: a recent page of a site already listed is left out.
  • Private tabs keep their icon in memory only, never in the profile. "Clear cookies, cache and history" also clears the icons.
  • Not a history oracle. A kept icon says the site was visited. The request from a web page looks exactly like the shell's (no referrer or origin), so joychromium://favicon needs a random per-run key that only the shell and the internal pages receive. The reply is sandboxed and nosniff, and only real image bytes are kept (sniffed, not trusted from the header, ≤ 128 KB).

Bug found on the way (existed on main)

Opening a private tab threw Attempted to register a second handler for '@ghostery/adblocker/inject-cosmetic-filters' when the block lists were loaded, so a private tab could not be opened at all. The blocker registers two global ipcMain handlers per session; AdBlock.enableIn now drops the earlier ones first (also used when the engine is swapped). Covered by the private-tab test in favicons.spec.ts, which only exercises it when the lists are available (network or cache), so not on every CI run.

Test plan

  • npm run typecheck
  • npm test (113 passed): the store, the safe reading of an icon, the file limit and the private mode
  • npm run test:e2e (53 passed, 3 skipped): tab icon and its file, no stale icon, page cannot read it with no key or a wrong one, /favicon.ico fallback, private tab, new tab tiles with background fetch and no repeated site
  • Real sites on Windows (Wikipedia, GitHub, Mozilla in tabs; YouTube, Netflix, Twitch fetched for the new tab)

🤖 Generated with Claude Code

…he app

- A tab downloads the icon its page announced (or /favicon.ico) through its
  own session, keeps it per host in the profile (favicons/, at most 500)
  and points the tab at that copy. The icon of the page being left no
  longer stays on the next one: it shows the new host's saved icon or the
  globe. A private tab keeps its icon in memory only and never writes to
  the profile.
- The new tab draws those icons on its tiles (the letter stays as the
  fallback), on a light circle so dark icons stay visible, and asks once,
  in the background, for the icons of favorites it never saw load. A site
  is one tile: a recent page of a site already listed is left out.
- joychromium://favicon is served only with a per-run key that the shell
  and the internal pages are handed, so a web page cannot probe which sites
  were visited; the reply is sandboxed and not sniffed. Clearing browsing
  data clears the icons too.
- Fix: opening a private tab threw "Attempted to register a second
  handler" when the block lists were loaded, because the ad blocker
  registers global ipcMain handlers once per session. A private tab could
  not be opened at all.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@lippdev
lippdev merged commit 0672aa2 into main Oct 21, 2026
5 checks passed
@lippdev
lippdev deleted the feat/favicons branch October 21, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant