Skip to content

v1.3.3

Choose a tag to compare

@ml3dev ml3dev released this 16 May 03:37

Added

  • First-run quick-suspend pass (firstRunQuickSuspend in background.js). 30s after install, Drowzy suspends tabs that Chrome reports as idle for 10+ minutes via tab.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.lastAccessed honored in suspend decisions (shouldSuspend and the final recheck inside suspendTab). Drowzy now takes the more recent of its own activation timestamp and Chrome's tab.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 _timestamps were re-seeded to Date.now() for missing tabs, but tab.lastAccessed had 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 a keepTabAwake message to the background; the background bumps the tab's timestamp via touchTab and suspendTab's pre-discard recheck reads the bumped timestamp and bails out. New i18n key: keepAwakeBtn.
  • keepTabAwake action allowed from content-script senders in background.js#onMessage. The unauthorized-content-script guard still blocks every other action, but keepTabAwake is 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 calling chrome.tabs.discard, the function now re-reads _timestamps[tabId] and tab.lastAccessed and 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" in popup.html and sidepanel.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 protectForms setting. Both ship as data-i18n-html so the embedded <strong> survives translation. New i18n keys: onboardingTimerTip, onboardingFormsTip.
  • Windows-specific keyboard shortcut defaults to avoid Microsoft Office Web access-key collisions:
    • suspend-current on Windows is now Alt+Shift+Z (was Alt+S). On Windows, Alt+S is the access key for Send in Outlook Web, Teams, OWA, and most Microsoft 365 surfaces. A user composing an email who hit Alt+S to send would instead suspend the compose tab - silently, with potential draft loss. macOS/Linux/ChromeOS keep Alt+S since those platforms don't have the conflict (Mac Outlook uses Cmd+Enter).
    • wake-all on Windows is now Alt+Shift+W (was Alt+W). Alt+W is the access key for the View ribbon in Word, Excel, and PowerPoint Web. Same per-platform override pattern.
    • suspend-others stays at Alt+Shift+S everywhere - no known conflict.
    • Existing users who customized their shortcuts via chrome://extensions/shortcuts are 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 query chrome.commands.getAll() after i18n localization runs and replace each <kbd>'s text with the user's actual binding - so Windows users see Alt+Shift+Z / Alt+Shift+W and 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 in getStatus as (allTabs - protected - discarded) × MB_PER_TAB and exposed as eligibleCount + estimatedMbForecast.
  • statForecast and ramForecastTooltip i18n keys in en/messages.json for 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 Others regardless of whether anything actually happened - a window with zero eligible tabs would still show "Done" and the user would close the popup confused. Now: suspendAllOthers and unsuspendAll both 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-redundant Done text-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: systemPageCantSuspend for chrome:// and other non-http schemes, pinnedWontSuspend when the user has Protect Pinned on and the tab is pinned, audioWontSuspend when Protect Audio is on and the tab is audible, whitelistedWontSuspend when the tab's host matches the whitelist, falling back to the existing activeWontSuspend. 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/shortcuts directly (<a href="chrome://..."> is blocked from regular pages, so the click handler uses chrome.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. renderStatsStrip now flips btn.disabled based on status.eligibleCount / status.suspendedCount and sets a title tooltip 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 on this.disabled as 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's addFromInput already strips http(s):// and www. 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 of whitelistPlaceholder still say the old wording; semantically equivalent so they stay valid until the next translation pass.

