Skip to content

Instantly share code, notes, and snippets.

@jgottula
Created November 17, 2023 08:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgottula/7829b077660d3c57e2d230dab55cd7e5 to your computer and use it in GitHub Desktop.
Save jgottula/7829b077660d3c57e2d230dab55cd7e5 to your computer and use it in GitHub Desktop.
Make the Reddit web experience on iPhone/iPad less miserable (RIP Apollo, fuck /u/spez)
// STOP FUCKING WITH MY REDDIT PAGES FROM SEARCH RESULTS!
// (remove referer from reddit links on google)
// for use with custom JS injection feature of StopTheMadness MobileSafari extension
'use strict';
const DEBUG = (str) => { console.debug(str); };
// generate a string of the path to the given element
// from the document root, in CSS selector form
const elem_path = (elem, arr = []) => {
const first = (arr.length == 0);
// REMOVE ME REMOVE ME REMOVE ME
// TypeError: undefined is not an object (evaluating 'elem.tagName.toLowerCase')
if (elem === undefined) DEBUG(`elem_path[${arr.length}]: elem undefined`);
if (elem.tagName === undefined) DEBUG(`elem_path[${arr.length}]: elem.tagName undefined`);
if (elem.tagName.toLowerCase === undefined) DEBUG(`elem_path[${arr.length}]: elem.tagName.toLowerCase undefined`);
// REMOVE ME REMOVE ME REMOVE ME
if (elem.id !== '') {
arr.push(`${elem.tagName.toLowerCase()}#${elem.id}`);
} else {
arr.push(elem.tagName.toLowerCase());
}
let str;
if (elem.parentElement !== null) {
str = elem_path(elem.parentElement, arr);
} else {
arr.reverse();
str = arr.join(' > ');
}
if (first) console.assert(elem.matches(str), elem);
return str;
};
// given an <a> element:
// - check if its href goes to reddit.com
// - set referrerpolicy: no-referrer
// - add to rel: noreferrer
const fixup = (elem) => {
console.assert(elem.tagName === 'A', elem.tagName);
if (elem.tagName !== 'A') return;
let url;
try {
url = new URL(elem.href);
} catch (e) {
DEBUG(`fixup('${elem.href}'): INVALID`);
return;
}
if (!url.hostname.match(/^(.*\.)?reddit\.com$/i)) {
DEBUG(`fixup('${elem.href}'): SKIP`);
return;
} else {
DEBUG(`fixup('${elem.href}'): MATCH`);
}
// attribute: referrerpolicy
const rp1 = elem.referrerPolicy;
// NOTE: only overwrite value if necessary!
// (otherwise we may infinitely loop mutations)
if (elem.referrerPolicy !== 'no-referrer') {
elem.referrerPolicy = 'no-referrer';
}
const rp2 = elem.referrerPolicy;
if (rp1 !== rp2) DEBUG(`fixup('${elem.href}'): referrerPolicy: '${rp1}' --> '${rp2}'`);
// attribute: rel
const r1 = elem.rel; const rl1 = elem.relList.value;
if (!elem.relList.contains('noreferrer')) {
elem.relList.add('noreferrer');
}
const r2 = elem.rel; const rl2 = elem.relList.value;
if (r1 !== r2) DEBUG(`fixup('${elem.href}'): rel: '${r1}' --> '${r2}'`);
if (rl1 !== rl2) DEBUG(`fixup('${elem.href}'): relList: [${rl1}] --> [${rl2}]`);
};
// do a one-time scan of ALL <a> elements in the document
const scan_all = () => {
for (const elem of document.getElementsByTagName('a')) {
fixup(elem);
}
};
// ongoing monitoring
const monitor = () => {
const observer = new MutationObserver((records, observer) => {
const elems = new Set();
DEBUG(`monitor: got ${records.length} records`);
for (const rec of records) {
DEBUG(`monitor: got '${rec.type}' record for: ${elem_path(rec.target)}`);
for (const elem of rec.addedNodes) { // childList
DEBUG(`monitor: added: ${elem_path(elem)} [href: ${elem.href}]`);
if (elem.tagName === 'a') elems.add(elem);
for (const child of elem.getElementsByTagName('a')) {
DEBUG(`monitor: added: child: ${elem_path(child)} [href: ${child.href}]`);
elems.add(child);
}
}
for (const elem of rec.removedNodes) { // childList
// TODO
// target
// previousSibling
// nextSibling
}
if (rec.attributeName !== null) { // attributes
DEBUG(`monitor: attr(${rec.attributeName}): ${elem_path(rec.target)} [href: ${rec.target.href}]`);
elems.add(rec.target);
// target
// attributeName
// attributeNamespace
// oldValue [maybe]
}
}
DEBUG(`monitor: collected ${elems.size} elements`);
for (const elem of elems) {
fixup(elem);
}
});
const options = {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['href', 'rel', 'referrerpolicy'],
attributeOldValue: false, // maybe?
characterData: false,
characterOldData: false,
};
// html > body > div#main
observer.observe(document.getElementById('main'), options);
};
// setup not-quite-initial scan
document.addEventListener('DOMContentLoaded', (event) => {
DEBUG(`==== DOMContentLoaded ====`);
scan_all();
});
// setup ongoing monitoring
monitor();
// initial scan
DEBUG(`==== INITIAL ====`);
scan_all();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment