RU AdList JS Fixes

try to take over the world!

Version au 29/07/2017. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         RU AdList JS Fixes
// @namespace    ruadlist_js_fixes
// @version      20170729.0
// @description  try to take over the world!
// @author       lainverse & dimisa
// @match        *://*/*
// @grant        unsafeWindow
// @grant        window.close
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    let win = (unsafeWindow || window),
        // http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
        isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0,
        isChrome = !!window.chrome && !!window.chrome.webstore,
        isSafari = (Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 ||
                    (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window.safari || safari.pushNotification)),
        isFirefox = typeof InstallTrigger !== 'undefined',
        inIFrame = (win.self !== win.top),
        _getAttribute = Element.prototype.getAttribute,
        _setAttribute = Element.prototype.setAttribute,
        _de = document.documentElement,
        _appendChild = Document.prototype.appendChild.bind(_de),
        _removeChild = Document.prototype.removeChild.bind(_de),
        _createElement = Document.prototype.createElement.bind(document);

    // NodeList iterator polyfill (mostly for Safari)
    // https://jakearchibald.com/2014/iterators-gonna-iterate/
    if (!NodeList.prototype[Symbol.iterator]) {
        NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    }

    // Options
    let opts = {
        'useWSIFunc': useWSI
    };

    {
        let optsCall = function(callback)
        {
            // Register event listener
            let key = "optsCallEvent_" + Math.random().toString(36).substr(2),
                cb = callback.func.bind(callback.name);
            window.addEventListener(key, cb, false);
            // Generate and dispatch synthetic event
            let ev = document.createEvent("HTMLEvents");
            ev.initEvent(key, true, false);
            window.dispatchEvent(ev);
            // Remove listener
            window.removeEventListener(key, cb, false);
        };

        let initOptsHandler = function()
        {
            /*jshint validthis:true */
            opts[this] = GM_getValue(this, true);
            if (opts[this])
                opts[this+'Func']();
        };

        optsCall({
            func: initOptsHandler,
            name: 'useWSI'
        });

        // show options page
        let openOptions = function()
        {
            let ovl = _createElement('div'),
                inner = _createElement('div');
            ovl.style = (
                'position: fixed;'+
                'top:0; left:0;'+
                'bottom: 0; right: 0;'+
                'background: rgba(0,0,0,0.85);'+
                'z-index: 2147483647;'+
                'padding: 5em'
            );
            inner.style = (
                'background: whitesmoke;'+
                'font-size: 10pt;'+
                'color: black;'+
                'padding: 1em'
            );
            inner.textContent = 'JS Fixes Options: (reload page to apply)';
            inner.appendChild(_createElement('br'));
            inner.appendChild(_createElement('br'));
            ovl.addEventListener(
                'click', function(e)
                {
                    if (e.target === ovl) {
                        ovl.parentNode.removeChild(ovl);
                        e.preventDefault();
                    }
                    e.stopPropagation();
                }, false
            );
            // append checkbox with label function
            function addCheckbox(optName, optLabel)
            {
                let c = _createElement('input'),
                    l = _createElement('label');
                c.type = 'checkbox';
                c.id = optName;
                optsCall({
                    func: function()
                    {
                        c.checked = GM_getValue(this);
                    },
                    name: optName
                });
                c.addEventListener(
                    'click', function(e)
                    {
                        optsCall({
                            func:function(){
                                GM_setValue(this, e.target.checked);
                                opts[this] = e.target.checked;
                            },
                            name:optName
                        });
                    }, true
                );
                l.textContent = optLabel;
                l.setAttribute('for', optName);
                inner.appendChild(c);
                inner.appendChild(l);
                inner.appendChild(_createElement('br'));
            }
            // append checkboxes
            addCheckbox('useWSI', 'Use WebSocket filter. Disable if experience problems with WebSocket connections.');
            document.body.appendChild(ovl);
            ovl.appendChild(inner);
        };

        // monitor keys pressed for Ctrl+Alt+Shift+J > s > f code
        let opPos = 0, opKey = ['KeyJ','KeyS','KeyF'];
        document.addEventListener(
            'keydown', function(e)
            {
                if ((e.code === opKey[opPos] || e.location) &&
                    (!!opPos || e.altKey && e.ctrlKey && e.shiftKey))
                {
                    opPos += e.location ? 0 : 1;
                    e.stopPropagation();
                    e.preventDefault();
                } else {
                    opPos = 0;
                }
                if (opPos === opKey.length)
                {
                    opPos = 0;
                    openOptions();
                }
            }, false
        );
    }

    // Special wrapper script to run scripts designed to override standard DOM functions
    // In Firefox appends supplied script to a page to make it run in page context and let
    // page content access overridden functions. In other browsers just run it as-is.
    function scriptLander(func, prepend)
    {
        if (!isFirefox)
        {
            func();
            return;
        }
        let script = _createElement('script');
        script.textContent = '!function(){let win=window;' + (
            prepend instanceof Function && prepend ||
            prepend instanceof Array && prepend.join('') || ''
        ) + '!' + func + '();}();';
        _appendChild(script);
        _removeChild(script);
    }

    function nullTools() {
        /*jshint validthis:true */
        let nt = this;
        nt.define = function(obj, prop, val)
        {
            Object.defineProperty(
                obj, prop, {
                    get: ()  => val,
                    set: (v) => v,
                    enumerable: true
                }
            );
        };
        nt.proxy = function(obj)
        {
            return new Proxy(
                obj, {
                    get: (t, p) => t[p],
                    set: (t, p, v) => v
                }
            );
        };
        nt.func = (val) => () => val;
    }

    // Fake objects of advertisement networks to break their workflow
    scriptLander(
        function()
        {
            let l = window.location;
            if (/^([^.]+\.)*?google\./i.test(l.host) ||
                // Google likes to define odd global variables like Ya
                (/^([^.]+\.)*?yandex\./i.test(l.host) &&
                 /^\/(search|images|video)/.test(l.pathname)))
                // Also, Yandex uses their Ya object for a lot of things on their pages and
                // wrapping it may cause problems. It's better to skip it in some cases.
                return;

            let nt = new nullTools();
            // Yandex API (ADBTools, Metrika)
            let Ya = {};
            nt.define(Ya, 'ADBTools', function(){
                for (let name of ['loadContext', 'testAdbStyle'])
                    this[name] = nt.func(null);
                this.getCurrentState = nt.func(true);
                return nt.proxy(this);
            });
            nt.define(Ya, 'adfoxCode', nt.proxy({
                create: nt.func(null),
                createScroll: nt.func(null)
            }));
            nt.define(Ya, 'Context', nt.proxy({
                _callbacks: { push: nt.func(null) }
            }));
            let Metrika = function(){
                for (let name of ['reachGoal', 'replacePhones', 'trackLinks', 'hit', 'params'])
                    this[name] = nt.func(null);
                this.id = 0;
                return nt.proxy(this);
            };
            Metrika.counters = () => Ya._metrika.counters;
            nt.define(Ya, 'Metrika', Metrika);
            let counter = new Ya.Metrika();
            nt.define(Ya, '_metrika', nt.proxy({
                counter: counter,
                counters: [counter],
                hitParam: {},
                counterNum: 0,
                hitId: 0,
                v: 1
            }));
            nt.define(Ya, '_globalMetrikaHitId', 0);
            nt.define(win, 'Ya', Ya);
            // Yandex.Metrika callbacks
            let yandex_metrika_callbacks = [];
            yandex_metrika_callbacks.push = function(callback)
            {
                document.addEventListener(
                    'DOMContentLoaded',
                    (e) => callback.call(window, e),
                    false
                );
            }
            nt.define(win, 'yandex_metrika_callbacks', yandex_metrika_callbacks);
        }, nullTools
    );

    // Creates and return protected style (unless protection is manually disabled).
    // Protected style will re-add itself on removal and remaind enabled on attempt to disable it.
    function createStyle(rules, props, skip_protect)
    {
        props = props || {};
        props.type = 'text/css';

        function _protect(style)
        {
            if (skip_protect)
                return;

            Object.defineProperty(style, 'sheet', {
                value: null,
                enumerable: true
            });
            Object.defineProperty(style, 'disabled', {
                get: () => true, //pretend to be disabled
                set: () => null,
                enumerable: true
            });
            (new MutationObserver(
                (ms) => _removeChild(ms[0].target)
            )).observe(style, { childList: true });
        }


        function _create()
        {
            let style = _appendChild(_createElement('style'));
            Object.assign(style, props);

            function insertRules(rule)
            {
                if (rule.forEach)
                    rule.forEach(insertRules);
                else try {
                    style.sheet.insertRule(rule, 0);
                } catch (e) {
                    console.error(e);
                }
            }

            insertRules(rules);
            _protect(style);

            return style;
        }

        let style = _create();
        if (skip_protect)
            return style;

        function resolveInANewContext(resolve)
        {
            setTimeout(
                (resolve) => resolve(_create()),
                0, resolve
            );
        }

        (new MutationObserver(
            function(ms)
            {
                let m, node;
                for (m of ms) for (node of m.removedNodes)
                    if (node === style)
                        (new Promise(resolveInANewContext))
                        .then((st) => (style = st));
            }
        )).observe(_de, { childList: true });

        return style;
    }

    // https://greatest.deepsurf.us/scripts/19144-websuckit/
    function useWSI()
    {
        // check does browser support Proxy and WebSocket
        if (typeof Proxy !== 'function' ||
            typeof WebSocket !== 'function')
            return;

        function getWrappedCode(removeSelf)
        {
            let text = getWrappedCode.toString() + WSI.toString();
            text = (
                '(function(){"use strict";'+
                text.replace(/\/\/[^\r\n]*/g,'').replace(/[\s\r\n]+/g,' ')+
                '(new WSI(self||window)).init();'+
                (removeSelf?'let s = document.currentScript; if (s) {s.parentNode.removeChild(s);}':'')+
                '})();\n'
            );
            return text;
        }

        function WSI(win, safeWin)
        {
            safeWin = safeWin || win;
            let masks = [], filter;
            for (filter of [// blacklist
                '||185.87.50.147^',
                '||10root25.website^', '||24video.xxx^',
                '||adlabs.ru^', '||adspayformymortgage.win^', '||aviabay.ru^',
                '||bgrndi.com^', '||brokeloy.com^',
                '||cnamerutor.ru^',
                '||docfilms.info^', '||dreadfula.ru^',
                '||et-code.ru^',
                '||franecki.net^', '||film-doma.ru^',
                '||free-torrent.org^', '||free-torrent.pw^',
                '||free-torrents.org^', '||free-torrents.pw^',
                '||game-torrent.info^', '||gocdn.ru^',
                '||hdkinoshka.com^', '||hghit.com^', '||hindcine.net^',
                '||kiev.ua^', '||kinotochka.net^',
                '||kinott.com^', '||kinott.ru^', '||kuveres.com^',
                '||lepubs.com^', '||luxadv.com^', '||luxup.ru^', '||luxupcdna.com^',
                '||mail.ru^', '||marketgid.com^', '||mixadvert.com^', '||mxtads.com^',
                '||nickhel.com^',
                '||oconner.biz^', '||oconner.link^', '||octoclick.net^', '||octozoon.org^',
                '||pkpojhc.com^',
                '||psma01.com^', '||psma02.com^', '||psma03.com^',
                '||recreativ.ru^', '||redtram.com^', '||regpole.com^', '||rootmedia.ws^', '||ruttwind.com^',
                '||skidl.ru^',
                '||torvind.com^', '||traffic-media.co^', '||trafmag.com^',
                '||webadvert-gid.ru^', '||webadvertgid.ru^',
                '||xxuhter.ru^',
                '||yuiout.online^',
                '||zoom-film.ru^'])
                masks.push(new RegExp(
                    filter.replace(/([\\\/\[\].*+?(){}$])/g, '\\$1')
                    .replace(/\^(?!$)/g,'\\.?[^\\w%._-]')
                    .replace(/\^$/,'\\.?([^\\w%._-]|$)')
                    .replace(/^\|\|/,'^(ws|http)s?:\\/+([^\/.]+\\.)*'),
                    'i'));

            function isBlocked(url) {
                for (let mask of masks)
                    if (mask.test(url))
                        return true;
                return false;
            }

            let realWebSocket = win.WebSocket;
            function wsGetter(target, name)
            {
                try {
                    if (typeof realWebSocket.prototype[name] === 'function')
                    {
                        if (name === 'close' || name === 'send') // send also closes connection
                            target.readyState = realWebSocket.CLOSED;
                        return (
                            function fake() {
                                console.log('[WSI] Invoked function "'+name+'"', '| Tracing', (new Error()));
                                return;
                            }
                        );
                    }
                    if (typeof realWebSocket.prototype[name] === 'number')
                        return realWebSocket[name];
                } catch(ignore) {}
                return target[name];
            }

            function createWebSocketWrapper(target)
            {
                return new Proxy(realWebSocket, {
                    construct: function (target, args)
                    {
                        let url = args[0];
                        console.log('[WSI] Opening socket on ' + url + ' \u2026');
                        if (isBlocked(url))
                        {
                            console.log("[WSI] Blocked.");
                            return new Proxy({
                                url: url,
                                readyState: realWebSocket.OPEN
                            }, {
                                get: wsGetter,
                                set: (val) => val
                            });
                        }
                        return new target(args[0], args[1]);
                    }
                });
            }

            function WorkerWrapper()
            {
                let realWorker = win.Worker;
                win.Worker = function Worker() {
                    let isBlobURL = /^blob:/i,
                        resourceURI = arguments[0],
                        deepLogMode = false,
                        _callbacks = new WeakMap(),
                        _worker = null,
                        _onevs = { names: ['onmessage', 'onerror'] },
                        _actions = [],
                        /*jshint validthis:true */
                        _self = this;

                    function log()
                    {
                        if (deepLogMode)
                            console.log.apply(this, arguments);
                    }

                    function callbackWrapper(func)
                    {
                        if (typeof func !== 'function')
                            return undefined;

                        return function callback()
                        {
                            return func.apply(_self, arguments);
                        };
                    }

                    function updateWorker()
                    {
                        for (let [action, name, args] of _actions) {
                            log(_worker, action, name, args);
                            if (action === 'set')
                                _worker[name] = callbackWrapper(args);
                            if (action === 'call')
                                _worker[name].apply(_worker, args);
                        }
                        _actions.length = 0;
                        log('Applied buffered actions.');
                    }

                    for (let prop of _onevs.names)
                        Object.defineProperty(_self, prop, {
                            set: function(val) {
                                _onevs[prop] = val;
                                if (_worker)
                                    _worker[prop] = callbackWrapper(val);
                                else {
                                    _actions.push(['set', prop, val]);
                                    log('Stored into buffer:', arguments);
                                }
                                return val;
                            },
                            get: () => _onevs[prop],
                            enumerable: true
                        });

                    _self.postMessage = function()
                    {
                        if (_worker)
                            _worker.postMessage.apply(_worker, arguments);
                        else {
                            _actions.push(['call', 'postMessage', arguments]);
                            log('Stored into buffer:', arguments);
                        }
                    };
                    _self.terminate = function()
                    {
                        if (_worker)
                            _worker.terminate();
                        else {
                            _actions.push(['call','terminate', arguments]);
                            log('Stored into buffer:', arguments);
                        }
                    };
                    _self.addEventListener = function(event, callback, other)
                    {
                        if (typeof callback !== 'function')
                            return;

                        if (!_callbacks.has(callback))
                            _callbacks.set(callback, callbackWrapper(callback));

                        arguments[1] = _callbacks.get(callback);
                        if (_worker)
                            _worker.addEventListener.apply(_worker, arguments);
                        else {
                            _actions.push(['call', 'addEventListener', arguments]);
                            log('Stored into buffer:', arguments);
                        }
                    };
                    _self.removeEventListener = function(event, callback, other)
                    {
                        if (typeof callback !== 'function' || !_callbacks.has(callback))
                            return;

                        arguments[1] = _callbacks.get(callback);
                        _callbacks.delete(callback);
                        if (_worker)
                            _worker.removeEventListener.apply(_worker, arguments);
                        else {
                            _actions.push(['call', 'removeEventListener', arguments]);
                            log('Stored into buffer:', arguments);
                        }
                    };

                    if (!isBlobURL.test(resourceURI))
                    {
                        _worker = new realWorker(resourceURI);
                        return; // not a blob, no need to wrap
                    }

                    (new Promise(
                        function(resolve, reject)
                        {
                            let xhr = new XMLHttpRequest();
                            xhr.responseType = 'blob';
                            try {
                                xhr.open('GET', resourceURI, true);
                            } catch(e) {
                                return reject(e);
                            }
                            if (xhr.readyState !== XMLHttpRequest.OPENED) {
                                // connection wasn't opened, unable to continue wrapping procedure
                                return reject(xhr.readyState);
                            }
                            xhr.onload = function(e)
                            {
                                if (e.target.status === 200)
                                {
                                    let reader = new FileReader();
                                    reader.addEventListener(
                                        'loadend', function(e)
                                        {
                                            resolve(
                                                new realWorker(URL.createObjectURL(
                                                    new Blob([getWrappedCode(false) + e.target.result])
                                                ))
                                            );
                                        }, false
                                    );
                                    reader.readAsText(e.target.response);
                                } else {
                                    return reject(e);
                                }
                            };
                            xhr.onerror = (e) => reject(e);
                            xhr.send();
                        }
                    )).then(
                        function(val)
                        {
                            _worker = val;
                            updateWorker();
                        }
                    ).catch(
                        function(e)
                        {
                            // connection were blocked by CSP or something else triggered error event on xhr object
                            // unable to proceed with wrapper, return object as-is
                            _worker = new realWorker(resourceURI);
                            updateWorker();
                        }
                    );

                    if (deepLogMode)
                    {
                        return new Proxy(_self, {
                            get: function(target, prop) {
                                console.log('Worker _get_', prop);
                                return target[prop];
                            },
                            set: function(target, prop, val) {
                                console.log('Worker _set_', prop, '_to_', val);
                                target[prop] = val;
                                return val;
                            }
                        });
                    }
                }.bind(safeWin);
            }

            function CreateElementWrapper()
            {
                let key = '_'+Math.random().toString(36).substr(2),
                    _createElement = Document.prototype.createElement,
                    _addEventListener = Element.prototype.addEventListener,
                    isDataURL = /^data:/i,
                    isBlobURL = /^blob:/i;

                // IFrame SRC get/set wrapper
                let ifGetSet = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'src');
                if (ifGetSet)
                {
                    let code = encodeURIComponent('<scr'+'ipt>'+getWrappedCode(true)+'</scr'+'ipt>\n'),
                        dataSrc = new WeakMap(),
                        _ifSet = ifGetSet.set,
                        _ifGet = ifGetSet.get;
                    ifGetSet.set = function(val)
                    {
                        if (this[key] && val === dataSrc.get(this))
                        { // if already processed data URL then do nothing
                            delete this[key];
                            return null;
                        }
                        let isData = isDataURL.test(val);
                        if (isData && val.indexOf(code) < 0)
                        {
                            dataSrc.set(this, val);
                            val = val.replace(',',',' + code);
                        }
                        if (!isData && dataSrc.get(this))
                            dataSrc.delete(this);
                        return _ifSet.call(this, val);
                    };
                    ifGetSet.get = function()
                    {
                        return dataSrc.get(this) || _ifGet.call(this);
                    };
                    Object.defineProperty(HTMLIFrameElement.prototype, 'src', ifGetSet);
                }

                function frameSetWSWrapper(e)
                {
                    let frm = e.target;
                    try {
                        if (!frm.src || isBlobURL.test(frm.src))
                            frm.contentWindow.WebSocket = createWebSocketWrapper();
                    } catch (ignore) {}
                }

                let scriptMap = new WeakMap();
                scriptMap.isBlocked = isBlocked;
                let onErrorWrapper = {
                    set: function(val)
                    {
                        if (scriptMap.has(this))
                        {
                            this.removeEventListener('error', scriptMap.get(this).wrp, false);
                            scriptMap.delete(this);
                        }
                        if (!val || typeof val !== 'function')
                            return val;

                        scriptMap.set(this, {
                            org: val,
                            wrp: function()
                            {
                                if (scriptMap.isBlocked(this.src))
                                    console.log('[WSI] Blocked "onerror" callback from', this);
                                else
                                    scriptMap.get(this).org.apply(this, arguments);
                            }
                        });
                        this.addEventListener('error', scriptMap.get(this).wrp, false);

                        return val;
                    },
                    get: function()
                    {
                        return scriptMap.has(this) ? scriptMap.get(this).org : null;
                    },
                    enumerable: true
                };
                Document.prototype.createElement = function createElement(name) {
                    /*jshint validthis:true */
                    let el = _createElement.apply(this, arguments);

                    if (el.tagName === 'IFRAME')
                        _addEventListener.call(el, 'load', frameSetWSWrapper, false);
                    if (el.tagName === 'SCRIPT')
                        Object.defineProperty(el, 'onerror', onErrorWrapper);

                    return el;
                };

                document.addEventListener(
                    'DOMContentLoaded', function()
                    {
                        for (let ifr of document.querySelectorAll('IFRAME'))
                        {
                            if (isDataURL.test(ifr.src))
                            {
                                ifr[key] = true;
                                ifr.src = ifr.src; // call setter and let it do the job
                            }
                            _addEventListener.call(ifr, 'load', frameSetWSWrapper, false);
                        }
                    }, false
                );
            }

            this.init = function()
            {
                win.WebSocket = createWebSocketWrapper();
                if (!(/firefox/i.test(navigator.userAgent))) // skip WorkerWrapper in Firefox
                    (new Promise(
                        function(resolve, reject)
                        { // test is it possible to run inline scripts
                            if (self.constructor.name.indexOf('Worker') > -1)
                                return resolve(); // running within a Worker
                            let onerr = window.onerror,
                                onscr = (e) => resolve();
                            // for some reason addEventListener on 'error' doesn't catch this error
                            window.onerror = (e) => reject(e);
                            window.addEventListener('inlineSuccess', onscr, false);
                            let scr = document.createElement('script');
                            scr.textContent = "window.dispatchEvent(new Event('inlineSuccess'));";
                            document.documentElement.appendChild(scr);
                            document.documentElement.removeChild(scr);
                            window.removeEventListener('inlineSuccess', onscr, false);
                            window.onerror = onerr;
                        }
                    )).then(
                        (e) => WorkerWrapper()
                    ).catch(
                        (e) => console.log('[WSI] Unable to create inline script. Skipping Worker wrapper to avoid further issues.', e)
                    );
                if (typeof document !== 'undefined')
                    CreateElementWrapper();
            };
        }

        if (isFirefox)
        {
            let script = _createElement('script');
            script.textContent = getWrappedCode(true);
            _appendChild(script);
            _removeChild(script);
            return; //we don't want to call functions on page from here in Fx, so exit
        }

        (new WSI((unsafeWindow||self||window),(self||window))).init();
    }

    if (!isFirefox)
    { // scripts for non-Firefox browsers
        // https://greatest.deepsurf.us/scripts/14720-it-s-not-important
        {
            let imptt = /((display|(margin|padding)(-top|-bottom)?)\s*:[^;!]*)!\s*important/ig,
                ret_b = (a,b) => b,
                _toLowerCase = String.prototype.toLowerCase,
                protectedNodes = new WeakSet(),
                log = false;

            let logger = function()
            {
                if (log)
                    console.log('Some page elements became a bit less important.');
                log = false;
            };

            let unimportanter = function(node)
            {
                let style = (node.nodeType === Node.ELEMENT_NODE) ?
                    _getAttribute.call(node, 'style') : null;

                if (!style || !imptt.test(style) || node.style.display === 'none' ||
                    (node.src && node.src.slice(0,17) === 'chrome-extension:')) // Web of Trust IFRAME and similar
                    return false; // get out if we have nothing to do here

                protectedNodes.add(node);
                _setAttribute.call(node, 'style',
                                   style.replace(imptt, ret_b));
                log = true;
            };

            (new MutationObserver(
                function(mutations)
                {
                    setTimeout(
                        function(ms)
                        {
                            let m, node;
                            for (m of ms) for (node of m.addedNodes)
                                unimportanter(node);
                            logger();
                        }, 0, mutations
                    );
                }
            )).observe(document, {
                childList : true,
                subtree : true
            });

            Element.prototype.setAttribute = function setAttribute(name, value)
            {
                "[native code]";
                let replaced = value;
                if (_toLowerCase.call(name) === 'style' && protectedNodes.has(this))
                    replaced = value.replace(imptt, ret_b);
                log = (replaced !== value);
                logger();
                return _setAttribute.call(this, name, replaced);
            };

            win.addEventListener (
                'load', function()
                {
                    for (let imp of document.querySelectorAll('[style*="!"]'))
                        unimportanter(imp);
                    logger();
                }, false
            );
        }

        // Naive ABP Style protector
        {
            let _querySelector = Document.prototype.querySelector.bind(document);
            let _removeChild = Node.prototype.removeChild;
            let _appendChild = Node.prototype.appendChild;
            let createShadow = () => _createElement('shadow');
            // Prevent adding fake content entry point
            Node.prototype.appendChild = function(child)
            {
                if (this instanceof ShadowRoot &&
                    child instanceof HTMLContentElement)
                    return _appendChild.call(this, createShadow());
                return _appendChild.apply(this, arguments);
            };
            {
                let _shadowSelector = ShadowRoot.prototype.querySelector;
                let _innerHTML = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'innerHTML');
                let _parentNode = Object.getOwnPropertyDescriptor(Node.prototype, 'parentNode');
                if (_innerHTML && _parentNode)
                {
                    let _set = _innerHTML.set;
                    let _getParent = _parentNode.get;
                    _innerHTML.configurable = false;
                    _innerHTML.set = function()
                    {
                        _set.apply(this, arguments);
                        let content = _shadowSelector.call(this, 'content');
                        if (content)
                        {
                            let parent = _getParent.call(content);
                            _removeChild.call(parent, content);
                            _appendChild.call(parent, createShadow());
                        }
                    };
                }
                Object.defineProperty(ShadowRoot.prototype, 'innerHTML', _innerHTML);
            }
            // Locate and apply extra protection to a style on top of what ABP does
            let style;
            (new Promise(
                function(resolve, reject)
                {
                    let getStyle = () => _querySelector('::shadow style');
                    style = getStyle();
                    if (style)
                        return resolve(style);
                    let intv = setInterval(
                        function()
                        {
                            style = getStyle();
                            if (!style)
                                return;
                            intv = clearInterval(intv);
                            return resolve(style);
                        }, 0
                    );
                    document.addEventListener(
                        'DOMContentLoaded',
                        function()
                        {
                            if (intv)
                                clearInterval(intv);
                            style = getStyle();
                            return style ? resolve(style) : reject();
                        },
                        false
                    );
                }
            )).then(
                function(style)
                {
                    let emptyArr = [],
                        nullStr = {
                            get: () => '',
                            set: (x) => x
                        };
                    Object.defineProperties(style, {
                        innerHTML: nullStr,
                        textContent: nullStr
                    });
                    Object.defineProperties(style.sheet, {
                        deleteRule: () => null,
                        cssRules: emptyArr,
                        rules: emptyArr
                    });
                }
            ).catch(()=>null);
            Node.prototype.removeChild = function(child)
            {
                if (child === style)
                    return;
                return _removeChild.apply(this, arguments);
            };
        }
    }

    if (/^https?:\/\/(mail\.yandex\.|music\.yandex\.|news\.yandex\.|(www\.)?yandex\.[^\/]+\/(yand)?search[\/?])/i.test(win.location.href))
        // https://greatest.deepsurf.us/en/scripts/809-no-yandex-ads
        document.addEventListener(
            'DOMContentLoaded', function()
            {
                let adWords = [/Яндекс.Директ/i, /Реклама/i, /Ad/i],
                    genericAdSelectors = (
                        '.serp-adv__head + .serp-item,'+
                        '#adbanner,'+
                        '.serp-adv,'+
                        '.b-spec-adv,'+
                        'div[class*="serp-adv__"]:not(.serp-adv__found):not(.serp-adv__displayed)'
                    );
                // Generic ads removal and fixes
                {
                    let node = document.querySelector('.serp-header');
                    if (node)
                        node.style.marginTop = '0';
                    for (node of document.querySelectorAll(genericAdSelectors))
                        remove(node);
                }
                // Short name for parentNode.removeChild
                function remove(node) {
                    node.parentNode.removeChild(node);
                }
                // Search ads
                function removeSearchAds()
                {
                    let node, subNode, content;
                    for (node of document.querySelectorAll('.t-construct-adapter__legacy'))
                    {
                        subNode = node.querySelector('.organic__subtitle');
                        if (subNode)
                            content = window.getComputedStyle(subNode, ':after').content.replace(/"/g,'');
                        if (subNode && content && adWords.map((expr)=>expr.test(content)).indexOf(true) > -1)
                        {
                            remove(node);
                            console.log('Ads removed.');
                        }
                    }
                }
                // News ads
                function removeNewsAds()
                {
                    for (let node of document.querySelectorAll(
                        '.page-content__left > *,'+
                        '.page-content__right > *:not(.page-content__col),'+
                        '.page-content__right > .page-content__col > *'
                    ))
                        if (adWords[0].test(node.textContent) ||
                            (node.clientHeight < 15 && node.classList.contains('rubric')))
                        {
                            remove(node);
                            console.log('Ads removed.');
                        }
                }
                // Music ads
                function removeMusicAds()
                {
                    for (let node of document.querySelectorAll('.ads-block'))
                        remove(node);
                }
                // Mail ads
                function removeMailAds()
                {
                    let slice = Array.prototype.slice,
                        nodes = slice.call(document.querySelectorAll('.ns-view-folders')),
                        node, len, cls;

                    for (node of nodes)
                        if (!len || len > node.classList.length)
                            len = node.classList.length;

                    node = nodes.pop();
                    while (node)
                    {
                        if (node.classList.length > len)
                            for (cls of slice.call(node.classList))
                                if (cls.indexOf('-') === -1)
                                {
                                    remove(node);
                                    break;
                                }
                        node = nodes.pop();
                    }
                }
                // News fixes
                function removePageAdsClass()
                {
                    if (document.body.classList.contains("b-page_ads_yes"))
                    {
                        document.body.classList.remove("b-page_ads_yes");
                        console.log('Page ads class removed.');
                    }
                }
                // Function to attach an observer to monitor dynamic changes on the page
                function pageUpdateObserver(func, obj, params) {
                    if (obj)
                        (new MutationObserver(func))
                        .observe(obj, (params || { childList:true, subtree:true }));
                }

                if (win.location.hostname.search(/^mail\./i) === 0) {
                    pageUpdateObserver(
                        function(ms, o)
                        {
                            let aside = document.querySelector('.mail-Layout-Aside');
                            if (aside) {
                                o.disconnect();
                                pageUpdateObserver(removeMailAds, aside);
                            }
                        }, document.body
                    );
                    removeMailAds();
                } else if (win.location.hostname.search(/^music\./i) === 0) {
                    pageUpdateObserver(removeMusicAds, document.querySelector('.sidebar'));
                    removeMusicAds();
                } else if (win.location.hostname.search(/^news\./i) === 0) {
                    pageUpdateObserver(removeNewsAds, document.body);
                    pageUpdateObserver(removePageAdsClass, document.body, { attributes:true, attributesFilter:['class'] });
                    removeNewsAds();
                    removePageAdsClass();
                } else {
                    pageUpdateObserver(removeSearchAds, document.querySelector('.main__content'));
                    removeSearchAds();
                }
            }
        );

    // Yandex Link Tracking
    if (/^https?:\/\/([^.]+\.)*yandex\.[^\/]+/i.test(win.location.href))
    {
        let fakeRoot = {
            appendChild: ()=>null,
            firstChild: null
        };
        Element.prototype.createShadowRoot = () => fakeRoot;
        Object.defineProperty(Element.prototype, "shadowRoot", {
            value: fakeRoot,
            enumerable: true,
            configurable: false
        });
        // Partially based on https://greatest.deepsurf.us/en/scripts/22737-remove-yandex-redirect
        let selectors = (
            'A[onmousedown*="/jsredir"],'+
            'A[data-vdir-href],'+
            'A[data-counter]'
        );
        let removeTrackingAttributes = function(link)
        {
            link.removeAttribute('onmousedown');
            if (link.hasAttribute('data-vdir-href')) {
                link.removeAttribute('data-vdir-href');
                link.removeAttribute('data-orig-href');
            }
            if (link.hasAttribute('data-counter')) {
                link.removeAttribute('data-counter');
                link.removeAttribute('data-bem');
            }
        };
        let removeTracking = function(scope)
        {
            for (let link of scope.querySelectorAll(selectors))
                removeTrackingAttributes(link);
        };
        document.addEventListener('DOMContentLoaded', (e) => removeTracking(e.target));
        (new MutationObserver(
            function(ms)
            {
                let m, node;
                for (m of ms) for (node of m.addedNodes) if (node.nodeType === Node.ELEMENT_NODE)
                    if (node.tagName === 'A' && node.matches(selectors)) {
                        removeTrackingAttributes(node);
                    } else {
                        removeTracking(node);
                    }
            }
        )).observe(_de, { childList: true, subtree: true });

        //skip fixes for other sites
        return;
    }

    // https://greatest.deepsurf.us/en/scripts/21937-moonwalk-hdgo-kodik-fix v0.8 (adapted)
    document.addEventListener(
        'DOMContentLoaded', function()
        {//createPlayer();
            function log (e) {
                console.log('Player FIX: Detected', e, 'player in', win.location.href);
            }
            if (win.adv_enabled !== undefined && win.condition_detected !== undefined)
            {
                log('Moonwalk');
                if (win.adv_enabled)
                    win.adv_enabled = false;
                win.condition_detected = false;
                if (win.MXoverrollCallback)
                    document.addEventListener(
                        'click', function catcher(e)
                        {
                            e.stopPropagation();
                            win.MXoverrollCallback.call(window);
                            document.removeEventListener('click', catcher, true);
                        }, true
                    );
            }
            else if (win.stat_url !== undefined && win.is_html5 !== undefined && win.is_wp8 !== undefined)
            {
                log('HDGo');
                document.body.onclick = null;
                let tmp = document.querySelector('#swtf');
                if (tmp)
                    tmp.style.display = 'none';
                if (win.banner_second !== undefined)
                    win.banner_second = 0;
                if (win.$banner_ads !== undefined)
                    win.$banner_ads = false;
                if (win.$new_ads !== undefined)
                    win.$new_ads = false;
                if (win.createCookie !== undefined)
                    win.createCookie('popup', 'true', '999');
                if (win.canRunAds !== undefined && win.canRunAds !== true)
                    win.canRunAds = true;
            }
            else if (win.MXoverrollCallback && win.iframeSearch !== undefined)
            {
                log('Kodik');
                let tmp = document.querySelector('.play_button');
                if (tmp)
                    tmp.onclick = win.MXoverrollCallback.bind(window);
                win.IsAdBlock = false;
            }
            else if (win.getnextepisode && win.uppodEvent)
            {
                log('Share-Serials.net');
                scriptLander(
                    function()
                    {
                        let _setInterval = win.setInterval,
                            _setTimeout = win.setTimeout;
                        win.setInterval = function(func)
                        {
                            if (func instanceof Function && func.toString().indexOf('_delay') > -1)
                            {
                                let intv = _setInterval.call(
                                    this, function()
                                    {
                                        _setTimeout.call(
                                            this, function(intv)
                                            {
                                                clearInterval(intv);
                                                let timer = document.querySelector('#timer');
                                                if (timer)
                                                    timer.click();
                                            }, 100, intv);
                                        func.call(this);
                                    }, 5
                                );

                                return intv;
                            }
                            return _setInterval.apply(this, arguments);
                        };
                        win.setTimeout = function(func) {
                            if (func instanceof Function && func.toString().indexOf('adv_showed') > -1)
                            {
                                return _setTimeout.call(this, func, 0);
                            }
                            return _setTimeout.apply(this, arguments);
                        };
                    }
                );
            }
        }, false
    );

    // piguiqproxy.com circumvention prevention
    scriptLander(
        function()
        {
            let _open = XMLHttpRequest.prototype.open;
            let blacklist = /[/.@]piguiqproxy\.com[:/]/i;
            XMLHttpRequest.prototype.open = function(method, url)
            {
                if (method === 'GET' && blacklist.test(url))
                {
                    this.send = () => null;
                    console.log('Blocked request: ', url);
                    return;
                }
                return _open.apply(this, arguments);
            }
        }
    );

    // === Helper functions ===

    // function to search and remove nodes by content
    // selector - standard CSS selector to define set of nodes to check
    // words - regular expression to check content of the suspicious nodes
    // params - object with multiple extra parameters:
    //   .log - display log in the console
    //   .hide - set display to none instead of removing from the page
    //   .parent - parent node to remove if content is found in the child node
    //   .siblings - number of simling nodes to remove (excluding text nodes)
    let scRemove = (node) => node.parentNode.removeChild(node);
    let scHide = function(node)
    {
        let style = _getAttribute.call(node, 'style') || '',
            hide = ';display:none!important;';
        if (style.indexOf(hide) < 0)
            _setAttribute.call(node, 'style', style + hide);
    };
    function scissors (selector, words, scope, params)
    {
        if (params.log)
            console.log('[s] starting with', selector, words, scope, JSON.stringify(params));
        let remFunc = (params.hide ? scHide : scRemove),
            iterFunc = (params.siblings > 0 ? 'nextSibling' : 'previousSibling'),
            toRemove = [],
            siblings;
        for (let node of scope.querySelectorAll(selector))
        {
            if (params.log)
                console.log('[s] found node', node);
            if (params.parent)
            {
                while(node !== scope && !(node.matches(params.parent)))
                    node = node.parentNode;
                if (params.log)
                    console.log('[s] moving to parent node', node);
                if (node === scope)
                {
                    if (params.log)
                        console.log('[s] reached scope node, nothing to remove here.');
                    break;
                }
            }
            if (words.test(node.innerHTML) || !node.childNodes.length)
            {
                // drill up to the specified parent node if required
                if (toRemove.indexOf(node) === -1)
                {
                    if (params.log)
                        console.log('[s] adding node into list for removal');
                    toRemove.push(node);
                    // add multiple nodes if defined more than one sibling
                    siblings = Math.abs(params.siblings) || 0;
                    while (siblings)
                    {
                        node = node[iterFunc];
                        if (node.nodeType === Node.ELEMENT_NODE)
                        {
                            if (params.log)
                                console.log('[s] adding sibling node', node);
                            toRemove.push(node);
                            siblings -= 1; //count only element nodes
                        }
                        else if (!params.hide)
                        {
                            if (params.log)
                                console.log('[s] adding sibling node', node);
                            toRemove.push(node);
                        }
                    }
                } else {
                    if (params.log)
                        console.log('[s] node already marked for removal');
                }
            } else {
                if (params.log)
                    console.log('[s] word test failed, proceed to the next node');
            }
        }
        if (params.log)
            console.log('[s] proceeding with', (params.hide?'hide':'removal'), 'of', toRemove);
        for (let node of toRemove)
            remFunc(node);

        return toRemove.length;
    }

    // function to perform multiple checks if ads inserted with a delay
    // by default does 30 checks withing a 3 seconds unless nonstop mode specified
    // also does 1 extra check when a page completely loads
    // selector and words - passed dow to scissors
    // params - object with multiple extra parameters:
    //   .log - display log in the console
    //   .root - selector to narrow down scope to scan;
    //   .observe - if true then check will be performed continuously;
    // Other parameters passed down to scissors.
    function gardener(selector, words, params)
    {
        params = params || {};
        if (params.log)
            console.log('[g] starting with', selector, words, JSON.stringify(params));
        let scope = document,
            nonstop = false;
        // narrow down scope to a specific element
        if (params.root)
        {
            scope = scope.querySelector(params.root);
            if (!scope) // exit if the root element is not present on the page
                return 0;
            if (params.log)
                console.log('[g] scope', scope);
        }
        // add observe mode if required
        if (params.observe)
        {
            if (typeof MutationObserver === 'function')
            {
                (new MutationObserver(
                    function(ms)
                    {
                        for (let m of ms) if (m.addedNodes.length)
                            scissors(selector, words, scope, params);
                    }
                )).observe(scope, { childList:true, subtree: true });
                if (params.log)
                    console.log('[g] observer enabled');
            } else {
                nonstop = true;
                if (params.log)
                    console.log('[g] nonstop mode enabled');
            }
        }
        // wait for a full page load to do one extra cut
        win.addEventListener(
            'load', function()
            {
                if (params.log)
                    console.log('[g] onload cleanup');
                scissors(selector, words, scope, params);
            }
        );
        // do multiple cuts during page load until ads removed
        function cut(sci, s, w, sc, p, i)
        {
            if (i > 0)
                i -= 1;
            if (i && !sci(s, w, sc, p))
                setTimeout(cut, 100, sci, s, w, sc, p, i);
        }
        cut(scissors, selector, words, scope, params, (nonstop ? -1 : 30));
    }

    // Helper function to close background tab if site opens itself in a new tab and then
    // loads a 3rd-party page in the background one (thus performing background redirect).
    function preventBackgroundRedirect()
    {
        // create "cose_me" event to call high-level window.close()
        let key = Math.random().toString(36).substr(2);
        window.addEventListener('close_me_' + key, () => window.close());

        // window.open wrapper
        function pbrLander()
        {
            let _open = window.open,
                idx = String.prototype.indexOf,
                event = new CustomEvent("close_me_%key%", {});
            // site went to a new tab and attempts to unload
            // call for high-level close through event
            let closeWindow = () => window.dispatchEvent(event);

            // window.open wrapper
            window.open = function open()
            {
                console.log(arguments, window.location.host);
                if (arguments[0] &&
                    (idx.call(arguments[0], window.location.host) > -1 ||
                     idx.call(arguments[0], '://') === -1))
                    window.addEventListener('unload', closeWindow, true);
                _open.apply(window, arguments);
            }.bind(window);

            // Node.createElement wrapper to prevent click-dispatch in Google Chrome and similar browsers
            let _createElement = Document.prototype.createElement;
            Document.prototype.createElement = function createElement(name)
            {
                /*jshint validthis:true */
                let el = _createElement.apply(this, arguments);
                if (el.tagName === 'A')
                    el.addEventListener(
                        'click', function(e)
                        {
                            if (!e.target.parentNode || !e.isTrusted)
                                window.addEventListener('unload', closeWindow, true);
                        }, false
                    );
                return el;
            };
            console.log("Background redirect prevention enabled.");
        }

        // land wrapper on the page
        let script = document.createElement('script');
        script.textContent = '('+pbrLander.toString().replace(/%key%/g,key)+')();';
        _appendChild(script);
        _removeChild(script);
    }

    // Function to catch and block various methods to open a new window with 3rd-party content.
    // Some advertisement networks went way past simple window.open call to circumvent default popup protection.
    // This funciton blocks window.open, ability to restore original window.open from an IFRAME object,
    // ability to perform an untrusted (not initiated by user) click on a link, click on a link without a parent
    // node or simply a link with piece of javascript code in the HREF attribute.
    function preventPopups()
    {
        if (inIFrame)
        {
            let i = -1, val;
            do {
                i++;
                val = GM_getValue('forbid.popups.' + i);
            } while(val & val !== win.location.href);
            GM_setValue('forbid.popups.' + i, win.location.href);
            win.top.postMessage('forbid.popups.' + i, '*');
            return;
        }

        scriptLander(
            function()
            {
                let _createElement = Document.prototype.createElement,
                    _appendChild = Element.prototype.appendChild;

                function open()
                {
                    '[native code]';
                    console.log('Site attempted to open a new window', arguments);
                    return {
                        document: {
                            write: () => {},
                            writeln: () => {}
                        }
                    };
                }

                function redefineOpen(obj)
                {
                    Object.defineProperty(obj, 'open', {
                        get: () => open,
                        set: (val) => val,
                        enumerable: true
                    });
                }
                redefineOpen(win);

                Document.prototype.createElement = function createElement(name)
                {
                    /*jshint validthis:true */
                    let el = _createElement.apply(this, arguments);
                    if (el.tagName === 'A')
                        el.addEventListener(
                            'click', function(e)
                            {
                                if (!e.target.parentNode || !e.isTrusted ||
                                    (e.target.href && e.target.href.toLowerCase().indexOf('javascript') > -1))
                                {
                                    e.preventDefault();
                                    console.log('Blocked suspicious click event', e, 'on', e.target);
                                }
                            }, false
                        );
                    if (el.tagName === 'IFRAME')
                        el.addEventListener(
                            'load', function(e)
                            {
                                try {
                                    redefineOpen(e.target.contentWindow);
                                } catch(ignore) {}
                            }, false
                        );
                    return el;
                };

                Element.prototype.appendChild = function appendChild()
                {
                    /*jshint validthis:true */
                    let el = _appendChild.apply(this, arguments);
                    if (el && el.nodeType === Node.ELEMENT_NODE && el.tagName === 'IFRAME') {
                        try {
                            redefineOpen(el.contentWindow);
                        } catch(ignore) {}
                    }
                    return el;
                };
                console.log('Popup prevention enabled.');
            }
        );
    }
    // External listener for case when site known to open popups were loaded in iframe
    // It will sandbox any iframe which will send message 'forbid.popups' (preventPopups sends it)
    // Some sites replace frame's window.location with data-url to run in clean context
    if (!inIFrame)
    {
        let popWindows = new WeakSet();
        window.addEventListener(
            'message', function(e)
            {
                if (typeof e.data === "string" && e.data.slice(0,13) === 'forbid.popups' &&
                    !popWindows.has(e.source))
                {
                    let src = GM_getValue(e.data);
                    if (src)
                        GM_deleteValue(e.data);
                    popWindows.add(e.source); // remember window of iframe with suspected domain
                    for (let frame of document.querySelectorAll('iframe'))
                        if (frame.contentWindow === e.source)
                        {
                            if (frame.hasAttribute('sandbox'))
                                // remove allow-popups if frame already sandboxed
                                frame.sandbox.remove('allow-popups');
                            else
                                // set sandbox mode for troublesome frame and allow scripts, forms and a few other actions
                                // technically allowing both scripts and same-origin allows removal of the sandbox attribute,
                                // but to apply content must be reloaded and this script will re-apply it in the result
                                frame.setAttribute('sandbox','allow-forms allow-scripts allow-presentation allow-top-navigation allow-same-origin');
                            console.log('Disallowed popups from iframe', frame);

                            // reload frame content to apply restrictions
                            if (!src) {
                                src = frame.src;
                                console.log('Unable to get current iframe location, reloading from src', src);
                            } else
                                console.log('Reloading iframe with URL', src);
                            frame.src = 'about:blank';
                            frame.src = src;
                        }
                }
            }, false
        );
    }

    // Currently unused piece of code developed to prevent site from registering serviceWorker
    // and uninstall any existing instances of serivceWorker in case there is one already.
    /* Commented out since not used
    function forbidServiceWorker()
    {
        if (!("serviceWorker" in navigator))
            return;
        let svr = navigator.serviceWorker.ready;
        Object.defineProperty(navigator, 'serviceWorker', {
            value: {
                register: function()
                {
                    console.log('Registration of serviceWorker ' + arguments[0] + ' blocked.');
                    return new Promise(function(){});
                },
                ready: new Promise(() => null),
                addEventListener: () => null
            }
        });
        document.addEventListener(
            'DOMContentLoaded', function()
            {
                if (!svr)
                    return;
                svr.then(
                    function(sw)
                    {
                        console.log('Found existing serviceWorker:', sw);
                        console.log('Attempting to unregister...');
                        sw.unregister().then(
                            () => console.log('Done.')
                        ).catch(
                            function(err)
                            {
                                console.log('Unregistration failed. :(', err);
                                console.log('Try to remove it manually:');
                                console.log(' 1. Open: chrome://serviceworker-internals/ (Google Chrome and alike) or about:serviceworkers (Mozilla Firefox) in a new tab.');
                                console.log(' 2. Search there for one with "'+document.domain+'" in the name.');
                                console.log(' 3. Use buttons in the same block with service you found to stop it and uninstall/unregister.');
                            }
                        );
                    }
                ).catch(
                    (e) => console.log("LOL, existing serviceWorker failed on it's own! -_-", e)
                );
            }, false
        );
    }
    /**/

    // Currently obsolete code developed to prevent error and load calls on objects supposed to load resources
    // from the internet like IMG or IFRAME, but missing SRC/HREF attribute. Usually tricks like this are used
    // to unwrap wrapped functions to be able to load ads.
    /* Commented out since not used
    function errorAndLoadEventsFilter()
    {
        let toString = Function.prototype.toString,
            _addEventListener = Element.prototype.addEventListener,
            _removeEventListener = Element.prototype.removeEventListener,
            hasAttribute = Element.prototype.hasAttribute,
            evtMap = new WeakMap();
        Element.prototype.addEventListener = function addEventListener(evt, func, capt) {
            if ((evt === 'error' || evt === 'load') && !evtMap.get(func))
            {
                evtMap.set(
                    func, function()
                    {
                        if (hasAttribute.call(this, 'src') ||
                            hasAttribute.call(this, 'href'))
                            func.apply(this, arguments);
                        else
                            console.log('Blocked', evt, 'handler', toString.call(func), 'on', this);
                    }
                );
            }
            _addEventListener.call(this, evt, (evtMap.get(func) || func), capt);
        };
        Element.prototype.removeEventListener = function removeEventListener(evt, func, capt) {
            _removeEventListener.call(this, evt, (evtMap.get(func) || func), capt);
        };
        Object.defineProperty(HTMLElement.prototype, 'onload', {
            set: function(func)
            {
                if(evtMap.has(this)) {
                    if (evtMap.get(this).onload)
                        _removeEventListener.call(this, 'load', evtMap.get(this).onload, false);
                    evtMap.get(this).onload = func;
                } else
                    evtMap.set(this, { onload: func });

                if (func)
                    _addEventListener.call(this, 'load', func, false);

                return func;
            },
            get: function()
            {
                return evtMap.has(this) ? evtMap.get(this).onload : null;
            }
        });
        Object.defineProperty(HTMLElement.prototype, 'onerror', {
            set: function(func)
            {
                if (evtMap.has(this))
                    evtMap.get(this).onerror = func;
                else
                    evtMap.set(this, { onerror: func });

                if (func)
                    console.log('Blocked error handler', toString.call(func), 'on', this);

                return func;
            },
            get: function()
            {
                return evtMap.has(this) ? evtMap.get(this).onerror : null;
            }
        });
    }
    /**/

    // === Scripts for specific domains ===

    let scripts = {};
    // prevent popups and redirects block
    // Popups
    scripts.preventPopups = {
        other: [
            'biqle.ru',
            'chaturbate.com',
            'dfiles.ru',
            'hentaiz.org',
            'mirrorcreator.com',
            'online-multy.ru', 'openload.co',
            'radikal.ru',
            'seedoff.cc', 'seedoff.tv',
            'tapochek.net', 'thepiratebay.org', 'torseed.net',
            'unionpeer.com',
            'zippyshare.com'
        ],
        now: preventPopups
    };
    // Background redirects
    scripts.preventBackgroundRedirect = {
        other: [
            'mediafire.com', 'megapeer.org', 'megapeer.ru',
            'perfectgirls.net',
            'turbobit.net'
        ],
        now: preventBackgroundRedirect
    };

    // other
    scripts['4pda.ru'] = {
        now: function()
        {
            // https://greatest.deepsurf.us/en/scripts/14470-4pda-unbrender
            let hStyle,
                isForum = document.location.href.search('/forum/') !== -1,
                remove = (node) => (node ? node.parentNode.removeChild(node) : null),
                afterClean = () => remove(hStyle);

            function beforeClean()
            {
                // attach styles before document displayed
                hStyle = createStyle([
                    'html { overflow-y: scroll }',
                    'section[id] {'+(
                        'position: absolute;'+
                        'width: 100%'
                    )+'}',
                    'article + aside * { display: none !important }',
                    '#header + div:after {'+(
                        'content: "";'+
                        'position: fixed;'+
                        'top: 0;'+
                        'left: 0;'+
                        'width: 100%;'+
                        'height: 100%;'+
                        'background-color: #E6E7E9'
                    )+'}',
                    // http://codepen.io/Beaugust/pen/DByiE
                    '@keyframes spin { 100% { transform: rotate(360deg) } }',
                    'article + aside:after {'+(
                        'content: "";'+
                        'position: absolute;'+
                        'width: 150px;'+
                        'height: 150px;'+
                        'top: 150px;'+
                        'left: 50%;'+
                        'margin-top: -75px;'+
                        'margin-left: -75px;'+
                        'box-sizing: border-box;'+
                        'border-radius: 100%;'+
                        'border: 10px solid rgba(0, 0, 0, 0.2);'+
                        'border-top-color: rgba(0, 0, 0, 0.6);'+
                        'animation: spin 2s infinite linear'
                    )+'}'
                ], {id:'ubrHider'}, true);

                // display content of a page if time to load a page is more than 2 seconds to avoid
                // blocking access to a page if it is loading for too long or stuck in a loading state
                setTimeout(2000, afterClean);
            }

            createStyle([
                '#nav .use-ad { display: block !important }',
                'article:not(.post) + article:not(#id),'+
                'html:not(#id)>body:not(#id) a[target="_blank"] img[height="90"] { display: none !important }'
            ]);

            if (!isForum)
                beforeClean();

            // save links to non-overridden functions to use later
            let protectedElems;
            // protect/hide changed attributes in case site attempt to restore them
            function styleProtector(eventMode)
            {
                let _toLowerCase = String.prototype.toLowerCase,
                    isStyleText = (t) => (_toLowerCase.call(t) === 'style'),
                    protectedElems = new WeakMap();
                function protoOverride(element, functionName, isStyleCheck, returnIfProtected)
                {
                    let originalFunction = element.prototype[functionName];
                    element.prototype[functionName] = function wrapper()
                    {
                        if (protectedElems.has(this) && isStyleCheck(arguments[0]))
                            return returnIfProtected(this, arguments);
                        return originalFunction.apply(this, arguments);
                    };
                }
                protoOverride(Element, 'removeAttribute', isStyleText, () => undefined);
                protoOverride(Element, 'hasAttribute', isStyleText, (_this) => protectedElems.get(_this) !== null);
                protoOverride(Element, 'setAttribute', isStyleText, (_this, args) => protectedElems.set(_this, args[1]));
                protoOverride(Element, 'getAttribute', isStyleText, (_this) => protectedElems.get(_this));
                if (!eventMode)
                    return protectedElems;
                else
                {
                    let e = document.createEvent('Event');
                    e.initEvent('protoOverride', false, false);
                    window.protectedElems = protectedElems;
                    window.dispatchEvent(e);
                }
            }
            if (!isFirefox)
                protectedElems = styleProtector(false);
            else
            {
                let script = document.createElement('script');
                script.textContent = '(' + styleProtector.toString() + ')(true);';
                window.addEventListener(
                    'protoOverride', function protoOverrideCallback(e)
                    {
                        if (win.protectedElems) {
                            protectedElems = win.protectedElems;
                            delete win.protectedElems;
                        }
                        document.removeEventListener('protoOverride', protoOverrideCallback, true);
                    }, true
                );
                _appendChild(script);
                _removeChild(script);
            }

            // clean a page
            window.addEventListener(
                'DOMContentLoaded', function()
                {
                    let width = () => window.innerWidth || _de.clientWidth || document.body.clientWidth || 0;
                    let height = () => window.innerHeight || _de.clientHeight || document.body.clientHeight || 0;

                    if (isForum)
                    {
                        let si = document.querySelector('#logostrip');
                        if (si)
                            remove(si.parentNode.nextSibling);
                    }

                    if (document.location.href.search('/forum/dl/') !== -1) {
                        document.body.setAttribute('style', (document.body.getAttribute('style')||'')+
                                                   ';background-color:black!important');
                        for (let itm of document.querySelectorAll('body>div'))
                            if (!itm.querySelector('.dw-fdwlink'))
                                remove(itm);
                    }

                    if (isForum) // Do not continue if it's a forum
                        return;

                    {
                        let si = document.querySelector('#header');
                        if (si)
                        {
                            let rem = si.previousSibling;
                            while (rem)
                            {
                                si = rem.previousSibling;
                                remove(rem);
                                rem = si;
                            }
                        }
                    }

                    for (let itm of document.querySelectorAll('#nav li[class]'))
                        if (itm && itm.querySelector('a[href^="/tag/"]'))
                            remove(itm);

                    let style, result,
                        fakeStyles = new WeakMap(),
                        styleProxy = {
                            get: function(target, prop)
                            {
                                let fakeStyle = fakeStyles.get(target);
                                return ((prop in fakeStyle) ? fakeStyle : target)[prop];
                            },
                            set: function(target, prop, value)
                            {
                                let fakeStyle = fakeStyles.get(target);
                                ((prop in fakeStyle) ? fakeStyle : target)[prop] = value;
                                return value;
                            }
                        };
                    for (let itm of document.querySelectorAll('DIV, A'))
                    {
                        if (itm.tagName ==='DIV' &&
                            itm.offsetWidth > 0.95 * width() &&
                            itm.offsetHeight > 0.85 * height())
                        {
                            style = window.getComputedStyle(itm, null);
                            result = [];

                            if (style.backgroundImage !== 'none')
                                result.push('background-image:none!important');

                            if (style.backgroundColor !== 'transparent' &&
                                style.backgroundColor !== 'rgba(0, 0, 0, 0)')
                                result.push('background-color:transparent!important');

                            if (result.length)
                            {
                                if (itm.getAttribute('style'))
                                    result.unshift(itm.getAttribute('style'));

                                fakeStyles.set(itm.style, {
                                    'backgroundImage': itm.style.backgroundImage,
                                    'backgroundColor': itm.style.backgroundColor
                                });

                                try {
                                    Object.defineProperty(itm, 'style', {
                                        value: new Proxy(itm.style, styleProxy),
                                        enumerable: true
                                    });
                                } catch (e) {
                                    console.log('Unable to protect style property.', e);
                                }

                                if (protectedElems)
                                    protectedElems.set(itm, _getAttribute.call(itm, 'style'));

                                _setAttribute.call(itm, 'style', result.join(';'));
                            }
                        }
                        if (itm.tagName ==='A' &&
                            (itm.offsetWidth > 0.95 * width() ||
                             itm.offsetHeight > 0.85 * height()))
                        {
                            if (protectedElems)
                                protectedElems.set(itm, _getAttribute.call(itm, 'style'));

                            _setAttribute.call(itm, 'style', 'display:none!important');
                        }
                    }

                    for (let itm of document.querySelectorAll('ASIDE>DIV'))
                        if ( ((itm.querySelector('script, iframe, a[href*="/ad/www/"]') ||
                               itm.querySelector('img[src$=".gif"]:not([height="0"]), img[height="400"]')) &&
                              !itm.classList.contains('post') ) || !itm.childNodes.length )
                            remove(itm);

                    document.body.setAttribute('style', (document.body.getAttribute('style')||'')+';background-color:#E6E7E9!important');

                    // display content of the page
                    afterClean();
                }
            );
        }
    };

    scripts['allmovie.pro'] = {
        other: ['rufilmtv.org'],
        dom: function()
        {
            // pretend to be Android to make site use different played for ads
            if (isSafari)
                return;
            Object.defineProperty(navigator, 'userAgent', {
                get: function(){ return 'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19'; },
                enumerable: true
            });
        }
    };

    scripts['anidub-online.ru'] = {
        other: ['online.anidub.com'],
        dom: function()
        {
            if (win.ogonekstart1)
                win.ogonekstart1 = () => console.log("Fire in the hole!");
        },
        now: () => createStyle([
            '.background {background: none!important;}',
            '.background > script + div,'+
            '.background > script ~ div:not([id]):not([class]) + div[id][class]'+
            '{display:none!important}'
        ])
    };

    scripts['drive2.ru'] = () => gardener('.c-block:not([data-metrika="recomm"]),.o-grid__item', />Реклама<\//i);

    scripts['fishki.net'] = () => gardener('.drag_list > .drag_element, .list-view > .paddingtop15, .post-wrap', /543769|Новости\sпартнеров/);

    scripts['gidonline.club'] = {
        now: () => createStyle('.tray > div[style] {display: none!important}')
    };

    scripts['hdgo.cc'] = {
        other: ['46.30.43.38', 'couber.be'],
        now: () => (new MutationObserver(
            function(ms)
            {
                let m, node;
                for (m of ms) for (node of m.addedNodes)
                    if (node.tagName === 'SCRIPT' && _getAttribute.call(node, 'onerror') !== null)
                        node.removeAttribute('onerror');
            }
        )).observe(document, { childList:true, subtree: true })
    };

    scripts['gismeteo.ru'] = {
        other: ['gismeteo.ua'],
        dom: () => gardener('div > a[target^="_"]', /Яндекс\.Директ/i, { root: 'body', observe: true, parent: 'div[class*="frame"]'})
    };

    scripts['hdrezka.me'] = {
        now: function()
        {
            Object.defineProperty(win, 'fuckAdBlock', {
                value: { onDetected: () => console.log('Pretending to be an ABP detector.') },
                enumerable: true
            });
            Object.defineProperty(win, 'ab', {
                value: false,
                enumerable: true
            });
        },
        dom: () => gardener('div[id][onclick][onmouseup][onmousedown]', /onmouseout/i)
    };

    scripts['imageban.ru'] = {
        now: preventBackgroundRedirect,
        dom: () => win.addEventListener(
            'unload', function()
            {
                window.location.hash = 'x'+Math.random().toString(36).substr(2);
            }, true
        )
    };

    scripts['mail.ru'] = {
        now: function()
        {
            // Trick to prevent mail.ru from removing 3rd-party styles
            scriptLander(
                () => Object.defineProperty(Object.prototype, 'restoreVisibility', {
                    get: () => (() => null),
                    set: () => null
                })
            );
            /* Experimental code, disabled for end users for now
            // Ads removal on e.mail.ru
            if (window.location.host === 'e.mail.ru')
            {
                let selector = (
                    '.b-datalist div[class]:not([id]) > div[class]:not([class*="js-"]),'+
                    '.b-letter div[class]:not([id]) > div[class]:not([class*="js-"]):not([class*="drop"]):not([class*="letter"]):not([style]):not([id]),'+
                    'div[id]:not([class]) > div[id][class]:not([class*="js-"]):not([class*="drop"]):not([style])'
                );
                let janitor = function(nodes)
                {
                    let color;
                    for (let node of nodes)
                    {
                        if (node.nodeType !== Node.ELEMENT_NODE)
                            continue;
                        color = window.getComputedStyle(node).backgroundColor;
                        if (/^rgb\(/.test(color) && color !== 'rgb(255, 255, 255)')
                        {
                            node.style.display = 'none';
                            console.log('Hide node:', node);
                        }
                    }
                };
                janitor(document.querySelectorAll(selector));
                (new MutationObserver(
                    function(ms)
                    {
                        for (let m of ms)
                            janitor(m.addedNodes);
                    }
                )).observe(
                    document.documentElement, {
                        childList: true,
                        subtree: true
                    }
                );
            }
            /**/
        }
    };

    scripts['megogo.net'] = {
        now: function()
        {
            Object.defineProperty(win, "adBlock", {
                get: () => false,
                set: () => null,
                enumerable : true
            });
            Object.defineProperty(win, "showAdBlockMessage", {
                get: () => (() => null),
                set: () => null,
                enumerable: true
            });
        }
    };

    scripts['naruto-base.su'] = () => gardener('div[id^="entryID"],.block', /href="http.*?target="_blank"/i);

    scripts['overclockers.ru'] = {
        now: function()
        {
            createStyle('.fixoldhtml {display:block!important}');
            if (!isChrome && !isOpera)
                return; // Looks like my code works only in Chrome-like browsers
            let noContentYet = true;
            function jWrap()
            {
                win.$ = new Proxy(
                    win.$, {
                        apply: function(_$, _this, args)
                        {
                            let _ret = _$.apply(_this, args);
                            if (_ret[0] === document.body)
                                _ret.html = () => console.log('Anti-adblock prevented.');
                            return _ret;
                        }
                    }
                );
                win.jQuery = win.$;
            }
            (function jReady()
             {
                if (!win.$ && noContentYet)
                    setTimeout(jReady, 0);
                else
                    jWrap();
            })();
            document.addEventListener ('DOMContentLoaded', () => (noContentYet = false), false);
        }
    };
    scripts['forums.overclockers.ru'] = {
        now: function()
        {
            createStyle('.needblock {position: fixed; left: -10000px}');
            Object.defineProperty(win, 'adblck', {
                get: () => 'no',
                set: () => null,
                enumerable: true
            });
        }
    };

    scripts['pb.wtf'] = {
        other: ['piratbit.org', 'piratbit.ru'],
        dom: function()
        {
            createStyle('.reques,#result,tbody.row1:not([id]) {display: none !important}');
            // image in the slider in the header
            gardener('a[href^="/ex"],a[href$="=="]', /img/i, {root:'.release-navbar', observe:true, parent:'div'});
            // ads in blocks on the page
            gardener('a[href^="/topic/234257"]', /Как\sразместить/i, {siblings:-1, root:'#main_content', observe:true, parent:'span[style]'});
            // line above topic content
            gardener('.re_top1', /./, {root:'#main_content', parent:'.hidden-sm'});
        }
    };

    scripts['pikabu.ru'] = () => gardener('.story', /story__sponsor|story__gag|profile\/ads"/i, {root: '.inner_wrap', observe: true});

    scripts['qrz.ru'] = {
        now: function()
        {
            Object.defineProperty(win, 'ab', {
                get:()=>false,
                set:()=>null
            });
            Object.defineProperty(win, 'tryMessage', {
                get:()=>(()=>null),
                set:()=>null
            });
        }
    };

    scripts['razlozhi.ru'] = {
        now: function()
        {
            for (let func of ['createShadowRoot', 'attachShadow'])
                if (func in Element.prototype)
                    Element.prototype[func] = function(){ return this.cloneNode(); };
        }
    };

    scripts['rp5.ru'] = {
        other: ['rp5.by', 'rp5.kz', 'rp5.ua'],
        dom: function()
        {
            createStyle('#bannerBottom {display: none!important}');
            let co = document.querySelector('#content');
            if (!co)
                return;
            let nodes = co.parentNode.childNodes,
                i = nodes.length;
            while (i--)
                if (nodes[i] !== co)
                    nodes[i].parentNode.removeChild(nodes[i]);
        }
    };

    scripts['rustorka.com'] = {
        other: ['rumedia.ws'],
        now: function()
        {
            createStyle('.header > div:not(.head-block) a, #sidebar1 img, #logo img {opacity:0!important}', {
                id: 'tempHidingStyles'
            }, true);
            preventPopups();
        },
        dom: function()
        {
            for (let o of document.querySelectorAll('IMG, A'))
                if ((o.clientWidth === 728 && o.clientHeight === 90) ||
                    (o.clientWidth === 300 && o.clientHeight === 250))
                {
                    while (o && o.tagName !== 'A')
                        o = o.parentNode;
                    if (o)
                        _setAttribute.call(o, 'style', 'display: none !important');
                }
            let s = document.querySelector('#tempHidingStyles');
            s.parentNode.removeChild(s);
        }
    };

    scripts['sport-express.ru'] = () => gardener('.js-relap__item',/>Реклама\s+<\//, {root:'.container', observe: true});

    scripts['sports.ru'] = function()
    {
        gardener('.aside-news-list__item', /aside-news-list__advert/i, {root:'.columns-layout__left', observe: true});
        gardener('.material-list__item', /Реклама/i, {root:'.columns-layout', observe: true});
        // extra functionality: shows/hides panel at the top depending on scroll direction
        createStyle([
            '.user-panel__fixed { transition: top 0.2s ease-in-out!important; }',
            '.user-panel-up { top: -40px!important }'
        ], {id: 'userPanelSlide'}, false);
        (function lookForPanel()
         {
            let panel = document.querySelector('.user-panel__fixed');
            if (!panel)
                setTimeout(lookForPanel, 100);
            else
                window.addEventListener(
                    'wheel', function(e)
                    {
                        if (e.deltaY > 0 && !panel.classList.contains('user-panel-up'))
                            panel.classList.add('user-panel-up');
                        else if (e.deltaY < 0 && panel.classList.contains('user-panel-up'))
                            panel.classList.remove('user-panel-up');
                    }, false
                );
        })();
    };

    scripts['vk.com'] = () => gardener('div[data-post-id]', /wall_marked_as_ads/, {root: '#page_wall_posts', observe: true});

    scripts['yap.ru'] = {
        other: ['yaplakal.com'],
        dom: function()
        {
            gardener('form > table[id^="p_row_"]:nth-of-type(2)', /member1438|Administration/);
            gardener('.icon-comments', /member1438|Administration|\/go\/\?http/, {parent:'tr', siblings:-2});
        }
    };

    scripts['rambler.ru'] = {
        other: ['championat.com','gazeta.ru','lenta.ru'],
        now: () => scriptLander(
            function()
            {
                let getDomain = (name) => name.replace(/[^:]+:\/\/([^:/]+)[:/].*/, '$1').replace(/[^.]+\./,'');
                let _onload = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'onload');
                let _set = _onload.set;
                _onload.configurable = false;
                _onload.set = function(func)
                {
                    _set.call(
                        this, function(e)
                        {
                            let d = e.target.href ? getDomain(e.target.href) : null,
                                h = window.location.host;
                            if (d && e.target instanceof HTMLLinkElement &&
                                (d === 'rambler.ru' || d === h || h.indexOf('.'+d) > -1))
                            {
                                console.log('Blocked "onload" for', e.target.href);
                                return false;
                            }
                            return func.apply(this, arguments);
                        }
                    );
                };
                Object.defineProperty(HTMLElement.prototype, 'onload', _onload);
                // fake global Adf object
                let nt = new nullTools();
                nt.define(win, 'Adf', nt.proxy({
                    banner: nt.proxy({
                        sspScroll: nt.func(),
                        ssp: nt.func()
                    })
                }));
            }, nullTools
        )
    };

    scripts['reactor.cc'] = {
        other: ['joyreactor.cc', 'pornreactor.cc'],
        now: function()
        {
            win.open = (function(){ throw new Error('Redirect prevention.'); }).bind(window);
        },
        click: function(e)
        {
            let node = e.target;
            if (node.nodeType === Node.ELEMENT_NODE &&
                node.style.position === 'absolute' &&
                node.style.zIndex > 0)
                node.parentNode.removeChild(node);
        },
        dom: function()
        {
            let words = new RegExp(
                'блокировщика рекламы'
                .split('')
                .map(function(e){return e+'[\u200b\u200c\u200d]*';})
                .join('')
                .replace(' ', '\\s*')
                .replace(/[аоре]/g, function(e){return ['[аa]','[оo]','[рp]','[еe]']['аоре'.indexOf(e)];}),
                'i'),
                can;
            function deeper(spider)
            {
                let c, l, n;
                if (words.test(spider.innerText))
                {
                    if (spider.nodeType === Node.TEXT_NODE)
                        return true;
                    c = spider.childNodes;
                    l = c.length;
                    n = 0;
                    while(l--)
                        if (deeper(c[l]), can)
                            n++;
                    if (n > 0 && n === c.length && spider.offsetHeight < 750)
                        can.push(spider);
                    return false;
                }
                return true;
            }
            function probe()
            {
                if (words.test(document.body.innerText))
                {
                    can = [];
                    deeper(document.body);
                    let i = can.length, spider;
                    while(i--) {
                        spider = can[i];
                        if (spider.offsetHeight > 10 && spider.offsetHeight < 750)
                            _setAttribute.call(spider, 'style', 'background:none!important');
                    }
                }
            }
            (new MutationObserver(probe))
                .observe(document, { childList:true, subtree:true });
        }
    };

    scripts['auto.ru'] = function()
    {
        let words = /Реклама|Яндекс.Директ|yandex_ad_/;
        let userAdsListAds = (
            '.listing-list > .listing-item,'+
            '.listing-item_type_fixed.listing-item'
        );
        let catalogAds = (
            'div[class*="layout_catalog-inline"],'+
            'div[class$="layout_horizontal"]'
        );
        let otherAds = (
            '.advt_auto,'+
            '.sidebar-block,'+
            '.pager-listing + div[class],'+
            '.card > div[class][style],'+
            '.sidebar > div[class],'+
            '.main-page__section + div[class],'+
            '.listing > tbody'
        );
        gardener(userAdsListAds, words, {root:'.listing-wrap', observe:true});
        gardener(catalogAds, words, {root:'.catalog__page,.content__wrapper', observe:true});
        gardener(otherAds, words);
    };

    scripts['rsload.net'] = {
        load: function()
        {
            let dis = document.querySelector('label[class*="cb-disable"]');
            if (dis)
                dis.click();
        },
        click: function(e)
        {
            let t = e.target;
            if (t && t.href && (/:\/\/\d+\.\d+\.\d+\.\d+\//.test(t.href)))
                t.href = t.href.replace('://','://rsload.net:rsload.net@');
        }
    };

    let domain, name;
    // add alternate domain names if present
    for (name in scripts) if (scripts[name].other)
        for (domain of scripts[name].other) if (!(domain in scripts))
            scripts[domain] = scripts[name];
    // look for current domain in the list and run appropriate code
    domain = document.domain;
    while (domain.indexOf('.') > -1)
    {
        if (domain in scripts)
        {
            if (typeof scripts[domain] === 'function')
            {
                document.addEventListener ('DOMContentLoaded', scripts[domain], false);
                break;
            }
            for (name in scripts[domain])
                switch(name)
                {
                    case 'other':
                        break;
                    case 'now':
                        scripts[domain][name]();
                        break;
                    case 'load':
                        window.addEventListener('load', scripts[domain][name], false);
                        break;
                    case 'dom':
                        document.addEventListener('DOMContentLoaded', scripts[domain][name], false);
                        break;
                    default:
                        document.addEventListener (name, scripts[domain][name], false);
                }
        }
        domain = domain.slice(domain.indexOf('.') + 1);
    }
})();