Fixed

  • Stale-init of _timestamps could 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 _timestamps was empty (browser restart, session-storage cleared, or first install), both initTimestamps and the lazy-reload branch in checkAndSuspendTabs seeded missing entries with Date.now() - so a tab Chrome itself reported as last-accessed 45 minutes ago still got lastActive = NOW, and the new tab.lastAccessed-aware shouldSuspend couldn't help because it took the max of the two (NOW vs. 45m-ago = NOW). Fix: seed missing entries from tab.lastAccessed when Chrome 121+ reports it, falling back to Date.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 in checkAndSuspendTabs and the timer countdown in getTabList so 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.css nor popup.js actually toggled the class - both states rendered with the identical blue dot, so a user reading "~3.4 GB available" had no visual cue that this was a forecast and not a number Drowzy had actually freed. Fixed in three places: popup.js#renderStatsStrip now adds/removes strip-stat-forecast on the slot, and popup.css now has .strip-stat.strip-stat-forecast .strip-stat-label::before (amber dot) and .strip-stat.strip-stat-forecast #statMemoryValue (amber value color) overrides. The blue stays for :last-child by default, so saved still reads as saved. Color transitions added on .strip-stat-value and .strip-stat-label::before so the saved↔forecast flip animates smoothly instead of snapping.
  • Suspend warning banner ran on hidden tabs - pure theater, since the banner was being painted onto a tab the user definitionally wasn't looking at (it had been idle for 30 minutes). formcheck.js#showSuspendWarning now early-returns if document.visibilityState !== 'visible', so the banner only renders when there's a chance of being seen and the "Keep awake" button is reachable.
  • Form-data check missed several common input types. hasUnsavedFormData queried only text/email/url/tel/password/number/no-type/textarea/contenteditable - a search input on a long query, a date picker mid-fill, or a time/week/month/datetime-local/color field would all read clean and the tab would get discarded. Query expanded to cover those types. Also added a separate scan for input[type="checkbox"] and input[type="radio"] whose checked differs from defaultChecked, since a half-toggled settings page is just as lost as a typed-but-unsaved input.
  • currentTabStatus initial text was the literal string "Loading..." (via data-i18n="loading") which would stick on screen if loadAll threw before the first renderCurrentTab ran - the user would see a tab marked "Loading..." indefinitely with no way to know what was wrong. Replaced with an em-dash (matching the currentTabDomain initial state). Same change in both popup.html and sidepanel.html.
  • Suspend-warning banner "Click to dismiss" tooltip in formcheck.js was hardcoded English; only the banner body text was localized. Wired up via the new clickToDismiss i18n key with English fallback for unknown locales.

Translations

  • Full localization parity across all 57 locales for all 20 new keys added in this release (16 from the initial 1.3.3 work + 4 from the pre-publish audit pass: keepAwakeBtn, suspendTimerHint, onboardingTimerTip, onboardingFormsTip). 56 locales × 20 keys = 1120 fresh translations. Every locale now has all 175 keys; no English fallback gaps anywhere in the popup, side panel, onboarding, or content scripts. Same approach as the 1.3.2 parity sweep - AI-authored translations preserving the $COUNT$ / $RAM$ placeholder structure where applicable and the <strong> wrappers in onboarding tips, with native-speaker review welcomed for any awkward phrasing. The whitelistPlaceholder rewording is the one exception held back: existing locale translations of the old wording remain semantically valid so they stay until the next translation pass.

Final QA pass

  • changelog.html body content rewritten for 1.3.3. The page was still showing the v1.3.2 "Polish & Fixes" cards verbatim, but the dynamic version badge and the "What's new vX" link in Settings would render v1.3.3 - so a user clicking the link would see a header that says 1.3.3 paired with v1.3.2 changes. Replaced with six v1.3.3-specific cards (forecast stat, Windows-friendly shortcuts, quantified suspend/wake toasts, smart current-tab status, friendlier first-run tips, full translation coverage).
  • Dead done i18n key removed from all 57 locale files. It was used by the old text-flip pattern on Suspend Others / Wake All (Suspending... → Done → Suspend Others); the toast refactor in this release removed every reference. 57 stale entries dropped.
  • Three review-prompt button title= tooltips localized. The visible button text (reviewYes, reviewNo) was already i18n'd, but the hover tooltips on the thumbs-up, thumbs-down, and dismiss buttons in popup.html / sidepanel.html were hardcoded English. Added data-i18n-title bindings + three new keys (reviewLeaveTitle, reviewReportTitle, reviewDismissTitle) translated across all 56 non-English locales (168 strings). Low-frequency UI (only shown after 50+ suspensions) but rounds out the parity story.

Verifying this build

This release attaches the exact .zip uploaded to the Chrome Web Store. To verify it hasn't been tampered with:

sha256: 07596df748bf1874ea08854b683586bd54a01d49d02263a719ad57ba96978cb1

You can reproduce the build yourself by checking out v1.3.3 and zipping the extension files (everything except .git/, .github/, *.md, docs/).