From 39720fa90b6c1df33db5a4bff5f5346a1bef3198 Mon Sep 17 00:00:00 2001 From: Anand Hegde Date: Mon, 14 Sep 2026 11:58:15 +0530 Subject: [PATCH 1/2] Skip tracking in the heatmap preview iframe The heatmap report loads the tracked page in an iframe, so the site's own tracker recorded a page view every time a heatmap was opened. Name the preview frame umami.disabled and have the tracker treat that window name like the existing umami.disabled localStorage flag. Fixes #4529 --- .../(reports)/heatmaps/Heatmap.tsx | 1 + src/tracker/index.test.ts | 24 +++++++++++++++++++ src/tracker/index.ts | 1 + 3 files changed, 26 insertions(+) diff --git a/src/app/(main)/websites/[websiteId]/(reports)/heatmaps/Heatmap.tsx b/src/app/(main)/websites/[websiteId]/(reports)/heatmaps/Heatmap.tsx index d579dbd3c0..2ebd61b936 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/heatmaps/Heatmap.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/heatmaps/Heatmap.tsx @@ -1043,6 +1043,7 @@ function IframeSnapshot({ snapshot, onReady }: { snapshot: HeatmapSnapshot; onRe className={`${styles.snapshotIframe} rr-block`} src={iframeUrl} title={iframeUrl} + name="umami.disabled" tabIndex={-1} loading="lazy" scrolling="no" diff --git a/src/tracker/index.test.ts b/src/tracker/index.test.ts index 2e2fab59e8..0f0c8904df 100644 --- a/src/tracker/index.test.ts +++ b/src/tracker/index.test.ts @@ -36,3 +36,27 @@ test('identifies data-distinct-id before the initial page view', async () => { payload: { id: 'visitor-id', website: 'website-id' }, }); }); + +test('does not track when loaded in a frame named umami.disabled', async () => { + const script = document.createElement('script'); + script.src = 'https://analytics.example.com/script.js'; + script.dataset.websiteId = 'website-id'; + + Object.defineProperties(document, { + currentScript: { configurable: true, value: script }, + readyState: { configurable: true, value: 'complete' }, + }); + + const fetchMock = vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue({}) }); + vi.stubGlobal('fetch', fetchMock); + window.name = 'umami.disabled'; + + try { + await import('./index'); + await window.umami.track('signup-button'); + } finally { + window.name = ''; + } + + expect(fetchMock).not.toHaveBeenCalled(); +}); diff --git a/src/tracker/index.ts b/src/tracker/index.ts index 588136b009..33ac730c1b 100644 --- a/src/tracker/index.ts +++ b/src/tracker/index.ts @@ -378,6 +378,7 @@ type MetricEntry = PerformanceEntry & { disabled || !website || localStorage?.getItem('umami.disabled') || + window.name === 'umami.disabled' || (domain && !domains.includes(hostname)) || (dnt && hasDoNotTrack()); From 51d6304448dd3ca41e7d6ba30a2576c66976e4bf Mon Sep 17 00:00:00 2001 From: Anand Hegde Date: Mon, 14 Sep 2026 14:39:53 +0530 Subject: [PATCH 2/2] Only honor the umami.disabled window name inside a frame A top-level tab opened with that name (window.open or a link target) keeps the name across navigation, so it would silently stop tracking. The heatmap preview is always framed, so require top !== window. --- src/tracker/index.test.ts | 13 +++++++++++-- src/tracker/index.ts | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/tracker/index.test.ts b/src/tracker/index.test.ts index 0f0c8904df..ff4d4988a2 100644 --- a/src/tracker/index.test.ts +++ b/src/tracker/index.test.ts @@ -37,7 +37,7 @@ test('identifies data-distinct-id before the initial page view', async () => { }); }); -test('does not track when loaded in a frame named umami.disabled', async () => { +const loadTracker = async (framed: boolean) => { const script = document.createElement('script'); script.src = 'https://analytics.example.com/script.js'; script.dataset.websiteId = 'website-id'; @@ -49,6 +49,7 @@ test('does not track when loaded in a frame named umami.disabled', async () => { const fetchMock = vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue({}) }); vi.stubGlobal('fetch', fetchMock); + if (framed) vi.stubGlobal('top', {}); window.name = 'umami.disabled'; try { @@ -58,5 +59,13 @@ test('does not track when loaded in a frame named umami.disabled', async () => { window.name = ''; } - expect(fetchMock).not.toHaveBeenCalled(); + return fetchMock; +}; + +test('does not track when loaded in a frame named umami.disabled', async () => { + expect(await loadTracker(true)).not.toHaveBeenCalled(); +}); + +test('still tracks in a top-level window named umami.disabled', async () => { + expect(await loadTracker(false)).toHaveBeenCalled(); }); diff --git a/src/tracker/index.ts b/src/tracker/index.ts index 33ac730c1b..ad9be9ba7f 100644 --- a/src/tracker/index.ts +++ b/src/tracker/index.ts @@ -378,7 +378,7 @@ type MetricEntry = PerformanceEntry & { disabled || !website || localStorage?.getItem('umami.disabled') || - window.name === 'umami.disabled' || + (window.name === 'umami.disabled' && top !== window) || (domain && !domains.includes(hostname)) || (dnt && hasDoNotTrack());