jsd / Unfix Everything

// ==UserScript==
// @namespace    https://openuserjs.org/users/jsd
// @name         Unfix Everything
// @description  position:fixed is abused so much, we may as well abolish it by default.
// @author       jsd
// @license      MIT
// @version      0.4.2
// @grant        none
// @include      http*
// @exclude      *twitch.tv*
// @exclude      *aws.amazon.com*
// @exclude      *docs.google.com*
// @exclude      *online-go.com*
// ==/UserScript==

// This script attempts to detect and render un-fixed in position any menu bars and
// the like that stick to the top or bottom of web pages, because in almost all cases
// I'd rather not waste vertical space in the browser viewport.

// Use at your own risk, obviously.  In some cases you may not see "we use cookies"
// warnings or other vitally important information.

(function() {
    'use strict';

    // Set allow_sidebars to 0 if you want side-bars and other tall things to be unfixed.
    // There is less to gain and more potential problems with these elements, so it's 1 by default.
    // Warning:  Haven't tested this in a while...
    var allow_sidebars = 1;

    // Takes an element list, un-fixes it.
    function z_unfix(els)
    {
        if (typeof els === "undefined")
        {
            return;
        }
        for(var i = 0; i < els.length; i++)
        {
            if (!els[i])
                continue;

            // Get position attribute from style
            var style = null;
            try {
                style = window.getComputedStyle(els[i]);
            } catch(err) {
                style = null;
            }
            if (!style)
            {
                style = els[i].style;
                if (typeof style === "undefined" || style === null)
                    continue;
            }

            // the default is static
            var pos = style.getPropertyValue('position');
            if (typeof pos === "undefined")
                pos = "static";

            // for Washington Post
            if (els[i].nodeName === "amp-img" || els[i].tagName === "amp-img")
                pos = "relative";

            // for bugzilla.mozilla.org
            // ... an annoying one.  We need a more general fix for this style.
            if (els[i].id === "wrapper" && els[i].children && els[i].children[0] && els[i].children[0].id==="header")
                els[i].style.setProperty("overflow", "auto", "important");
            if (els[i].id === "bugzilla-body")
            {
                els[i].style.setProperty("overflow-y", "visible", "important");
                els[i].style.setProperty("overflow-x", "visible", "important");
                continue;
            }

            // So we must deal with it.
            if (els[i].style && ((pos === "fixed") || (pos === "sticky")))
            {
                var rect = els[i].getBoundingClientRect();
                var cHeight = document.documentElement.clientHeight;
                var cWidth = document.documentElement.clientHeight;
                var bDontHide = 0;
                var bUnsure = 0;
              	var scrollPos = window.scrollY || window.scrollTop || document.getElementsByTagName("body")[0].scrollTop;

                // For quadrazid.com, and anyone else helpful enough to say "modal"
                if (rect.height == 0)
                {
                    if (els[i].className && (els[i].className.indexOf("modal") >= 0))
                    {
                        console.log("modal");
                        continue;
                    }
                }

                // for twitter
                if (els[i].className && (els[i].className.indexOf("allery") >= 0 || els[i].className.indexOf("verlay") >=0))
                {
                    continue;
                }

                // allow sidebars and other tall elements if that's turned on
                if (rect.height > (cHeight * 0.7))
                {
                    if (allow_sidebars)
                    {
                        console.log("pass");
                        continue;
                    }
                }

                // don't do elements that are vertically centred, as it tends to break things.
                // ... mostly those stupid "Give us your email" forms that pop up.
                else if (Math.abs((rect.top + (rect.height / 2)) - (cHeight / 2)) < 30 )
                {
                    continue;
                }

                // stuff meant to stick to bottom of window is a problem, so let's not even
                // display it, if it's not too tall.  It's mostly "We use cookies" notices.
                else if (Math.abs(rect.bottom - cHeight) < 3)
                {
                    if (rect.height < 150 && rect.height > 0)
                        els[i].style.setProperty("display", "none");
                }

                // If it's near the bottom (or off the screen as at zomato.com)...
                // Catches, for example, animated bottom bars that slide up.
                else if (rect.top > cHeight - 30)
                {
                    if (!bDontHide)
                        els[i].style.setProperty("display", "none");
                }

                // Added this for nytimes.com's latest promotional nonsense.
                else if ((rect.top > cHeight - 150) && (rect.width > cWidth / 3) && !bDontHide)
                {
                    els[i].style.setProperty("display", "none");
                }

                console.log('unfix ' + rect.top + ' ' + rect.height + ' ' + scrollPos + ' ' + els[i].id + ' ' + els[i].className);

                // Not sure how to best choose between static, absolute, relative...
                if (pos === "fixed")
                {
                    pos = ((rect.height > 0) ? "absolute" : "static");
                } else if (pos === "sticky")
                    pos = "relative";
                if (bUnsure)
                    els[i].style.setProperty("position", pos);
                else
                    els[i].style.setProperty("position", pos, "important");
            }
        }
    }

    // unfix stylesheets, needed for rules not applied until after page load
    function unfix_sheet(sheet)
    {
        if (!sheet)
            return;
        var rules = sheet.cssRules;
        if (!rules || !rules.length)
            return;
        for (var j = 0; j < rules.length; j++)
        {
            var rule = rules[j];
            if (typeof rule === "undefined")
                continue;
            // console.log(rule.selectorText);
            var st = rule.selectorText;
            if (!st)
                continue;

            if (st.indexOf("ytd") > 0)  // try not to break youtube too much
                continue;

            var flag = 0;
            if (rule.type === CSSRule.IMPORT_RULE)
            {
                unfix_sheet(rule.getStyleSheet());
            }
            else //if (rule.type === CSSRule.STYLE_RULE)
            {
                // fix bad layout on imdark.com while we're in here
                if (st.indexOf("myplayers") >= 0)
                    flag=1;

                // catches one or two...
                if (st.indexOf("sticky") >= 0)
                    flag = 1;

                // catches quite a few ... hopefully not too many.
                else if (st.indexOf("secondary") >= 0 || st.indexOf("snap") >= 0 || st.indexOf("header") >= 0)
                {
                    var sv;
                    if (rule.style && (sv = rule.style.getPropertyValue("position")))
                    {
                        if (sv === "fixed" || sv === "sticky")
                            flag = 1;
                    }
                    if (rule.cssText && rule.cssText.indexOf("fixed") >= 0)
                        flag = 1;
                }

                if (flag)
                {
                    //console.log("delete: " + st);
                    sheet.deleteRule(j);
                    j--;
                }
            }
            // if (rule.style.getPropertyValue("position") === "fixed")
            // ... too complicated.
        }
    }

    // necessary for the more stubborn cases.
    function recursive_unfix(x)
    {
        if (!x)
            return;
        z_unfix([x]);
        if (x.children)
            for (var l = 0; l < x.children.length; l++)
                recursive_unfix(x.children[l]);
    }

    // watch for dynamic page changes
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    var obs;
    function re_observe()
    {
            obs.observe( document.getElementsByTagName("body")[0], { childList:true, subtree:true, attributes:true});
    }

    // on any browsers with no MutationObserver, we will succeed less often
    if (MutationObserver)
    {
        obs = new MutationObserver(function(mutations, observer)
        {
            for (var j=0; j < mutations.length; j++)
            {
                if (mutations[j].type === "attributes"  && mutations[j].target)
                {
                    var style = window.getComputedStyle(mutations[j].target);
                    if (style)
                    {
                        var s = style.getPropertyValue("position");
                        if (s !== "fixed" && s !== "sticky")
                            continue;
                    }
                    z_unfix([mutations[j].target]);
                }
                else if (mutations[j].type === "childList" && mutations[j].addedNodes.length > 0)
                {
                    for (var k = 0; k < mutations[j].addedNodes.length; k++)
                    {
                        //var el = mutations[j].addedNodes[k].parentElement;  // excessive
                        var el = mutations[j].addedNodes[k];

                        // This seems effective.
                        if (!el)
                            el = mutations[j].addedNodes[k];
                        recursive_unfix(el);
                    }
                }
            }
            re_observe();
        });
        obs.observe( document.getElementsByTagName("body")[0], { childList:true, subtree:true, attributes:true});
    }

    console.log("Unfix");

    // unfix things on load
    z_unfix(document.getElementsByTagName("*"));

    // still-experimental stylesheet munging -- seems not to break too much, now.
    var sheets = document.styleSheets;
    if (sheets)
        for (var j=0; j < sheets.length; j++)
        {
            try {
                unfix_sheet(sheets[j]);
            }catch (err){
                console.log("css exception: " + err.message);
            }
        }

})();