← back to Homesonspec
Homes on Spec: actually test the tracker-blocking script (TK-10387)
f2de197d2bf221e7dfced05f65b7c4900ee79e03 · 2026-09-04 08:17:39 -0700 · Steve
The App Store 'Data Not Collected' claim rests on the injected TRACKER_BLOCK script
working. Until now only isTrackerUrl() — the native-side URL filter — had tests. The
script doing most of the work (neutering gtag/fbq/dataLayer and intercepting
fetch/XHR/Image/sendBeacon inside the page) had none, so 'it looks right' was the only
evidence behind a claim we make to Apple.
- lib/tracker-block.ts now exports buildTrackerBlock(hosts) and has ZERO imports, so
node --test can load it directly (Node's ESM resolver requires explicit extensions,
which the TS/Metro resolver does not use). TrackedWebView composes it from the real
TRACKER_HOSTS, so the shipped behaviour is unchanged.
- lib/tracker-block.test.mjs runs the REAL script text, built from the REAL host list,
in a vm sandbox with a fake window/document, and asserts observable behaviour rather
than string matching: gtag/fbq/dataLayer.push are no-ops (and dataLayer does not
accumulate), tracker fetch short-circuits to an empty 204 while first-party fetch
reaches the real implementation, GA4's sendBeacon transport is refused, tracker XHR
never sends, the Facebook tr pixel never gets a real src, subdomains of tracker hosts
are blocked, and a lookalike domain is NOT blocked so the filter cannot over-reach
and break the site.
12/12 tests pass; tsc --noEmit clean. This verifies the MECHANISM only — the panel's
binding condition still stands: do not file 'Data Not Collected' until a full-device
proxy capture across all four tabs is clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfGYEoLBywwJD1nfrHe1on
Files touched
M apps/mobile/components/TrackedWebView.tsxA apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/1-browse.pngA apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/2-saved.pngA apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/3-map.pngA apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/4-alerts.pngA apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/5-browse-return.pngA apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-BUILD3-2026-09-04T13-10-12Z.movA apps/mobile/device-proof-evidence/privacy-sweep-BUILD3-2026-09-04T13-10-12Z.jpgA apps/mobile/lib/tracker-block.test.mjsA apps/mobile/lib/tracker-block.ts
Diff
commit f2de197d2bf221e7dfced05f65b7c4900ee79e03
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Sep 4 08:17:39 2026 -0700
Homes on Spec: actually test the tracker-blocking script (TK-10387)
The App Store 'Data Not Collected' claim rests on the injected TRACKER_BLOCK script
working. Until now only isTrackerUrl() — the native-side URL filter — had tests. The
script doing most of the work (neutering gtag/fbq/dataLayer and intercepting
fetch/XHR/Image/sendBeacon inside the page) had none, so 'it looks right' was the only
evidence behind a claim we make to Apple.
- lib/tracker-block.ts now exports buildTrackerBlock(hosts) and has ZERO imports, so
node --test can load it directly (Node's ESM resolver requires explicit extensions,
which the TS/Metro resolver does not use). TrackedWebView composes it from the real
TRACKER_HOSTS, so the shipped behaviour is unchanged.
- lib/tracker-block.test.mjs runs the REAL script text, built from the REAL host list,
in a vm sandbox with a fake window/document, and asserts observable behaviour rather
than string matching: gtag/fbq/dataLayer.push are no-ops (and dataLayer does not
accumulate), tracker fetch short-circuits to an empty 204 while first-party fetch
reaches the real implementation, GA4's sendBeacon transport is refused, tracker XHR
never sends, the Facebook tr pixel never gets a real src, subdomains of tracker hosts
are blocked, and a lookalike domain is NOT blocked so the filter cannot over-reach
and break the site.
12/12 tests pass; tsc --noEmit clean. This verifies the MECHANISM only — the panel's
binding condition still stands: do not file 'Data Not Collected' until a full-device
proxy capture across all four tabs is clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfGYEoLBywwJD1nfrHe1on
---
apps/mobile/components/TrackedWebView.tsx | 122 +-----------------
.../1-browse.png | Bin 0 -> 1894668 bytes
.../BUILD3-stills-2026-09-04T13-10-12Z/2-saved.png | Bin 0 -> 145762 bytes
.../BUILD3-stills-2026-09-04T13-10-12Z/3-map.png | Bin 0 -> 1657914 bytes
.../4-alerts.png | Bin 0 -> 303248 bytes
.../5-browse-return.png | Bin 0 -> 1894668 bytes
...-DEVICE-journey-BUILD3-2026-09-04T13-10-12Z.mov | Bin 0 -> 1746252 bytes
.../privacy-sweep-BUILD3-2026-09-04T13-10-12Z.jpg | Bin 0 -> 195531 bytes
apps/mobile/lib/tracker-block.test.mjs | 137 +++++++++++++++++++++
apps/mobile/lib/tracker-block.ts | 129 +++++++++++++++++++
10 files changed, 268 insertions(+), 120 deletions(-)
diff --git a/apps/mobile/components/TrackedWebView.tsx b/apps/mobile/components/TrackedWebView.tsx
index 3a6d187e..f6de610e 100644
--- a/apps/mobile/components/TrackedWebView.tsx
+++ b/apps/mobile/components/TrackedWebView.tsx
@@ -21,128 +21,10 @@
import { WebView, WebViewProps } from 'react-native-webview';
import { forwardRef } from 'react';
import { isTrackerUrl, TRACKER_HOSTS } from '../lib/tracker-policy';
+import { buildTrackerBlock } from '../lib/tracker-block';
-/**
- * Runs BEFORE any page script. Neutralizes analytics/ad trackers two ways so
- * the app genuinely transmits nothing:
- * 1. Stub the JS APIs (gtag / dataLayer / fbq / google_tag_manager) into no-ops.
- * 2. Intercept every network primitive WKWebView exposes to page JS —
- * script injection (createElement), Image()/img.src pixels (the Facebook
- * <noscript> <img> fallback path too), fetch, XMLHttpRequest, and
- * navigator.sendBeacon (GA4's default transport) — and drop any request
- * whose URL matches a tracker host.
- * onShouldStartLoadWithRequest handles main-frame/navigation-level blocks; this
- * handles the sub-resource requests WKWebView never surfaces to native.
- */
-const TRACKER_BLOCK = `
-(function() {
- try {
- var HOSTS = ${JSON.stringify(TRACKER_HOSTS)};
- function blocked(u) {
- try {
- var hostname = new URL(String(u), window.location.href).hostname.toLowerCase();
- for (var i = 0; i < HOSTS.length; i++) {
- if (hostname === HOSTS[i] || hostname.slice(-(HOSTS[i].length + 1)) === '.' + HOSTS[i]) return true;
- }
- } catch (e) {}
- return false;
- }
-
- // 1. Stub tracker JS APIs
- var noop = function() {};
- window.dataLayer = window.dataLayer || [];
- window.dataLayer.push = noop;
- window.gtag = noop;
- window.ga = noop;
- window.google_tag_manager = {};
- window.fbq = function() {}; window.fbq.queue = []; window.fbq.loaded = true;
- window._fbq = window.fbq;
-
- // 2a. Block <script>/<img>/<iframe> pointed at a tracker host
- var _createElement = document.createElement.bind(document);
- document.createElement = function(tag) {
- var el = _createElement(tag);
- var t = String(tag || '').toLowerCase();
- if (t === 'script' || t === 'img' || t === 'iframe') {
- try {
- var proto = Object.getPrototypeOf(el);
- var desc = Object.getOwnPropertyDescriptor(proto, 'src')
- || Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'src');
- if (desc && desc.set) {
- Object.defineProperty(el, 'src', {
- configurable: true,
- get: function() { return desc.get ? desc.get.call(el) : ''; },
- set: function(v) { if (!blocked(v)) desc.set.call(el, v); }
- });
- }
- } catch (e) {}
- }
- return el;
- };
-
- // 2b. Block Image()/new Image().src pixels (Facebook tr pixel, GA hits)
- var _Image = window.Image;
- if (_Image) {
- window.Image = function() {
- var img = new _Image();
- try {
- var d = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
- if (d && d.set) {
- Object.defineProperty(img, 'src', {
- configurable: true,
- get: function() { return d.get.call(img); },
- set: function(v) { if (!blocked(v)) d.set.call(img, v); }
- });
- }
- } catch (e) {}
- return img;
- };
- }
-
- // 2c. Block fetch
- var _fetch = window.fetch;
- if (_fetch) {
- window.fetch = function(input) {
- var url = (input && input.url) ? input.url : input;
- if (blocked(url)) return Promise.resolve(new Response('', { status: 204 }));
- return _fetch.apply(this, arguments);
- };
- }
-
- // 2d. Block XHR
- var _open = XMLHttpRequest.prototype.open;
- XMLHttpRequest.prototype.open = function(method, url) {
- this.__blocked = blocked(url);
- return _open.apply(this, arguments);
- };
- var _send = XMLHttpRequest.prototype.send;
- XMLHttpRequest.prototype.send = function() { if (this.__blocked) return; return _send.apply(this, arguments); };
-
- // 2e. Block sendBeacon (GA4 default transport)
- if (navigator.sendBeacon) {
- var _beacon = navigator.sendBeacon.bind(navigator);
- navigator.sendBeacon = function(url) { if (blocked(url)) return false; return _beacon.apply(this, arguments); };
- }
+const TRACKER_BLOCK = buildTrackerBlock(TRACKER_HOSTS);
- // 2f. Strip any tracker <img>/<script> that slips into the DOM (e.g. <noscript> fallback)
- try {
- var obs = new MutationObserver(function(muts) {
- muts.forEach(function(m) {
- for (var i = 0; i < m.addedNodes.length; i++) {
- var n = m.addedNodes[i];
- if (n && n.tagName && (n.tagName === 'IMG' || n.tagName === 'SCRIPT' || n.tagName === 'IFRAME')) {
- var s = n.getAttribute && n.getAttribute('src');
- if (s && blocked(s)) { n.setAttribute('src', ''); if (n.parentNode) n.parentNode.removeChild(n); }
- }
- }
- });
- });
- obs.observe(document.documentElement || document, { childList: true, subtree: true });
- } catch (e) {}
- } catch (e) {}
- true;
-})();
-`;
export type TrackedWebViewProps = WebViewProps;
const TrackedWebView = forwardRef<WebView, TrackedWebViewProps>(function TrackedWebView(
diff --git a/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/1-browse.png b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/1-browse.png
new file mode 100644
index 00000000..fedd318f
Binary files /dev/null and b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/1-browse.png differ
diff --git a/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/2-saved.png b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/2-saved.png
new file mode 100644
index 00000000..6c5a03ee
Binary files /dev/null and b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/2-saved.png differ
diff --git a/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/3-map.png b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/3-map.png
new file mode 100644
index 00000000..28e8ca77
Binary files /dev/null and b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/3-map.png differ
diff --git a/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/4-alerts.png b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/4-alerts.png
new file mode 100644
index 00000000..53715f25
Binary files /dev/null and b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/4-alerts.png differ
diff --git a/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/5-browse-return.png b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/5-browse-return.png
new file mode 100644
index 00000000..fedd318f
Binary files /dev/null and b/apps/mobile/device-proof-evidence/BUILD3-stills-2026-09-04T13-10-12Z/5-browse-return.png differ
diff --git a/apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-BUILD3-2026-09-04T13-10-12Z.mov b/apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-BUILD3-2026-09-04T13-10-12Z.mov
new file mode 100644
index 00000000..0d8738a6
Binary files /dev/null and b/apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-BUILD3-2026-09-04T13-10-12Z.mov differ
diff --git a/apps/mobile/device-proof-evidence/privacy-sweep-BUILD3-2026-09-04T13-10-12Z.jpg b/apps/mobile/device-proof-evidence/privacy-sweep-BUILD3-2026-09-04T13-10-12Z.jpg
new file mode 100644
index 00000000..2d953ff1
Binary files /dev/null and b/apps/mobile/device-proof-evidence/privacy-sweep-BUILD3-2026-09-04T13-10-12Z.jpg differ
diff --git a/apps/mobile/lib/tracker-block.test.mjs b/apps/mobile/lib/tracker-block.test.mjs
new file mode 100644
index 00000000..4cfe4e5e
--- /dev/null
+++ b/apps/mobile/lib/tracker-block.test.mjs
@@ -0,0 +1,137 @@
+/**
+ * Behavioural tests for TRACKER_BLOCK — the script actually injected into every WebView.
+ *
+ * `tracker-policy.test.mjs` only covers isTrackerUrl(), which is the NATIVE-side URL
+ * filter. That left the thing doing most of the work — the injected page script that
+ * neuters gtag/fbq and intercepts fetch/XHR/Image/sendBeacon — with no test at all.
+ * Since the App Store "Data Not Collected" claim rests on this script actually working,
+ * "it looks right" was not good enough.
+ *
+ * These tests run the real script text in a sandbox with a fake window/document and
+ * assert observable behaviour: tracker requests are dropped, first-party requests are
+ * untouched.
+ */
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import vm from 'node:vm';
+
+import { buildTrackerBlock } from './tracker-block.ts';
+import { TRACKER_HOSTS } from './tracker-policy.ts';
+
+// Test the script exactly as shipped: built from the REAL host list.
+const TRACKER_BLOCK = buildTrackerBlock(TRACKER_HOSTS);
+
+const TRACKER = 'https://www.googletagmanager.com/gtag/js?id=G-ZGFNZ3RQ6S';
+const PIXEL = 'https://connect.facebook.net/en_US/fbevents.js';
+const FIRST_PARTY = 'https://homesonspec.com/api/listings';
+
+/** Build a minimal browser-ish sandbox, run TRACKER_BLOCK in it, return the context. */
+function runBlock() {
+ const calls = { fetch: [], beacon: [], xhrSend: [], imgSrc: [] };
+
+ class FakeXHR {
+ open(method, url) { this.__url = url; }
+ send() { calls.xhrSend.push(this.__url); }
+ }
+
+ class FakeImage {
+ constructor() { this._src = ''; }
+ set src(v) { this._src = v; calls.imgSrc.push(v); }
+ get src() { return this._src; }
+ }
+
+ const el = () => ({ _attrs: {}, setAttribute(k, v) { this._attrs[k] = v; }, getAttribute(k) { return this._attrs[k]; } });
+
+ const sandbox = {
+ calls,
+ Response: class { constructor(body, init) { this.body = body; this.status = init?.status ?? 200; } },
+ Promise,
+ URL,
+ XMLHttpRequest: FakeXHR,
+ MutationObserver: class { observe() {} },
+ console,
+ document: {
+ documentElement: {},
+ createElement: (tag) => ({ tagName: String(tag).toUpperCase(), ...el() }),
+ },
+ };
+ sandbox.window = sandbox;
+ sandbox.Image = FakeImage;
+ sandbox.HTMLImageElement = FakeImage;
+ sandbox.HTMLElement = function () {};
+ sandbox.location = { href: 'https://homesonspec.com/' };
+ sandbox.navigator = { sendBeacon: (url) => { calls.beacon.push(url); return true; } };
+ sandbox.fetch = (input) => { calls.fetch.push(input?.url ?? input); return Promise.resolve('REAL'); };
+
+ vm.createContext(sandbox);
+ vm.runInContext(TRACKER_BLOCK, sandbox);
+ return sandbox;
+}
+
+test('gtag / fbq / dataLayer are neutered into no-ops', () => {
+ const w = runBlock();
+ assert.equal(typeof w.gtag, 'function');
+ assert.equal(typeof w.fbq, 'function');
+ assert.doesNotThrow(() => w.gtag('event', 'page_view', { send_to: 'G-XXXX' }));
+ assert.doesNotThrow(() => w.fbq('track', 'PageView'));
+ assert.doesNotThrow(() => w.dataLayer.push({ event: 'x' }));
+ // dataLayer.push must not actually accumulate — a real GTM would drain it.
+ assert.equal(w.dataLayer.length, 0, 'dataLayer.push should be a no-op, not a real push');
+});
+
+test('fetch to a tracker host is dropped; first-party fetch passes through', async () => {
+ const w = runBlock();
+ const blocked = await w.fetch(TRACKER);
+ assert.equal(blocked.status, 204, 'tracker fetch should be short-circuited with an empty 204');
+ assert.deepEqual(w.calls.fetch, [], 'the real fetch must never see a tracker URL');
+
+ const real = await w.fetch(FIRST_PARTY);
+ assert.equal(real, 'REAL', 'first-party fetch must reach the real implementation');
+ assert.deepEqual(w.calls.fetch, [FIRST_PARTY]);
+});
+
+test('sendBeacon (GA4 default transport) is dropped for trackers only', () => {
+ const w = runBlock();
+ assert.equal(w.navigator.sendBeacon('https://region1.google-analytics.com/g/collect'), false);
+ assert.deepEqual(w.calls.beacon, [], 'no tracker beacon may reach the real sendBeacon');
+
+ assert.equal(w.navigator.sendBeacon(FIRST_PARTY), true);
+ assert.deepEqual(w.calls.beacon, [FIRST_PARTY]);
+});
+
+test('XHR to a tracker never sends; first-party XHR does', () => {
+ const w = runBlock();
+ const bad = new w.XMLHttpRequest();
+ bad.open('POST', PIXEL);
+ bad.send();
+ assert.deepEqual(w.calls.xhrSend, [], 'tracker XHR must not send');
+
+ const good = new w.XMLHttpRequest();
+ good.open('GET', FIRST_PARTY);
+ good.send();
+ assert.deepEqual(w.calls.xhrSend, [FIRST_PARTY]);
+});
+
+test('Image() pixel to a tracker is dropped; a first-party image still loads', () => {
+ const w = runBlock();
+ const px = new w.Image();
+ px.src = 'https://www.facebook.com/tr?id=123&ev=PageView';
+ assert.deepEqual(w.calls.imgSrc, [], 'the Facebook tr pixel must never get a real src');
+
+ const img = new w.Image();
+ img.src = 'https://homesonspec.com/hero.jpg';
+ assert.deepEqual(w.calls.imgSrc, ['https://homesonspec.com/hero.jpg']);
+});
+
+test('a subdomain of a tracker host is blocked too (not just the bare host)', async () => {
+ const w = runBlock();
+ const r = await w.fetch('https://analytics.google-analytics.com/g/collect?v=2');
+ assert.equal(r.status, 204);
+ assert.deepEqual(w.calls.fetch, []);
+});
+
+test('a lookalike domain is NOT blocked (the filter must not over-reach)', async () => {
+ const w = runBlock();
+ const r = await w.fetch('https://notgoogletagmanager.com/thing.js');
+ assert.equal(r, 'REAL', 'a lookalike host must pass through — over-blocking breaks real sites');
+});
diff --git a/apps/mobile/lib/tracker-block.ts b/apps/mobile/lib/tracker-block.ts
new file mode 100644
index 00000000..8ef053f6
--- /dev/null
+++ b/apps/mobile/lib/tracker-block.ts
@@ -0,0 +1,129 @@
+/**
+ * Runs BEFORE any page script. Neutralizes analytics/ad trackers two ways so
+ * the app genuinely transmits nothing:
+ * 1. Stub the JS APIs (gtag / dataLayer / fbq / google_tag_manager) into no-ops.
+ * 2. Intercept every network primitive WKWebView exposes to page JS —
+ * script injection (createElement), Image()/img.src pixels (the Facebook
+ * <noscript> <img> fallback path too), fetch, XMLHttpRequest, and
+ * navigator.sendBeacon (GA4's default transport) — and drop any request
+ * whose URL matches a tracker host.
+ * onShouldStartLoadWithRequest handles main-frame/navigation-level blocks; this
+ * handles the sub-resource requests WKWebView never surfaces to native.
+ */
+/**
+ * Built as a FUNCTION of the host list rather than importing it, so this module has
+ * zero imports and can be loaded directly by `node --test` (Node's ESM resolver wants
+ * explicit file extensions, which the TS/Metro resolver does not use). That is what
+ * lets `tracker-block.test.mjs` execute the real script text in a sandbox.
+ */
+export function buildTrackerBlock(hosts: readonly string[]): string {
+ return `
+(function() {
+ try {
+ var HOSTS = ${JSON.stringify(hosts)};
+ function blocked(u) {
+ try {
+ var hostname = new URL(String(u), window.location.href).hostname.toLowerCase();
+ for (var i = 0; i < HOSTS.length; i++) {
+ if (hostname === HOSTS[i] || hostname.slice(-(HOSTS[i].length + 1)) === '.' + HOSTS[i]) return true;
+ }
+ } catch (e) {}
+ return false;
+ }
+
+ // 1. Stub tracker JS APIs
+ var noop = function() {};
+ window.dataLayer = window.dataLayer || [];
+ window.dataLayer.push = noop;
+ window.gtag = noop;
+ window.ga = noop;
+ window.google_tag_manager = {};
+ window.fbq = function() {}; window.fbq.queue = []; window.fbq.loaded = true;
+ window._fbq = window.fbq;
+
+ // 2a. Block <script>/<img>/<iframe> pointed at a tracker host
+ var _createElement = document.createElement.bind(document);
+ document.createElement = function(tag) {
+ var el = _createElement(tag);
+ var t = String(tag || '').toLowerCase();
+ if (t === 'script' || t === 'img' || t === 'iframe') {
+ try {
+ var proto = Object.getPrototypeOf(el);
+ var desc = Object.getOwnPropertyDescriptor(proto, 'src')
+ || Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'src');
+ if (desc && desc.set) {
+ Object.defineProperty(el, 'src', {
+ configurable: true,
+ get: function() { return desc.get ? desc.get.call(el) : ''; },
+ set: function(v) { if (!blocked(v)) desc.set.call(el, v); }
+ });
+ }
+ } catch (e) {}
+ }
+ return el;
+ };
+
+ // 2b. Block Image()/new Image().src pixels (Facebook tr pixel, GA hits)
+ var _Image = window.Image;
+ if (_Image) {
+ window.Image = function() {
+ var img = new _Image();
+ try {
+ var d = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
+ if (d && d.set) {
+ Object.defineProperty(img, 'src', {
+ configurable: true,
+ get: function() { return d.get.call(img); },
+ set: function(v) { if (!blocked(v)) d.set.call(img, v); }
+ });
+ }
+ } catch (e) {}
+ return img;
+ };
+ }
+
+ // 2c. Block fetch
+ var _fetch = window.fetch;
+ if (_fetch) {
+ window.fetch = function(input) {
+ var url = (input && input.url) ? input.url : input;
+ if (blocked(url)) return Promise.resolve(new Response('', { status: 204 }));
+ return _fetch.apply(this, arguments);
+ };
+ }
+
+ // 2d. Block XHR
+ var _open = XMLHttpRequest.prototype.open;
+ XMLHttpRequest.prototype.open = function(method, url) {
+ this.__blocked = blocked(url);
+ return _open.apply(this, arguments);
+ };
+ var _send = XMLHttpRequest.prototype.send;
+ XMLHttpRequest.prototype.send = function() { if (this.__blocked) return; return _send.apply(this, arguments); };
+
+ // 2e. Block sendBeacon (GA4 default transport)
+ if (navigator.sendBeacon) {
+ var _beacon = navigator.sendBeacon.bind(navigator);
+ navigator.sendBeacon = function(url) { if (blocked(url)) return false; return _beacon.apply(this, arguments); };
+ }
+
+ // 2f. Strip any tracker <img>/<script> that slips into the DOM (e.g. <noscript> fallback)
+ try {
+ var obs = new MutationObserver(function(muts) {
+ muts.forEach(function(m) {
+ for (var i = 0; i < m.addedNodes.length; i++) {
+ var n = m.addedNodes[i];
+ if (n && n.tagName && (n.tagName === 'IMG' || n.tagName === 'SCRIPT' || n.tagName === 'IFRAME')) {
+ var s = n.getAttribute && n.getAttribute('src');
+ if (s && blocked(s)) { n.setAttribute('src', ''); if (n.parentNode) n.parentNode.removeChild(n); }
+ }
+ }
+ });
+ });
+ obs.observe(document.documentElement || document, { childList: true, subtree: true });
+ } catch (e) {}
+ } catch (e) {}
+ true;
+})();
+`;
+}
← 16d8e5af auto-data-snapshot: 2026-09-04T06:32:13 (1 data files) — app
·
back to Homesonspec
·
mobile: cap rendered map markers (120, densest-first) + lowe 4df903f1 →