A Firefox extension that spoofs the time zone and (optionally) the User-Agent reported by the browser to websites — across all contexts: the main page, iframes (any nesting depth), Dedicated Worker, Shared Worker, and Service Worker.
Websites infer approximate location and browser characteristics via Intl.DateTimeFormat, Date.prototype.getTimezoneOffset, navigator.userAgent, and similar APIs — and they do this not only in the main window but also inside workers, where ordinary time-zone spoofing extensions usually can't reach. This extension patches all these points consistently, so every context returns the same spoofed values.
- Time zone spoofing —
Intl.DateTimeFormat,Date.prototype.getTimezoneOffset,toString/toDateString/toTimeString/toLocaleString/toLocaleDateString/toLocaleTimeString - Optional User-Agent spoofing —
navigator.userAgent,appVersion,platform,vendor,language,languages,maxTouchPoints,oscpu(Firefox), and related properties - HTTP header spoofing — optionally modifies outgoing
Accept-LanguageandUser-Agentheaders to match the selected timezone and browser identifier - Consistent across all contexts — main window, iframe (including nested iframe-within-iframe), Dedicated Worker, Shared Worker, Service Worker
- Real system clock untouched —
Date.now()and the actual OS clock are not modified; only how the date is interpreted and displayed in the chosen time zone is changed - Cached formatters —
Intl.DateTimeFormatandIntl.NumberFormatformatters are cached to avoid timing detection (important against detectors like CreepJS) - CSP-safe — works on sites with strict Content-Security-Policy (Spotify Web Player and similar) via declarative injection through
world: "MAIN" - No enumerable globals — all shared state lives inside the closure of a single file and is not enumerable from the outside
- Temporal API support — patches
Temporal.Now.timeZoneIdand related methods when available - Number format spoofing —
Intl.NumberFormatandNumber.prototype.toLocaleStringuse the spoofed locale
A single content script injected by the browser directly into the page's main world (world: "MAIN" in the manifest). This bypasses the page's CSP, since such injection is not treated as code added by the page itself.
Intl.DateTimeFormat,Intl.NumberFormat, andDate.prototypemethods are patched once per window, with cached formatters.navigator.userAgentand related properties are patched viaObject.definePropertyonNavigator.prototype.- All patched functions are masked as
[native code]via a customtoStringwrapper. - iframe: instead of
MutationObserver(which is always asynchronous), the gettercontentWindow/contentDocumentonHTMLIFrameElement.prototypeis patched — the spoof is applied at the moment of access, not at DOM insertion. Recursive patching handles iframe-within-iframe. - Dedicated / Shared Worker: the
Worker/SharedWorkerconstructors are wrapped so that our patch runs before the worker's original script, loaded viaimportScripts()— without blocking synchronous XHR on the main thread. - Service Worker:
navigator.serviceWorker.register()is intercepted to learn the exact URL of the worker script, sent to the background viabridge.js.
Reads saved tz / ua / enabled / spoofHeaders from browser.storage.local and passes them to main.js through three mechanisms:
- DOM attributes on
<html>— available immediately atdocument_start. sessionStorage/localStoragefallback (__tzGuardConfig) — survives page reloads and bfcache restores.CustomEvent(tz-guard-update-config) — for live updates when settings change in the popup.
Service Workers cannot be patched via blob: URLs (forbidden by spec). Therefore network-response interception is used:
webRequest.onBeforeRequest+webRequest.filterResponseData(requireswebRequestFilterResponse.serviceWorkerScript) splices the patch code into the beginning of the Service Worker script response.- The filter must be installed in
onBeforeRequest, not later, to avoid Firefox's internal script byte-cache (Invalid request ID). - Known Service Worker URLs are persisted in
storage.local(swUrls) so patches survive browser restarts.
When "Spoof HTTP Headers" is enabled in the popup, the extension modifies outgoing request headers via webRequest.onBeforeSendHeaders:
Accept-Language— derived from the selected timezone (e.g.,Europe/Helsinki→fi-FI,fi;q=0.9,en-US;q=0.8,en;q=0.7).User-Agent— set to the custom UA if provided.
- Extension Enabled — master toggle; disables all spoofing when turned off.
- Spoof HTTP Headers — toggles modification of outgoing
Accept-LanguageandUser-Agentheaders. - Timezone — select the timezone to report.
- User-Agent — choose from presets or enter a custom string.
about:debugging#/runtime/this-firefox→ Load Temporary Add-on → selectmanifest.json.- Open the popup, choose a time zone (and optionally a User-Agent), adjust toggles, Save & Apply.
- Fully close and reopen the tab (not just F5 — temporary extensions in Firefox get a new
moz-extension://UUID on every reload).
manifest.json
bridge.js — isolated world: reads storage, passes config via DOM + storage events
spoof/
main.js — all patches (Intl/Date/UA/iframe/Worker/SharedWorker/Temporal), one file, one IIFE
background/
background.js — network response interception, header spoofing, SW URL tracking
build_worker.js — patch-code generator for Service Worker injection (module)
frontend/
popup.html
popup.js
icons/
storage— saves the selected tz/ua and settings locally; persists known SW URLs.<all_urls>(host permission) — the spoof must apply on any site, before the site's own scripts run.webRequest,webRequestBlocking,webRequestFilterResponse,webRequestFilterResponse.serviceWorkerScript— required for patching Service Workers via network response interception and for HTTP header modification.
- Indexed access
window[n]/window.frames[n]— numeric index access is exotic internalWindowProxybehavior, not an ordinary JS property.Object.definePropertycannot intercept it. The first such access returns real data; subsequent accesses through.contentWindoware already patched. - "Dead" (detached) iframes — a reference to a removed iframe's window, obtained through a non-standard access path, may remain unpatched.
- Live updates require reload — after changing tz/ua in the popup, open tabs must be reloaded. The spoof is applied at page load time, not retroactively (although an already open tab will pick up new values for future API calls if the popup was opened before the page reload).
- OS clock untouched — the extension only changes what is reported through JS APIs and HTTP headers. The operating system clock and time zone are not modified.
| Before | After | |
|---|---|---|
| BrowserLeaks — HTTP Headers | ![]() | ![]() |
| BrowserLeaks — JS APIs | ![]() | ![]() |
| CreepJS — Fingerprint | ![]() | ![]() |
| CreepJS — Timezone | ![]() | ![]() |
| CreepJS — Workers | ![]() | ![]() |
MIT









