Unfix

Stop fixed/sticky elements leeching my valuable screen real estate!

// ==UserScript==
// @name	Unfix
// @description	Stop fixed/sticky elements leeching my valuable screen real estate!
// @author	Remph
// @license	GPL-3.0-or-later
// @version	0.2.5
// @include	*
// @namespace	https://git.sr.ht/~remph/unfix.js
// @homepageURL	https://git.sr.ht/~remph/unfix.js
// @supportURL	https://git.sr.ht/~remph/unfix.js
// @grant	none
// @compatible	firefox
// ==/UserScript==

'use strict';

function unfix_element(e) {
	var newpos;
	switch (getComputedStyle(e).position) {
	case 'fixed':
		newpos = 'absolute';
		break;
	case 'sticky':
		newpos = 'relative';
	}
	if (newpos) {
//		console.debug('setting ' + e.tagName + ' to ' + newpos);
		e.style.setProperty('position', newpos, 'important');
		/* I would rather not have abused !important ^ here, to let
		   pages specify when the position really *is* important
		   and shouldn't be elided, but no-one can be responsible
		   with it so I will take it away */
	}
}

function unfix_tree(root) {
	if (root.nodeType !== Node.ELEMENT_NODE)
		return;
	unfix_element(root);
	// NodeFilter.SHOW_ATTRIBUTE ?
	var t = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
	while (t.nextNode())
		unfix_element(t.currentNode);
}

(function () {
	// wait for window to finish loading to prevent races on style
	window.addEventListener('load', () => unfix_tree(document.body));

	// Catch sneaky attempts to re-fix an element from js
	new MutationObserver((muts) => muts.forEach(function (mut) {
		/* Are these sure to be mutually exclusive? Can an
		   attributes change also have addedNodes? */
		switch (mut.type) {
		case 'attributes':
			unfix_element(mut.target); // if (mut.attributeName == 'style') ?
			break;
		case 'childList':
			mut.addedNodes.forEach(unfix_tree);
		}
	})).observe(document.body, {
		subtree: true, childList: true, attributes: true
	});
})();