Greasy Fork is available in English.

Block reddit click tracking

Stops reddit from tracking your inbound and outbound clicks

// ==UserScript==
// @name        Block reddit click tracking
// @namespace   mjhcfwlmjfzg778evppa995xavvt2nmb
// @description Stops reddit from tracking your inbound and outbound clicks
// @license     MIT
// @match       *://*.reddit.com/*
// @version     1.2
// @grant       unsafeWindow
// @run-at      document-start
// ==/UserScript==

(function () {
	"use strict";

	const expiredDate = Date.now().toString();
	const targetLink = Symbol();


	// Capturing event listener on the outermost element.
	// Runs FIRST, before any others.
	const beforeClick = function (event) {
		let target = event.target;

		// Did we click on an element?
		if (target.nodeType === 1) {
			//  Is that element inside a link?
			target = target.closest("a");

			if (target) {
				// Stash link in the event object so that
				// the second handler doesn't have to dig
				// through the DOM with .closest() again
				event[targetLink] = target;

				if (target.dataset.hrefUrl) {
					// Remove link tracking attributes
					delete target.dataset.inboundUrl;
					delete target.dataset.outboundUrl;

					// Mark outbound link as expired so reddit code
					// does not try to use it
					if (target.dataset.outboundExpiration) {
						target.dataset.outboundExpiration = expiredDate;
					}
				}
			}
		}
	};


	// Bubbling event listener on the outermost element.
	// Runs LAST, just before the click goes through
	// (unless reddit code adds any others afterwards)
	const justBeforeClick = function (event) {
		const target = event[targetLink];

		// If reddit event handlers modified the link, change it back
		if (target && target.dataset.hrefUrl) {
			target.href = target.dataset.hrefUrl;
		}
	};


	// Mark both event listeners as passive so they
	// won't impact scroll performance
	const doCapture = { capture: true,  passive: true };
	const doBubble  = { capture: false, passive: true };

	window.addEventListener("mousedown",  beforeClick, doCapture);
	window.addEventListener("keydown",    beforeClick, doCapture);
	window.addEventListener("touchstart", beforeClick, doCapture);

	window.addEventListener("mousedown",  justBeforeClick, doBubble);
	window.addEventListener("keydown",    justBeforeClick, doBubble);
	window.addEventListener("touchstart", justBeforeClick, doBubble);


	// Patch navigator.sendBeacon() if it is available
	if (typeof unsafeWindow.Navigator.prototype.sendBeacon === "function") {
		// Firefox/Greasemonkey defines exportFunction to make
		// sandboxed functions callable by the page script.
		const exportFunction = (window.exportFunction) ? window.exportFunction : (f) => f;

		// Fake sendBeacon() function that does nothing
		Reflect.defineProperty(unsafeWindow.Navigator.prototype, "sendBeacon", {
			value: exportFunction(function sendBeacon(url) { return true; }, unsafeWindow)
		});
	}
})();