Remove Facebook ads

Uses mutation observer to take action when elements added to page. Looks for ads based on Spon appearing in non hidden elements!

// ==UserScript==
// @name         Remove Facebook ads
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  Uses mutation observer to take action when elements added to page. Looks for ads based on Spon appearing in non hidden elements!
// @author       Skarn
// @match        https://www.facebook.com*
// ==/UserScript==

(function() {
    'use strict';

    //set up an observer object
    var observerOptions = {
        childList: true,
        subtree: true
    }

    var observer = new MutationObserver(callback);
    var processedNodes = new Set();
    var targetNode;

    //init
    trytryagain(init);

    function trytryagain(fnToTry){
        var tries = 10;
        var tryNumber = 0;
        var tryTimer;

        if(fnToTry.timer){
            window.clearTimeout(fnToTry.timer);
            fnToTry.timer = null;
        }

        var fnToTryWrapper = function(){
            tryNumber++;

            try{
                fnToTry();
            }catch(e){
                if(tryNumber > tries){
                    //give up.
                    return;
                }

                //try again
                fnToTry.timer = window.setTimeout(function(){
                    fnToTryWrapper();
                }, 2000);
            }
        };

        fnToTryWrapper();
    }

    function init(){
        //GM_addStyle(".userContentWrapper {opacity: 0; transition: opacity 0.1s;}");
        targetNode = document.getElementById("mainContainer");
        removeAds(targetNode);

        //observe
        trytryagain(restartObserver);

        //right column removal - more fiddly than worthwhile - might remove later or make optional
        document.querySelector('#rightCol').style.display = 'none';
        var content = document.querySelector('#contentArea') || document.querySelector('#timeline_story_column');
        if(content){
            content.style.left = '0px';
            content.style.setProperty("width", "calc(100% - 50px)", "important");
            content.style.setProperty("left", "0px", "important");
        }
    }

    function restartObserver(){
        targetNode = document.getElementById("mainContainer");
        observer.disconnect();
        observer.observe(targetNode, observerOptions);
    }

    function removeAds(addedNode){
        try{
            if(processedNodes.has(addedNode)){
                return;
            }

            processedNodes.add(addedNode);

            observer.disconnect();

            var interestingNodes = addedNode.querySelectorAll('.c_1kvvzw3p2b.v_1kvvzw3p30.o_1kvvzw3p2o')
            interestingNodes.forEach(function(item){
                var interestingCharacters = item.querySelectorAll('.c_1kvvzw3p2b');
                var visibleChars = '';
                if(interestingCharacters.length > 1){
                    interestingCharacters.forEach(function(char){
                        if(char.children.length === 0){
                            var stylePosition = window.getComputedStyle(char).position;
                            if(stylePosition !== 'absolute' && stylePosition !=='fixed'){
                                visibleChars = visibleChars + char.textContent;
                            }
                        }
                    });

                    var rx = /.*S.*p.*o.*n.*s.*o.*r.*e.*d.*/;
                    var match = visibleChars.match(rx);
                    if(match && match.length > 0){
                        var section = item.closest('.userContentWrapper');
                        if(section){
                            section.style.display = 'none';
                        }
                    }
                }


            });

            observer.observe(targetNode, observerOptions);
        }catch(e){
            //error during ad removal - skip it, but re-enable observer still
            observer.observe(targetNode, observerOptions);
        }
    }

    function callback(mutationList, observer) {
        //wipe ads out of whatever was added to the page
        mutationList.forEach((mutation) => {
            switch(mutation.type) {
                case 'childList':
                    var addedNodes = mutation.addedNodes;
                    if(addedNodes && addedNodes.length > 0){
                        var addedNode = addedNodes[0];
                        if(addedNode.nodeType === 1){
                            removeAds(addedNode);
                        }
                    }
                    break;
            }
        });

        //every time the page dom is modified, restart the observer
        //this is because clicking on linked pages etc. for some reason killed observer
        //doesn't hurt to reinitialise it every time.
        trytryagain(restartObserver);
    }
})();