Fix flash of unstyled content (FOUC) on page load - #833
Open
ankit wants to merge 1 commit into
Open
Conversation
The inject-css content script ran at document_start, but still had to message the background service worker and wait for it to read chrome.storage.local before it could insert the <style> tag. When the MV3 service worker was cold, waking it up added enough latency to cause a visible flash of unstyled content before styles applied. The content script now reads chrome.storage.local directly (handled by the browser process, not the service worker) and applies the existing URL-matching logic itself, removing the message round trip and the 300ms x 10 retry-polling fallback that was compensating for it. Extracted the pure getStylesForPage/BackgroundPageUtils matching logic into a new shared src/styles package so both the background page and the content script can use it. Moved badge-icon updates into the existing tabs.onUpdated listener since they used to piggyback on the now-removed message, and removed the GetStylesForIframe message type, which was only ever sent by the old content script.
ankit
force-pushed
the
worktree-css-fouc-fix
branch
from
September 2, 2026 22:23
b818076 to
f182dea
Compare
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.
Summary
Custom styles could briefly fail to apply on page load, causing a flash of unstyled content (FOUC) before Stylebot's CSS kicked in.
Root cause: the
inject-csscontent script runs atdocument_start, but it still had tochrome.runtime.sendMessagethe background service worker and wait for it to readchrome.storage.localbefore the<style>tag could be inserted. In Manifest V3, the service worker can be asleep between uses, and waking it up added enough latency to produce a visible flash — worse, the script only detected a failed lookup and retried every 300ms, up to 10 times, if the service worker wasn't ready in time.Fix: the content script now reads
chrome.storage.localdirectly and applies the existing URL-matching logic itself, entirely skipping the message round trip to the background page.chrome.storageis handled by the browser process, not the extension's service worker, so styles apply immediately regardless of whether the service worker is awake. The old retry-polling fallback is no longer needed and has been removed.Changes
src/inject-css/index.ts: readchrome.storage.localdirectly instead of messaging the background page; drop the retry-polling fallback.src/styles/(new): extracted the puregetStylesForPageURL-matching logic andBackgroundPageUtilsout ofsrc/background/into a shared package (@stylebot/styles) so both the background page and the content script can use it without the content script depending on background-only code.src/background/listeners.ts: badge/icon updates now happen in the existingchrome.tabs.onUpdatedlistener, since they used to piggyback on the message the content script no longer sends.GetStylesForIframemessage type/handler, which was only ever sent by the old content script and is now dead code.webpack.config.js: added a resolve alias for the new@stylebot/stylespackage.Test plan
npx jest— all 76 tests passnpx eslint --resolve-plugins-relative-to . src— no lint errors on touched filesyarn buildcompiles successfully