Releases: ml3dev/drowzy
v1.3.5: 15-min default timer, whitelist UX overhaul, audit fixes
Fixed
- Whitelist remove button replaced with a visible "Remove ×" pill (was an icon-only X users reported missing entirely). Optimistic fade on click +
_whitelistSigreset to defeat a race with the pollingloadAllthat could otherwise short-circuit the re-render. - "Never suspend this site" toggle flips optimistically + confirmation toast (
Added X to whitelist/Removed X from whitelist). New i18n keys:addedToWhitelist,removedFromWhitelist. - Whitelist text-input add now toasts to match: success or
X is already in whiteliston duplicate. New i18n key:alreadyWhitelisted. renderShortcutsStatusnow re-polls on eachloadAllso the Settings → Keyboard shortcuts row updates without a popup reopen if the user edits bindings inchrome://extensions/shortcutsvia the Customize link. Previously it ran once at popup init only.- "Suspend Others" silently skipped tabs in Chrome's
loadingstate.suspendTab's pre-discard recheck bailed onfresh.status === 'loading'for every path. SPAs like Reddit sit inloadingindefinitely from background streaming, so the manual action did nothing on those tabs. Check is now insideif (opts && opts.auto)— manual paths bypass it. - Welcome page showed bogus shortcut hints for commands Chrome couldn't auto-bind.
onboarding.jsnow hides the<li>entirely for unbound commands; bound commands swap the<kbd>for the realcmd.shortcut. closeDuplicatescould close a live tab and keep a discarded shell. Pick order is now pinned → active → live → first; the live-tab tiebreaker is new.- Popup whitelist toggle no-op'd on IP-literal current tabs (
http://[::1]/,http://127.0.0.1:3000/). Gate now matchesbackground.addWhitelist's accept set (IPv4 dotted-quads +::1). addWhitelistskipped the^www\.strip on whitespace-prefixed input..trim()runs before the regex now. Read-time normalization ingetSettingshad been masking it.- Share Stats clipboard text was hardcoded English. All five strings now go through
chrome.i18n.getMessage(newshareStats*keys with$COUNT$/$RAM$/$DATE$placeholders). - Share Stats used hardcoded
150instead ofMB_PER_TAB. Missed spot from 1.3.4's constant cleanup.
Changed
- Default auto-suspend timer: 30 min → 15 min. Industry median is 10–15 min (Auto Tab Discard 10, Tab Wrangler 20, Chrome Memory Saver 2 hrs and panned for it). 30 was on the long end; 1.3.3 added the first-run quick-suspend pass specifically because users didn't notice memory savings at 30. Drowzy's protections (active/pinned/audio/forms/warning banner) let it run more aggressive than competitors. Existing users keep their setting via
chrome.storage.sync. UpdatedfeatureAutoSuspend+onboardingTimerTipacross all 57 locales (substring 30→15 inside those two keys only). - Windows
suggested_keydefaults rotated to less-claimed combos:suspend-currentAlt+Shift+Z→Alt+Shift+P,wake-allAlt+Shift+W→Alt+Shift+O. Old combos collided too often (Zoom, screen recorders, Office). New installers only — Chrome doesn't retrysuggested_keyfor existing installs.suspend-othersstays onAlt+Shift+S.
Added
-
Whitelisted-sites label now shows a count (
Whitelisted sites (5)) so the list reads as bounded; it already scrolled atmax-height: 140px. -
Settings → "Keyboard shortcuts" row. Shows bind status from
chrome.commands.getAll()("All set" or "$BOUND$ of$TOTAL$ set") and a Customize link tochrome://extensions/shortcuts. Persistent path to the shortcuts editor; welcome page was the only entry before and it only shows on install. New i18n keys:keyboardShortcuts,customizeShortcuts,shortcutsAllSet,shortcutsSomeUnset. Chrome has no programmatic shortcut-set API (chrome.commandsexposes onlygetAll()), so the editor is the only path.
Polish
- Whitelisted badge icon:
star→shield. Star reads as bookmark/favorite; shield matches the rest of the whitelist UI (the "Never suspend this site" toggle, the empty-whitelist state, the popup button), where shield/shieldCheck already mean "protected from suspension." Gold badge color kept so the row still visually distinguishes from blue/green/amber neighbors. - "Today" stat icon:
moon→calendarDays. Moon is the right icon for sleeping tabs and dark-mode (both = night) but reading "today" off a crescent doesn't connect. Calendar icon makes the stat readable at a glance. Added Lucide'scalendar-daysSVG toicons.js. fmtRamstrips trailing.0for whole-GB values ("2 GB" not "2.0 GB"). Affects stats strip, hero card, suspend toast, share text.- Wake-tab click from popup focuses the destination window via
chrome.windows.update({focused: true})so a click on a tab in another window actually takes you there. - Per-tab suspend button has
aria-labelmatching itstitle. - Toolbar action-badge color changed
#6C63FF→#7c3aedto match Drowzy's brand accent (it was a slightly-off indigo before). Also explicitly set the badge text color to white viachrome.action.setBadgeTextColor(feature-detected — Chrome 110+; older browsers fall back to Chrome's default auto-contrast). - Welcome page CTA rewritten from "Drowzy is already running. Open a few tabs and try it out!" (misleading with 15-min default) to "Drowzy is now running. Open it from your toolbar and hit 'Suspend Others' to try it out." — points at a button that gives instant feedback. Updated
onboardingCtainen/en_US/en_GB+ HTML fallback; 54 translated locales keep their existing copy until translators update. - Popup
max-heightraised 520 → 580px +html { overflow: hidden }added. Old 520 surfaced a near-useless ~20px scroll range in popups with 3+ visible tabs. 600 (Chrome's nominal max) triggered a double scrollbar on some displays because actual usable popup height is closer to 580 in practice. Sidepanel unaffected (overrides withmax-height: none).
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: 585f6b37e13423982b039a1f0f20a4def69dae68f659952e9bec1af4d3d5134c
You can reproduce the build yourself by checking out v1.3.5 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.3.4: full audit pass, suspension reliability fixes, polish
Small quality update from a full audit pass: bug fixes, polish, accessibility, and one minor code-quality cleanup. No new permissions, no new strings, no schema changes.
Fixed
- Startup suspend pass could be skipped when the service worker died before the 5s timer fired.
onStartupscheduledsuspendAllOnStartupviasetTimeout(..., 5000). OnceonStartup's awaits resolve, the service worker has no pending events keeping it alive; Chrome can terminate it after ~30s of idle, and a setTimeout doesn't count as keepalive. On a cold browser launch where the worker has no other work, the 5s deadline was usually hit, but not guaranteed. Replaced withchrome.alarms.create('startup-suspend', { delayInMinutes: 0.1 })which Chrome clamps to ~30s in production and survives a worker restart. TheonAlarmhandler now dispatches thestartup-suspendalarm tosuspendAllOnStartup(after re-fetching settings, in case the user toggled "Suspend on startup" off between the schedule and the fire). handleSuspendCurrentfell back tocandidates[length - 1]when no next tab existed, which could land far from the active tab (the last candidate in the filtered list, not necessarily the previous tab). Now it explicitly looks for the closest previous tab (largestindexbelowactiveTab.index), then falls back to any candidate only if both next and previous are absent. Triggered when you suspend the active tab viaAlt+S/ context menu while it's the rightmost tab in a window.
Changed
MB_PER_TABis now defined once per file rather than scattered as a magic150in three places inpopup.js. The constant must stay in sync withbackground.js'sMB_PER_TAB; comment inpopup.jscalls this out. No user-visible change; defensive cleanup so a future tuning is a one-line edit.
Polish
prefers-reduced-motionhonored on the onboarding page. The popup, side panel, changelog, and privacy-policy pages all already had the media query that flattens animations and transitions to 1ms;onboarding.htmlwas the only page that didn't, so a user with reduced-motion enabled would still see the 0.4s fade-up of the welcome container. Added the same* { animation-duration: 1ms !important; ... }block toonboarding.html's inline style.- Whitelist input HTML placeholder synced with the i18n value. The 1.3.3 release updated
en/messages.json#whitelistPlaceholderto"example.com, full URL, or site.com/path/*"but the staticplaceholder=attribute inpopup.htmlandsidepanel.htmlwas still"example.com or site.com/path/*". Localization overwrites it at popup-open, so users normally see the new wording, but a failed i18n pass (extension reload race, unusual locale) would leave the old text visible. HTML defaults now match en exactly.
Audit
- Full read-through of all source files.
background.js,popup.js/html/css,sidepanel.html,formcheck.js,icons.js,onboarding.html/js,changelog.html/js,privacy-policy.html/js. No additional bugs found beyond the two fixed above; the 1.3.2 / 1.3.3 polish work held up. - All 57 locale files validated for JSON validity, key parity against
en(175 keys, no missing/extra in any locale), and placeholder structure (every$COUNT$/$RAM$/$VERSION$/$DATE$substitution token present inenis also present in the corresponding translated value). No locale repairs needed. - i18n key reference scan confirms every
data-i18n*attribute andchrome.i18n.getMessage/t(...)call resolves to a real key, and every key inen/messages.jsonis reachable from source (including the dynamic ones referenced via variable interpolation:t(statusKey)for the current-tab status andBADGES[reason].labelKeyfor the protect-reason badges). - Chrome MV3 best practices verified for the suspend pipeline:
chrome.tabs.discard()remains the canonical API;tab.lastAccessed(Chrome 121+) is feature-detected withtypeof === 'number'and gracefully falls back on 120;chrome.storage.sessioncorrectly used for the in-memorytabTimestampscache that survives worker restarts via re-seeding fromtab.lastAccessed;chrome.alarmsminimum delay (0.5 min in production) respected. The known quirk thattab.lastAccessedbecomesundefinedfor discarded tabs is handled correctly: every code path that reads it either guards with the typeof check or has already early-returned ontab.discarded.
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: ad5e3179f250108483b27ba5781c79bbbaa506d3726e3601ecb75842be0f677f
You can reproduce the build yourself by checking out v1.3.4 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.3.3: first-run quick-suspend, Keep awake banner, tab.lastAccessed awareness
Added
- First-run quick-suspend pass (
firstRunQuickSuspendinbackground.js). 30s after install, Drowzy suspends tabs that Chrome reports as idle for 10+ minutes viatab.lastAccessed(Chrome 121+; skipped on 120). Without this, a fresh installer waited the full 30-minute timer before any visible effect — the most common reason cited for "didn't notice a memory difference" in uninstall feedback. Conservative threshold (10 min via Chrome's own tracking) avoids surprising a user with a recently-used tab going away. Honors all the usual protections (pinned, audio, whitelist, internal pages). tab.lastAccessedhonored in suspend decisions (shouldSuspendand the final recheck insidesuspendTab). Drowzy now takes the more recent of its own activation timestamp and Chrome'stab.lastAccessed(Chrome 121+) when deciding whether a tab is past the idle threshold. Fixes a class of "tab I just used got suspended" caused by service-worker restarts: when the worker came up cold, Drowzy's in-memory_timestampswere re-seeded toDate.now()for missing tabs, buttab.lastAccessedhad the truer "last focused" timestamp. Feature-detected so 120 still works.- "Keep awake" button on the suspend warning banner (
formcheck.js). The banner used to be a click-to-dismiss strip with no way to actually stop the imminent discard — the only recourse was to whitelist the site after the fact. The banner now has an explicit "Keep awake" button that sends akeepTabAwakemessage to the background; the background bumps the tab's timestamp viatouchTabandsuspendTab's pre-discard recheck reads the bumped timestamp and bails out. New i18n key:keepAwakeBtn. keepTabAwakeaction allowed from content-script senders inbackground.js#onMessage. The unauthorized-content-script guard still blocks every other action, butkeepTabAwakeis whitelisted because the suspend-warning banner runs in page context. Scoped to the sender's own tab id so a content script can't bump arbitrary tabs.- Pre-discard timestamp recheck inside
suspendTab. After the form-check + message round-trips, before callingchrome.tabs.discard, the function now re-reads_timestamps[tabId]andtab.lastAccessedand aborts if either is now within the threshold. Closes the race where the user hits Keep awake during the 2500ms warning window or refocuses the tab via a different code path. - Suspend-timer hint tooltip on the Settings row (
data-i18n-title="suspendTimerHint"inpopup.htmlandsidepanel.html). New users with workflows where they come back to a tab every ~35 min had no hint that 30 was tunable; the tooltip points them at it. New i18n key:suspendTimerHint. - Two new onboarding tip cards addressing the actual top uninstall reasons: "Suspending tabs you still want?" points at the timer + whitelist as fixes for over-aggressive suspends, and "Filling out a form?" points at the
protectFormssetting. Both ship asdata-i18n-htmlso the embedded<strong>survives translation. New i18n keys:onboardingTimerTip,onboardingFormsTip. - Windows-specific keyboard shortcut defaults to avoid Microsoft Office Web access-key collisions:
suspend-currenton Windows is nowAlt+Shift+Z(wasAlt+S). On Windows,Alt+Sis the access key for Send in Outlook Web, Teams, OWA, and most Microsoft 365 surfaces. A user composing an email who hitAlt+Sto send would instead suspend the compose tab — silently, with potential draft loss. macOS/Linux/ChromeOS keepAlt+Ssince those platforms don't have the conflict (Mac Outlook usesCmd+Enter).wake-allon Windows is nowAlt+Shift+W(wasAlt+W).Alt+Wis the access key for the View ribbon in Word, Excel, and PowerPoint Web. Same per-platform override pattern.suspend-othersstays atAlt+Shift+Severywhere — no known conflict.- Existing users who customized their shortcuts via
chrome://extensions/shortcutsare unaffected; only new installs and unmodified bindings pick up the new defaults.
- Dynamic shortcut display in the onboarding page. The
Press <kbd>Alt+S</kbd> ...lines now querychrome.commands.getAll()after i18n localization runs and replace each<kbd>'s text with the user's actual binding — so Windows users seeAlt+Shift+Z/Alt+Shift+Wand any user remap is reflected without re-translating 57 locale files. If a command is unbound, the translated default is left alone. - Forecast stat in the stats strip — adaptive third slot. The third memory slot now adapts based on state to keep the strip at three items (a fourth would feel cramped). When Drowzy has actually freed memory, it shows that ("saved", blue dot,
ramEstimateTooltip). Before any tabs have been suspended but eligible tabs exist, it shows the forecast instead ("available", amber dot,ramForecastTooltip) — e.g.~3.4 GB available. First-session users no longer see "— saved" with no signal anything will happen; they see a real number immediately, addressing the most common uninstall reason ("didn't notice a memory difference"). When neither applies, the slot shows an em-dash with the default "saved" label. Computed ingetStatusas(allTabs - protected - discarded) × MB_PER_TABand exposed aseligibleCount+estimatedMbForecast. statForecastandramForecastTooltipi18n keys inen/messages.jsonfor the new strip item label and tooltip.- Quantified toast feedback for Suspend Others and Wake All. Pre-1.3.3 the buttons flashed
Suspending... → Done → Suspend Othersregardless of whether anything actually happened — a window with zero eligible tabs would still show "Done" and the user would close the popup confused. Now:suspendAllOthersandunsuspendAllboth return their actual count, the popup handlers show"Suspended N tab(s) · ~M MB freed"/"Woke N tab(s)"toasts on success, and"No other tabs to suspend"/"No suspended tabs to wake"toasts when the action would have been a no-op. Removed the now-redundantDonetext-flip and the 400ms timeout since the toast carries the confirmation. New i18n keys:suspendedToast,wokeToast,noOthersToSuspend,noSuspendedToWake. - Smarter current-tab status line. The status under the active tab's domain (e.g.
"Active — won't be suspended") used to be hardcoded regardless of why the tab was protected. Now picks the actual reason in priority order:systemPageCantSuspendforchrome://and other non-http schemes,pinnedWontSuspendwhen the user has Protect Pinned on and the tab is pinned,audioWontSuspendwhen Protect Audio is on and the tab is audible,whitelistedWontSuspendwhen the tab's host matches the whitelist, falling back to the existingactiveWontSuspend. The whitelist button below already updated correctly; only the status text was stale. New i18n keys:pinnedWontSuspend,audioWontSuspend,whitelistedWontSuspend,systemPageCantSuspend. - Onboarding now includes a pin-to-toolbar tip and a one-click link to remap shortcuts. Two of the most common first-hour confusions: "where is the icon?" and "I don't like the default keys." A subtle accent-bordered tip card sits above the CTA with a tip about pinning Drowzy via the puzzle icon, plus a link that opens
chrome://extensions/shortcutsdirectly (<a href="chrome://...">is blocked from regular pages, so the click handler useschrome.tabs.create). New i18n keys:onboardingPinTip,onboardingRemapHint.
Changed
- Strip-stat dot color for the third slot stays blue (
:last-child) by default for "saved", and is overridden to amber (.strip-stat.strip-stat-forecast) when the slot is rendering the forecast instead. JS toggles the class as the slot's mode flips between saved/forecast/empty. - Suspend Others and Wake All buttons disable themselves when there's nothing to act on.
renderStatsStripnow flipsbtn.disabledbased onstatus.eligibleCount/status.suspendedCountand sets atitletooltip explaining why (noOthersToSuspend/noSuspendedToWake). The existing.btn:disabled { opacity: 0.5 }rule already handled the visuals — no CSS change needed. The click handlers also short-circuit onthis.disabledas belt-and-braces. - Whitelist input placeholder updated from
"example.com or site.com/path/*"to"example.com, full URL, or site.com/path/*". The popup'saddFromInputalready stripshttp(s)://andwww.so users can paste full URLs directly, but the old placeholder didn't say so — users would manually trim URLs first. The 56 non-English locale translations ofwhitelistPlaceholderstill say the old wording; semantically equivalent so they stay valid until the next translation pass.
Fixed
- Stale-init of
_timestampscould give every tab a fresh 30-min timer after a service-worker restart. Caught in the pre-publish audit. When the worker came up cold and_timestampswas empty (browser restart, session-storage cleared, or first install), bothinitTimestampsand the lazy-reload branch incheckAndSuspendTabsseeded missing entries withDate.now()— so a tab Chrome itself reported as last-accessed 45 minutes ago still gotlastActive = NOW, and the newtab.lastAccessed-awareshouldSuspendcouldn't help because it took the max of the two (NOW vs. 45m-ago = NOW). Fix: seed missing entries fromtab.lastAccessedwhen Chrome 121+ reports it, falling back toDate.now()only on Chrome 120. Now a service-worker restart recovers the actual idle time instead of resetting it. Same fix applied to the lazy-init incheckAndSuspendTabsand the timer countdown ingetTabListso the popup's "Xm" badge matches the actual eligibility check. - Forecast strip-stat had no visual differentiation from "saved". The 1.3.3 changelog claimed an amber dot for the third slot when rendering the forecast (vs blue for actual saved memory), but neither
popup.cssnorpopup.jsactually toggled the...
v1.3.1: polish pass, uninstall feedback page, reliability fixes
Added
- Uninstall feedback page -- on uninstall, Chrome opens a Drowzy-themed feedback page hosted on GitHub Pages (
docs/uninstall.html) asking what made the user leave, how long they used it, what OS they're on, and an optional comment + follow-up email. Submissions land in a Pageclip dashboard (1000/month free tier). After submit, the page shows a thank-you state with a "Reinstall Drowzy" button that deep-links back to the Chrome Web Store listing and a "Star on GitHub" button, so users who change their mind have a one-click path back. The page is mobile-responsive, has dark/light theme support, no analytics, and no guilt-trippy copy. chrome.runtime.setUninstallURLwired inonInstalledandonStartupso the uninstall page is shown every time, including after browser updates that may clear the URL.- Polished empty states -- the tab list, sessions list, and whitelist all now show a centered icon with the existing copy instead of bare gray text
- Animated loading indicator -- the "Loading tabs..." placeholder shown on popup open now has a gently pulsing moon icon
- Persistent section state -- Sessions, Stats, and Settings sections remember whether you had them open or closed between popup opens (stored locally)
- Tab search Escape shortcut -- pressing Esc in the tab search field clears the query, closes the search, and returns focus to the search toggle button
- Cross-window badge accuracy -- the badge refreshes when you switch between Chrome windows so the count always reflects the window you're looking at
- Memory estimate tooltip -- the "saved" strip stat and the "Lifetime RAM saved" hero card now show a tooltip on hover explaining that the number is based on ~150 MB per suspended tab and actual savings vary by page content. Transparency about what the number means.
Changed
- Current-tab card no longer jitters -- the domain/favicon area was shifting every few seconds as the 5s poll re-set the favicon
srcand retriggeredonerror;renderCurrentTabnow signature-dedupes and only touches the DOM when url, favicon, domain, or whitelist/internal state actually change - Session list no longer rebuilds on every poll -- added signature dedupe; session cards only re-render when the session data actually changes, eliminating the subtle 5-second flicker
- Whitelist list no longer rebuilds on every poll -- same treatment; remove-buttons don't get recreated between polls unless the whitelist actually changes
- Badge clears when Chrome is unfocused -- previously fell through to counting discarded tabs across every window when no window was focused (e.g. Chrome minimized), giving a misleading cross-window count; now just clears
- Save-session feedback timer no longer stacks -- rapid re-saves were queuing multiple clear timers, which could wipe a fresh message early
- Stats section visuals -- hero card toned down, distinct colored left borders on section cards, cleaner strip label colors (four-commit refinement pass)
- Permission surface reduced
tabGroupspermission dropped; session save/restore still works without it- Host access moved to
optional_host_permissions;protectFormsandmarkSuspendedTabsrequest<all_urls>on toggle and revert if denied - Host-gated settings reset on install/update if host permission is absent
- Collapsible sections animate via
grid-template-rowsso stats/settings no longer snap-scroll on open/close - Popup/sidepanel poll interval raised to 5s
suspendTabhardening -- rechecks pinned/audio/whitelist status after the 2500ms warning delay so tabs whose state changed mid-warning aren't suspended- Paused-audio tabs get a grace period -- when a tab's audio stops (pause, mute, call ends), Drowzy treats that as activity and resets the idle timer so the tab gets the full suspend threshold before becoming eligible. Previously, pausing Spotify or ending a Meet call would immediately expose the tab to suspension (since
tab.audibleflipped to false), forcing a full page reload when you came back. - Session restore rollback -- replace mode now rolls back if any tab fails to create, and the cleanup wraps
chrome.tabs.removeso a failed cleanup doesn't mask the original error - Tab list rendering is now idempotent via signature diffing; skip DOM rebuild when tabs unchanged and disable re-entry animations on subsequent renders
- Whitelist button hidden on
chrome://,chrome-extension://, and other non-http pages where it cannot function - Re-check
tab.activeimmediately beforechrome.tabs.discardso the active tab can't be reloaded after form-check awaits - Privacy policy wording tightened for
scriptingand host permissions to match actual behavior - Onboarding "zero RAM" claim corrected
- "What's new" link in settings now shows the live version from
manifest.jsonvia a single i18n key (whatsNew), instead of two keys plus a hardcoded fallback changelog.htmlcontent replaced with v1.3.1-specific items (polished empty states, rock-steady current tab, refined stats, permission surface reduction, 22-fix pass, smarter suspension, safer session restore) so the "What's new" link no longer shows v1.3.0 features- Dead
changelog120*i18n keys removed from the English locale (18 orphaned keys) -- they referenced the old v1.3.0 changelog items that no longer exist in the HTML
Fixed
Background script
injectFormCheckTOCTOU race: addtabIdto set beforeawaithandleSuspendCurrentcomparestab.index, not array indexonTabRemovedflushes timestamps immediately instead of schedulingonTabRemovedno longer marks the timestamp store dirty (and triggers a session-storage write) when the removed tab wasn't being trackedsuspendTabskips tabs still loading in the final recheck and accepts an optionalcachedSettingsparamsuspendAllOtherspasses settings to avoid N storage readsaddWhitelistacceptslocalhostand IP addressesgetTabListlazily reloads timestamps after service-worker restartcloseDuplicatesURL normalization now lowercases the dedup key sohttps://EXAMPLE.com/andhttps://example.com/collapse correctlycloseDuplicatesnever closes a pinned tab even if it's a duplicate, and prefers keeping pinned over active when choosing which copy to keep_injectedTabsmarker now also clears when the tab starts loading again (covers manual reload / browser restore), not just on URL changerestoreSessionreturns the number of tabs actually created, not the input count, so partial-restore toasts report real numbers- Fresh install writes
drowzy_lastChangelogVersionso the changelog doesn't pop on first run
Popup
loadAllcatch path null-checks the tab list elementrelativeTimeguards forNaN/0/string timestampsaddFromInputhas a double-submit guard with disabled state- Whitelist toggle accepts
localhost - RAM display drops the stray
~prefix formcheck.jsmessage handler guards against cross-extension senders- Session cards defensive-guard against missing
tabsarray ornamefield (no more crash on corrupted storage) - Section state restoration now runs synchronously before the first async operation in
init(), preventing a visible closed-to-open animation on popup open
Misc
icon()trailing space in class attribute- Removed spurious
src=""from favicon<img>that triggered an empty request - Removed no-op
backdrop-filteron the opaque toast background toggleThemenull-guards#themeToggle- Dead
badgeStarredi18n key removed from all locale files
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: 0846bd8238c32305f0d020b8f7fbcd8da8f95f3596ce2646c3db17ffa57ff72b
You can reproduce the build yourself by checking out v1.3.1 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.3.2: localization parity, sessions overhaul, polish
Added
data-i18n-ariaattribute support inlocalizeHtmlso any HTML element carryingdata-i18n-aria="key"has itsaria-labelpopulated from_localesat init timeramEstimateTooltipi18n key (was referenced frompopup.html/sidepanel.htmlbut never defined; users previously always saw the hardcoded English fallback)- i18n hooks on the privacy policy "Your data" section and footer (
privacyYourDataTitle,privacyYourDataText,privacyFooterText,privacyFooterLink). Those blocks were added in 1.3.1 as hardcoded English and skipped the existingdata-i18npass on the rest of the page
Changed
- Sessions section overhaul.
- Delete confirm UX no longer uses the awkward red "Sure?" text. The trash icon now morphs into a check mark with a red filled background and a soft pulsing halo; clicking the check confirms, auto-reverts after 3s.
- Newly saved sessions slide in from above with a brief tinted highlight and a subtle scale bounce — only the new card animates, not every existing one.
- Deleting a session plays a short slide-out + collapse animation before the row disappears, instead of snapping out.
- Action buttons (Open / Replace / Trash) sit at 70% opacity at rest and go to 100% on hover — they're visible without hovering now, so users on touch/no-hover devices can find them.
- Removed the redundant inline "Session saved!" feedback below the input on success — the toast already confirms it. Errors still inline for visibility.
- Added new i18n key
confirmDeleteTitle("Click again to confirm delete") translated into all 57 locales for the trash button's tooltip in confirming state. - Removed the now-dead
confirm("Sure?") i18n key from all 57 locales. - Added a
checkicon toicons.jsfor the confirm state.
- Changelog now only opens on major version bumps (e.g.
1.x.x → 2.x.x). Previously it also opened on minor bumps (1.2.x → 1.3.x). The "What's new" link in Settings still takes you to the changelog anytime. - Changelog page content reframed as a short "Polish & Fixes" section for 1.3.2; previously showed v1.3.1-specific items verbatim
- Sleeping-tab text contrast bumped from
opacity: 0.45to0.55in light mode so suspended titles clear WCAG AA (they scraped the bottom before); dark mode unchanged - Whitelist list now caps at
max-height: 140pxwith internal scroll — a long whitelist no longer stretches the Settings panel whatsNewBtntext now uses thewhatsNewi18n key (matching the 1.3.1 intent), notchangelogWhatsNew— those keys are distinct and had different capitalization- Locale files pretty-printed via
json.dump(indent=2)for formatting consistency across all 57 locales (non-en locales were already pretty-printed;ennow matches)
Fixed
- Tab list recovers from transient load errors. The
catchbranch inpopup.js#loadAllreplaced the#tabListDOM but didn't reset_tabListSig. If the next poll returned the same tabs, the signature-dedupe guard would short-circuit the re-render and leave the "Failed to load" state stuck on screen._tabListSigis now reset to''in the catch path. - Icon-only toolbar buttons now expose accessible names.
#btnCloseDuplicates,#btnSearchToggle, and#btnExportTabsin bothpopup.htmlandsidepanel.htmlonly carried atitleattribute; added explicitaria-label(plusdata-i18n-ariaso it localizes) so screen readers announce them reliably. - Locale cleanup backport. The 1.3.1 release stripped 23 orphaned keys from
en/messages.jsononly. The other 56 locales still shipped with the deadchangelogA11y*,changelogAnimations*,changelogBugFixes,changelogChangelog*,changelogFormCheck*,changelogLiveStats*,changelogReviewPrompt*,changelogSessionOptions*,changelogSessionRestore*,changelogWhitelistDupes*,changelogWhitelistVal*,failedToSuspend, andsuspendThiskeys. Cleaned in this release. - Two more dead i18n keys removed from every locale:
statRamSaved(replaced bystatLifetimeRamin 1.3.1) andprivacyShortTitle(the privacy policy no longer uses a separate heading for the TL;DR block). PluschangelogNewFeaturesandchangelogImprovementswhich aren't referenced by the simplified 1.3.2 changelog page. faviconFallbackhostname regex hardened from.replace('www.', '')(unanchored literal, technically could matchwww.mid-hostname) to.replace(/^www\./, '')tabListSignaturevariable-shadow cleanup — innervar t = tabs[i]was shadowing the outert()i18n helper inside the same function's scope; renamed totabfor clarity. No behavior change, lint hygiene.- Safety guard in changelog-open branch: if
drowzy_lastChangelogVersionis unexpectedly empty on anupdateevent (shouldn't happen but belt-and-suspenders), we now silently update the stored version instead of comparing against an empty string and opening the tab. - Share-stats URL uses canonical slug. The "Share" button in Stats was copying
https://chromewebstore.google.com/detail/drowzy/oijfnkaakdamnijjgehjpfmclhigmapa— Chrome Web Store redirects that to the real listing, but fixed to use the canonicaldrowzy-tab-suspender-memoslug so the pasted link doesn't look like an unrelated shortener. - Whitelist input clears its error state on success. If the user tried an invalid domain (red border + toast), then within 1.5s tried a valid domain, the red border lingered until the original fade-timer fired even though the add had succeeded. The success path now explicitly removes
.input-error. docs/privacy-policy.htmlre-synced with the in-extension copy. The "Your data" section and footer were missing thedata-i18nhooks added to the extension'sprivacy-policy.html; the GitHub Pages mirror is now byte-identical to the extension copy.- "What's new" link showed double version (
v1.3.1 v1.3.2) in non-English locales. 56 of 57 locales had the previous version baked into the translated string (e.g.dewas"Neu in v1.3.1"), andpopup.jswas appendingv$CURRENT$on top — producing concatenations like"Neu in v1.3.1 v1.3.2". Fixed by switchingwhatsNewto a$VERSION$chrome.i18n placeholder so each locale puts the version in the correct grammatical position natively, then passing the live manifest version as the substitution. Removeddata-i18n="whatsNew"frompopup.html/sidepanel.htmlsolocalizeHtmldoesn't render the literal placeholder beforeattachListenerssubstitutes it. - Full localization parity across all 57 locales. Twelve i18n keys (
changelogVersionLabel,copiedStats,privacyFooterLink,privacyFooterText,privacyPermHost,privacyPermScripting,privacyPermSidePanel,privacyYourDataText,privacyYourDataTitle,ramEstimateTooltip,shareStats,statLifetimeRam) had been added toenover 1.3.1 / 1.3.2 but never translated to the other 56 locales — non-English users were silently falling back to English text on those strings. Added native translations for all 12 keys × 56 locales (672 strings). Every locale now has all 156 keys. - Reconciled three privacy permission strings (
privacyPermHost,privacyPermScripting,privacyPermSidePanel) where theen/messages.jsonvalue was older verbose text disagreeing with the concise text inprivacy-policy.html's static fallback.localizeHtmlwas overwriting the rendered text with the older copy, causing a flash of wrong content. Theenvalues now match the HTML fallbacks exactly. - Reconciled six more privacy strings (
privacyTitle,privacyPermissionsIntro,privacyPermTabs,privacyPermStorage,privacyPermAlarms,privacyPermContextMenus) with the same drift pattern — theenvalues were stale (e.g."tabs – listing, suspending, and restoring browser tabs"redundantly included the permission name and dash that's already rendered separately by the<code>element). Updatedento the HTML's concise form and re-translated all six keys across the 56 non-English locales (336 fresh translations). Caught by a deeper localization audit that compares static HTML fallback text against the correspondingeni18n value.
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: 54d29d4e5f14043f7b0522350b076a15147044e637e45723c9252acb6a6c5770
You can reproduce the build yourself by checking out v1.3.2 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.3.0: major feature release with full repo overhaul
Added
- Close duplicate tabs -- one-click button to find and close duplicate tabs in the current window (keeps the first occurrence)
- Tab search -- search and filter your tab list by title or URL
- Dark / light theme -- toggle between themes with the button in the header; preference is saved across sessions
- Side panel -- open Drowzy in Chrome's side panel for a persistent, full-height view
- Mark suspended tabs -- optional
[zzz]prefix on suspended tab titles so you can spot them in the tab bar - Export tab list -- copy all tab titles and URLs to clipboard with one click
- Export sessions as JSON -- copy all saved sessions for backup or transfer
- Suspend warning -- tabs show a brief "Suspending tab soon..." notice before being auto-suspended
- Whitelist import/export -- copy your whitelist for backup or paste one from another machine
- Session replace mode -- replace current window's tabs with a saved session
sidePanelpermission -- enables Chrome side panel integrationoptional_host_permissions-- host access for form detection is now optional and requested only when you enable the feature (avoids broad install-time permission prompt)scriptingpermission -- programmatic content script injection for form checking (replaces static content scripts)
Removed
tabGroupspermission -- tab group reconstruction on session restore is no longer supported; sessions still save and restore tabs themselves (dropped to minimize permission surface)
Changed
- Privacy policy updated with new permissions (April 2026)
- Changelog page redesigned with feature cards and dark/light theme support
- Review prompt threshold raised from 10 to 50 suspensions
- Form check content script now injected on-demand via
chrome.scripting.executeScriptinstead of running on every page - Formcheck uses
WeakMapsnapshots for contenteditable detection - Badge count scoped to current window instead of all windows
- Stats use local date instead of UTC for "today" tracking
- Whitelist validation requires domain to contain a dot
- Session save filters out incognito tabs to prevent privacy leaks
- Onboarding close uses
chrome.tabs.remove()instead ofwindow.close() - All pages set
document.documentElement.langdynamically for accessibility - All collapsible sections have
aria-controlsandaria-expandedattributes - Light theme tertiary text darkened for WCAG AA contrast compliance
- Focus-visible outlines on form inputs (replaces
outline: none) - Review prompt dismiss button enlarged for easier click targets
- Minimum Chrome version: 120
Fixed
- Whitelist import no longer silently drops domains (uses individual
addWhitelistcalls instead of bulkupdateSettings) - Session save no longer crashes on untitled tabs (fixed variable shadowing of i18n function)
- Protected count no longer includes suspended pinned/audio tabs
- Startup suspend now correctly records suspensions in stats
- Port-stripping regex no longer breaks path-containing whitelist entries
isInternalUrluses URL parsing instead of fragile prefix matching- Alarm creation checks for existing alarm to avoid resetting the period
- Tab timestamps properly mark dirty flag for flush
- Duplicate detection strips URL fragments and trailing slashes
[zzz]prefix cleaned up before tab reload- Changelog only opens on major/minor version bumps, not patches
- Content script has idempotency guard against double-injection
- Message handler blocks unauthorized content script messages
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: 941a0a2d3434fc8dcac06687bba31c9f7178d6574b8166f2829b56dc7ef58cf8
You can reproduce the build yourself by checking out v1.3.0 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.2.1: fix CSP inline scripts, remove broken suspend button
Fixed
- Moved inline scripts to external
.jsfiles for Manifest V3 CSP compliance - Removed broken "Suspend Tab" button from quick actions (redundant with keyboard shortcut)
- Fixed invalid JSON in several locale translation files
- Removed dead
suspendCurrentTabmessage handler from background.js
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: 318d5bf1da48d02864b0ab07e9774182f8603bcecf34e6fb3b74370bb748e59b
You can reproduce the build yourself by checking out v1.2.1 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.2.0: side panel, tab groups, and scripting-based form detection
Added
- Side panel support -- Drowzy can now be opened in Chrome's side panel (
sidepanel.html) - Tab group awareness -- sessions preserve tab group names and colors on restore
- Form data protection -- content script detects unsaved form data before suspension via
chrome.scripting.executeScript tabGroupspermission for tab group operationsscriptingpermission for programmatic content script injection
Changed
- Content script injection switched from static
content_scriptsmanifest entry to on-demandchrome.scripting.executeScript - Extended popup UI with additional settings and controls
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: 5bb836c4cfd4f1763171dec6f0f93d781d5589a25726d261b6f1589b2ad6ef50
You can reproduce the build yourself by checking out v1.2.0 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.1.0: sessions, stats, changelog, and whitelist management
Added
- 57 language translations -- full i18n support; UI automatically matches browser language
- Changelog page (
changelog.html) -- in-extension "What's New" page shown after updates - Session management -- save, name, restore, and delete tab sessions
- Stats tracking -- daily and all-time suspension counts, estimated RAM saved, member-since date
- Whitelist management UI -- add, remove, and view whitelisted sites from the popup
Changed
- Popup UI expanded with collapsible sections (Sessions, Stats, Settings)
- Settings panel moved into the popup (previously separate or minimal)
- Background script extended with session save/restore, stats recording, and whitelist CRUD
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: caa7ad11f63c6779522c72eda7c3482c2f1c743483dc7b48f76639c3b24b3661
You can reproduce the build yourself by checking out v1.1.0 and zipping the extension files (everything except .git/, .github/, *.md, docs/).
v1.0.0: initial release
Added
- Initial release
- Auto-suspend inactive tabs using Chrome's native
chrome.tabs.discard()API - Configurable suspend timer (5 minutes to 4 hours, or never)
- Protect pinned tabs and audio-playing tabs from suspension
- Site whitelist with pattern matching
- Keyboard shortcuts:
Alt+S(suspend current),Alt+Shift+S(suspend others),Alt+W(wake all) - Right-click context menu for quick suspend actions
- Popup UI with tab list, quick actions, and settings
- Onboarding page for first-time users
- Privacy policy page
- 57 language locale files
- MIT license
Verifying this build
This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:
sha256: 139783ab601b24bc52576367c59ec6c4db450f5a06709527e7b9e4aea7b9969f
You can reproduce the build yourself by checking out v1.0.0 and zipping the extension files (everything except .git/, .github/, *.md, docs/).