RU AdList JS Fixes

try to take over the world!

La data de 12-03-2018. Vezi ultima versiune.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         RU AdList JS Fixes
// @namespace    ruadlist_js_fixes
// @version      20180312.2
// @description  try to take over the world!
// @author       lainverse & dimisa
// @supportURL   https://greatest.deepsurf.us/en/scripts/19993-ru-adlist-js-fixes/feedback
// @match        *://*/*
// @exclude      *://auth.wi-fi.ru/*
// @exclude      *://*.alfabank.ru/*
// @exclude      *://alfabank.ru/*
// @exclude      *://*.unicreditbanking.net/*
// @exclude      *://unicreditbanking.net/*
// @exclude      *://*.telegram.org/*
// @exclude      *://telegram.org/*
// @grant        unsafeWindow
// @grant        window.close
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    let win = (unsafeWindow || window);

    // MooTools are crazy enough to replace standard browser object window.Document: https://mootools.net/core
    // Occasionally their code runs before my script on some domains and causes all kinds of havoc.
    let _Document = Object.getPrototypeOf(HTMLDocument);
    // dTree 2.05 in some cases replaces Node object
    let _Node = Object.getPrototypeOf(Element);

    // http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
    let 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 = 'InstallTrigger' in win,
        ifFirefoxUnder58 = isFirefox && 'StopIteration' in win,
        inIFrame = (win.self !== win.top),
        _getAttribute = Element.prototype.getAttribute,
        _setAttribute = Element.prototype.setAttribute,
        _removeAttribute = Element.prototype.removeAttribute,
        _de = document.documentElement,
        _appendChild = _Document.prototype.appendChild.bind(_de),
        _removeChild = _Document.prototype.removeChild.bind(_de),
        _createElement = _Document.prototype.createElement.bind(document);

    if (isFirefox && // Exit on image pages in Fx
        document.constructor.prototype.toString() === '[object ImageDocumentPrototype]')
        return;

    // 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];

    // Wrapper to run scripts designed to override objects available to other scripts
    // Required in old versions of Firefox (<58) or when running with Greasemonkey
    let skipLander = true;
    try {
        skipLander = !(ifFirefoxUnder58 || (isFirefox && GM.info.scriptHandler === 'Greasemonkey'));
    } catch(ignore){}
    let batchLand = [];
    let batchPrepend = [];
    let _APIString = 'let win = window, _Document = Object.getPrototypeOf(HTMLDocument), _Node = Object.getPrototypeOf(Element);';
    let landScript = (f, pre) => {
        let script = _createElement('script');
        script.textContent = `(()=>{${_APIString}${(
            (pre.length > 0 ? pre.join(';') : '')
        )};(${f.join(')();(')})();})();`;
        _appendChild(script);
        _removeChild(script);
    };
    let scriptLander = f => f();
    if (!skipLander) {
        scriptLander = (func, ...prepend) => {
            prepend.forEach(
                x => batchPrepend.includes(x) ? null : batchPrepend.push(x)
            );
            batchLand.push(func);
        };
        document.addEventListener(
            'DOMContentLoaded', ignore => scriptLander = (f, ...prep) => landScript([f], prep), false
        );
    }

    function nullTools(opts) {
        // jshint validthis:true
        let nt = this;
        opts = opts || {};
        function log() { if (opts.log) console.log(...arguments); }
        function trace() { if (opts.log || opts.trace) console.warn(...arguments); }

        nt.destroy = function(o, destroy)
        {
            if (!opts.destroy && !destroy && o instanceof Object)
                return;
            log('cleaning', o);
            try {
                for (let item in o)
                {
                    if (item instanceof Object)
                        nt.destroy(item);
                    delete o[item];
                }
            } catch (e) {
                log('Error in object destructor', e);
            }
        };

        nt.define = function(obj, prop, val, enumerable = true)
        {
            try {
                Object.defineProperty(
                    obj, prop, {
                        get: ()  => val,
                        set: (v) => (v !== val ? (log(`set ${prop} of`, obj, 'to', v), nt.destroy(v)) : null, undefined),
                        enumerable: enumerable
                    }
                );
            } catch (err) {
                console.log(`Unable to redefine "${prop}" in `, obj, err);
            }
        };
        nt.proxy = function(obj)
        {
            return new Proxy(
                obj, {
                    get: (t, p) => p in t ? t[p] : console.warn(`Missing ${p} in`, t),
                    set: (t, p, v) => (v !== t[p] ? (log(`set ${p} of`, t, 'to', v), nt.destroy(v)) : null, true)
                }
            );
        };
        nt.func = (val, name = '') => (...args) => (trace(`call obj.${name}(`, ...args,`) return`, val), val);
    }

    // Debug function, lists all unusual window properties
    function getStrangeObjectsList()
    {
        console.warn('Strangers list start');
        let _ts = Function.prototype.toString;
        let _skip = 'frames/self/window/webkitStorageInfo'.split('/');
        for (let n in win) {
            let val = win[n];
            if (val && !_skip.includes(n) && (win !== window && val !== window[n] || win === window) &&
                (!(val instanceof Function) ||
                 val instanceof Function &&
                 !(new RegExp (`^function\\s(${n})?\\(\\)[\\s\\r\\n]*\\{[\\s\\r\\n]*\\[native\\scode\\][\\s\\r\\n]*\\}$`)).test(_ts.call(val))))
                console.log(`${n} =`, val);
        }
        console.warn('Strangers list end');
    }

    // Fake objects of advertisement networks to break their workflow
    scriptLander(
        function()
        {
            let nt = new nullTools();
            // Popular adblock detector
            if (!('fuckAdBlock' in win))
            {
                let FuckAdBlock = function(options) {
                    let self = this;
                    self._options = {
                        checkOnLoad: false,
                        resetOnEnd: false,
                        checking: false
                    };
                    self.setOption = function(opt, val)
                    {
                        if (val)
                            self._options[opt] = val;
                        else
                            Object.assign(self._options, opt);
                    };
                    if (options)
                        self.setOption(options);

                    self._var = { event: {} };
                    self.clearEvent = function()
                    {
                        self._var.event.detected = [];
                        self._var.event.notDetected = [];
                    };
                    self.clearEvent();

                    self.on = function(detected, fun)
                    {
                        self._var.event[detected?'detected':'notDetected'].push(fun);
                        return self;
                    };
                    self.onDetected = function(cb)
                    {
                        return self.on(true, cb);
                    };
                    self.onNotDetected = function(cb)
                    {
                        return self.on(false, cb);
                    };
                    self.emitEvent = function()
                    {
                        for (let fun of self._var.event.notDetected)
                            fun();
                        if (self._options.resetOnEnd)
                            self.clearEvent();
                        return self;
                    };
                    self._creatBait = () => null;
                    self._destroyBait = () => null;
                    self._checkBait = function() {
                        setTimeout((() => self.emitEvent()), 1);
                    };
                    self.check = function() {
                        self._checkBait();
                        return true;
                    };

                    let callback = function()
                    {
                        if (self._options.checkOnLoad)
                            setTimeout(self.check, 1);
                    };
                    window.addEventListener('load', callback, false);
                };
                nt.define(win, 'FuckAdBlock', FuckAdBlock);
                nt.define(win, 'fuckAdBlock', new FuckAdBlock({
                    checkOnLoad: true,
                    resetOnEnd: true
                }));
            }

            // CoinHive miner stub. Continuous 100% CPU load can easily kill some CPU with overheat.
            if (!('CoinHive' in win))
            {
                if (location.hostname !== 'cnhv.co')
                {
                    // CoinHive stub for cases when it doesn't affect site functionality
                    let CoinHiveConstructor = function()
                    {
                        console.warn('Fake CoinHive miner created.');
                        this.setThrottle = nt.func(null);
                        this.start = nt.func(null);
                        this.on = nt.func(null);
                        this.getTotalHashes = nt.func(0);
                    };
                    let CoinHiveStub = nt.proxy({
                        Anonymous: CoinHiveConstructor,
                        User: CoinHiveConstructor,
                        Token: CoinHiveConstructor,
                        JobThread: nt.func(null),
                        IF_EXCLUSIVE_TAB: false
                    });
                    nt.define(win, 'CoinHive', CoinHiveStub);
                } else {
                    // CoinHive wrapper to fool sites which expect it to actually work and return results
                    let CoinHiveObject;
                    let fishnet = {
                        apply: (target, thisArg, args) => {
                            console.log(`miner.${target._name}(${JSON.stringify(args).slice(1,-1)})`);
                            return target.apply(thisArg, args);
                        }
                    };
                    Object.defineProperty(win, 'CoinHive', {
                        set: function(obj)
                        {
                            if ('Token' in obj)
                            {
                                console.log('[CoinHive] Token wrapper applied.');
                                let _Token = obj.Token.bind(obj);
                                obj.Token = function(siteKey, goal, params)
                                {
                                    let _goal = goal;
                                    if (goal > 256)
                                        goal = 256;
                                    console.log(`[CoinHive] Original goal: ${_goal}, new smaller goal ${goal}.`);
                                    console.log(`With smaller goals server may return 'invalid_goal' error and stop working.`);
                                    let miner = _Token(siteKey, goal, params);
                                    miner.setThrottle(0.99);
                                    miner.setThrottle = () => null;
                                    let _start = miner.start.bind(miner);
                                    miner.start = function() {
                                        let res = _start(CoinHive.FORCE_EXCLUSIVE_TAB);
                                        return res;
                                    };
                                    let _getTotalHashes = miner.getTotalHashes;
                                    miner.getTotalHashes = function()
                                    {
                                        return Math.trunc(_getTotalHashes.call(this) / goal * _goal);
                                    };
                                    let __emit = miner._emit;
                                    miner._emit = function(state, props)
                                    {
                                        let _self = this;
                                        console.log('[CoinHive] state:', state, props);
                                        if (state === 'job')
                                            setTimeout(() => {
                                                _self.stop();
                                                _self._emit('accepted', { hashes: goal });
                                            }, 1000);
                                        return __emit.apply(_self, arguments);
                                    };
                                    let _on = miner.on.bind(miner);
                                    miner.on = function(type, callback)
                                    {
                                        if (type === 'accepted')
                                        {
                                            console.log('[CoinHive] "accepted" callback wrapper applied.');
                                            let _callback = callback;
                                            callback = function(params)
                                            {
                                                console.log('[CoinHive] "accepted" callback is called, imitating original goal being reached.');
                                                params.hashes = _goal;
                                                return _callback.apply(this, arguments);
                                            };
                                            miner.stop();
                                        }
                                        return _on(type, callback);
                                    };
                                    return miner;
                                };
                            }
                            CoinHiveObject = obj;
                        },
                        get: () => CoinHiveObject
                    });
                }
            }

            // VideoJS player wrapper
            VideoJS: {
                let _videojs = win.videojs || void 0;
                Object.defineProperty(win, 'videojs', {
                    get: x => _videojs,
                    set: f => {
                        if (f === _videojs)
                            return true;
                        console.log('videojs =', f);
                        _videojs = new Proxy(f, {
                            apply: (tgt, ths, args) => {
                                console.log('videojs(', ...args, ')');
                                let params = args[1];
                                if (params) {
                                    if (params.hasAd)
                                        params.hasAd = false;
                                    if (params.plugins && params.plugins.vastClient)
                                        delete params.plugins.vastClient;
                                }
                                let res = tgt.apply(ths, args);
                                console.log('player = ', res);
                                return res;
                            }
                        });
                    }
                });
            }
            // Yandex API (ADBTools, Metrika)
            let hostname = location.hostname;
            if (location.protocol === 'about:' ||
                // Thank you, Greasemonkey, now I have to check for this. -_-
                hostname.startsWith('google.') || hostname.includes('.google.') ||
                // Google likes to define odd global variables like Ya
                ((hostname.startsWith('yandex.') || hostname.includes('.yandex.')) &&
                 /^\/((yand)?search|images)/i.test(location.pathname) &&
                 !hostname.startsWith('news.')) ||
                // 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.
                hostname.endsWith('github.io') || hostname.endsWith('grimtools.com'))
                return;

            let YaProps = new Set();
            function onAdvManager (Ya, rootProp, obj)
            {
                return new Proxy(obj, {
                    set: (tgt, prop, val) => {
                        if (prop === 'AdvManager')
                        {
                            console.log(`Ya.${rootProp} = Ya.Context`);
                            nt.define(Ya, rootProp, Ya.Context);
                            YaProps.add(rootProp);
                        }
                        tgt[prop] = val;
                        return true;
                    },
                    get: (tgt, prop) => tgt[prop]
                });
            }
            let Rum = {};
            [
                '__timeMarks', '_timeMarks', '__deltaMarks', '_deltaMarks',
                '__defRes', '_defRes', '__defTimes', '_defTimes',
                'commonVars'
            ].forEach(name => Rum[name] = []);
            [
                'send', 'sendRaf', 'sendTimeMark', 'sendResTiming', 'sendTTI',
                'time', 'timeEnd', 'getTime', 'mark', 'init',
                'makeSubPage', 'observeDOMNode', 'isVisibilityChanged'
            ].forEach(name => Rum[name] = nt.func(null, name));
            [
                'getSettings', 'getVarsList'
            ].forEach(name => Rum[name] = nt.func([], name));
            [
                ['ajaxStart', 0], ['ajaxComplete', 0],
                ['enabled', true], ['_tti', null],
                ['vsChanged', false], ['vsStart', 'visible']
            ].forEach(([prop, val]) => Rum[prop] = val);
            Rum = nt.proxy(Rum);
            let Ya = new Proxy({}, {
                set: function(tgt, prop, val)
                {
                    if (val === tgt[prop])
                        return true;
                    if (prop === 'Rum')
                    {
                        nt.define(tgt, prop, Rum);
                        YaProps.add(prop);
                        Object.assign(val, Rum);
                    }
                    if (YaProps.has(prop))
                    {
                        console.log(`Ya.${prop} \u2260`, val);
                        return true;
                    }
                    if (val instanceof Object && prop !== '__inline_params__')
                        val = onAdvManager(Ya, prop, val);
                    tgt[prop] = val;
                    console.log(`Ya.${prop} =`, val);
                    return true;
                },
                get: (tgt, prop) => tgt[prop]
            });
            let callWithParams = function(f)
            {
                f.call(this, Ya.__inline_params__ || {});
                Ya.__inline_params__ = null;
            };
            nt.define(Ya, 'callWithParams', callWithParams);
            nt.define(Ya, 'PerfCounters', nt.proxy({
                addCacheEvent: nt.func(null),
                getTime: nt.func(null),
                sendCacheEvents: nt.func(null),
                sendResTiming: nt.func(null),
                sendTimeMark: nt.func(null),
                __cacheEvents: []
            }));
            nt.define(Ya, '__isSent', true);
            nt.define(Ya, 'confirmUrl', '');
            nt.define(Ya, 'Direct', nt.proxy({
                insertInto: nt.func(null)
            }));
            nt.define(Ya, 'mediaCode', nt.proxy({
                create: function()
                {
                    if (inIFrame)
                    {
                        console.log('Removed body of ad-frame.');
                        document.documentElement.removeChild(document.body);
                    }
                },
                moduleLoad: nt.func(null),
                setModule: nt.func(null)
            }));
            let extra = nt.proxy({
                extra: nt.proxy({ match: 0, confirm: '', src: '' }),
                id: 0, percent: 100, threshold: 1
            });
            nt.define(Ya, '_exp', nt.proxy({
                id: 0, coin: 0,
                choose: nt.func(extra),
                get: (prop) => extra.hasOwnProperty(prop) ? extra[prop] : null,
                getId: nt.func(0),
                defaultVersion: extra,
                getExtra: nt.func(extra.extra),
                getDefaultExtra: nt.func(extra.extra),
                versions: [extra]
            }));
            nt.define(Ya, 'c', nt.func(null));
            nt.define(Ya, 'ADBTools', function(){
                ['loadContext', 'testAdbStyle'].forEach(name => this[name] = nt.func(null));
                this.getCurrentState = nt.func(true);
                return nt.proxy(this);
            });

            if (!location.hostname.includes('kinopoisk.ru')) {
                // kinopoisk uses adfox wrapped in Yandex code to display self-ads
                let adfoxCode = {};
                [
                    'create', 'reload', 'destroy',
                    'createAdaptive', 'moduleLoad',
                    'createScroll', 'pr'
                ].forEach(name => adfoxCode[name] = nt.func(null, name));
                nt.define(Ya, 'adfoxCode', nt.proxy(adfoxCode));
            } else {
                nt.define(win, 'abExperiments', nt.proxy([]));
            }
            let AdvManager = function()
            {
                [
                    'renderDirect',
                    'getBid', 'releaseBid',
                    'getSkipToken',
                    'getAdSessionId'
                ].forEach(name => this[name] = nt.func(null, name));
                this.render = function(o) {
                    if (!o.renderTo)
                        return;
                    let placeholder = document.getElementById(o.renderTo);
                    let parent = placeholder.parentNode;
                    placeholder.style = 'display:none!important';
                    parent.style = (parent.getAttribute('style')||'') + 'height:auto!important';
                    // fix for Yandex TV pages
                    if (location.hostname.startsWith('tv.yandex.'))
                    {
                        let sibling = placeholder.previousSibling;
                        if (sibling && sibling.classList && sibling.classList.contains('tv-spin'))
                            sibling.style.display = 'none';
                    }
                };
                return nt.proxy(this);
            };
            nt.define(Ya, 'Context', nt.proxy({
                __longExperiment: null,
                _callbacks: nt.proxy([]),
                _asyncModeOn: true,
                _init: nt.func(null),
                isAllowedRepeatAds: nt.func(null),
                AdvManager: new AdvManager()
            }));
            let Metrika = function()
            {
                [
                    'reachGoal', 'replacePhones',
                    'trackLinks', 'userParams',
                    'hit', 'params', 'clickmap'
                ].forEach(name => this[name] = nt.func(null, name));
                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);
            counter = {};
            [
                'cpHitDelayed','wHitDelayed',
                'hitDelayed','makeReq',
                'redir','hit','log','cp','w'
            ].forEach(name => counter[name] = nt.func(null, name));
            [
                'stringifyParams','_getVars',
                'getUid','getUrl','getHash'
            ].forEach(name => counter[name] = nt.func(''));
            nt.define(Ya, 'counter', nt.proxy(counter));
            nt.define(Ya, 'jserrors', []);
            nt.define(Ya, 'onerror', nt.func(null, 'onerror'));
            if (win.Ya) {
                console.log('Found existing Ya object:', win.Ya);
                for (let prop in win.Ya)
                    Ya[prop] = win.Ya[prop];
            }
            for (let prop in Ya) if (prop !== '__inline_params__')
                YaProps.add(prop);
            nt.define(win, 'Ya', Ya);
            // Yandex.Metrika callbacks
            let yandex_metrika_callbacks = [];
            document.addEventListener(
                'DOMContentLoaded',
                (e) => {
                    yandex_metrika_callbacks.forEach((f) => f && f.call(window));
                    yandex_metrika_callbacks.length = 0;
                    yandex_metrika_callbacks.push = (f) => setTimeout(f, 0);
                },
                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: () => undefined,
                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;
    }

    // Simplistic WebSocket wrapper for troublesome browsers and
    // disable 'onerror' handler for scripts from blacklisted sources
    scriptLander(() => {
        let masks = [],
            isBlocked = url => masks.some(mask => mask.test(url));
        for (let 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^', '||etcodes.com^',
            '||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^',
            '||joyreactor.cc^', '||pornreactor.cc^', '||reactor.cc^',
            '||kiev.ua^', '||kinotochka.net^',
            '||kinott.com^', '||kinott.ru^', '||kuveres.com^',
            '||lepubs.com^', '||luxadv.com^', '||luxup.ru^', '||luxupcdna.com^',
            '||mail.ru^', '||marketgid.com^', '||mebablo.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^', '||rutvind.com^',
            '||skidl.ru^',
            '||torvind.com^', '||traffic-media.co^', '||trafmag.com^', '||ttarget.ru^',
            '||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'));

        let _createElement = _Document.prototype.createElement,
            _addEventListener = Element.prototype.addEventListener,
            _removeEventListener = Element.prototype.removeEventListener;

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

                scriptMap.set(this, {
                    org: val,
                    wrp: function(...args)
                    {
                        if (scriptMap.isBlocked(this.src))
                            console.log('[WSI] Blocked "onerror" callback from', this.src);
                        else
                            scriptMap.get(this).org.apply(this, args);
                    }
                });
                _addEventListener.call(this, 'error', scriptMap.get(this).wrp, false);
            },
            get: function()
            {
                return scriptMap.has(this) ? scriptMap.get(this).org : null;
            },
            enumerable: true
        };
        _Document.prototype.createElement = function createElement(...args) {
            let el = _createElement.apply(this, args);

            // Custom onerror handler to block execution of callback if script source is blacklisted
            if (el instanceof HTMLScriptElement)
                Object.defineProperty(el, 'onerror', onErrorWrapper);
            // Block popular method to open a new window in Google Chrome by dispatching a custom click
            // event on a newly created anchor with _blank target. Untrusted events must not open a new window.
            if (el instanceof HTMLAnchorElement) {
                el.addEventListener('click', e => {
                    if (!e.isTrusted && e.target && !e.target.parentNode && e.target.target[0] === '_') {
                        e.stopPropagation();
                        e.preventDefault();
                        console.log('Blocked untrusted click on parentless anchor:', e.target);
                    }
                }, false);}

            return el;
        };
        document.createElement = _Document.prototype.createElement;
        // Simplistic WebSocket wrapper for Maxthon and Firefox before v58
        WSWrap: {
            if (/Maxthon/.test(navigator.appVersion) ||
                'InstallTrigger' in win && 'StopIteration' in win) {
                let _ws = Object.getOwnPropertyDescriptor(win, 'WebSocket');
                if (!_ws)
                    break WSWrap;
                _ws.value = new Proxy(_ws.value, {
                    construct: (ws, args) => {
                        if (isBlocked(args[0])) {
                            console.log('[WSI] Blocked WS connection:', args[0]);
                            return {};
                        }
                        return new ws(...args);
                    }
                });
                Object.defineProperty(win, 'WebSocket', _ws);
            }
        }
    });

    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: () => undefined
                        };
                    let shadow = style.parentNode;
                    Object.defineProperties(shadow, {
                        childElementCount: { value: 0 },
                        styleSheets: { value: emptyArr },
                        firstChild: { value: null },
                        firstElementChild: { value: null },
                        lastChild: { value: null },
                        lastElementChild: { value: null },
                        childNodes: { value: emptyArr },
                        children: { value: emptyArr },
                        innerHTML: { value: nullStr },
                    });
                    Object.defineProperties(style, {
                        innerHTML: { value: nullStr },
                        textContent: { value: nullStr },
                        ownerDocument: { value: null },
                        parentNode: {value: null },
                        previousElementSibling: { value: null },
                        previousSibling: { value: null },
                        disabled: { get: () => true, set: x => null }
                    });
                    Object.defineProperties(style.sheet, {
                        deleteRule: { value: () => null },
                        disabled: { get: () => true, set: x => null },
                        cssRules: { value: emptyArr },
                        rules: { value: 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?:\/\/tv\.yandex\./i.test(win.location.href))
    {
        // https://greatest.deepsurf.us/en/scripts/809-no-yandex-ads
        let yadWord = /Яндекс.Директ/i,
            adWords = /Реклама|Ad/i;
        let _querySelector = document.querySelector.bind(document),
            _querySelectorAll = document.querySelectorAll.bind(document),
            _getAttribute = Element.prototype.getAttribute,
            _setAttribute = Element.prototype.setAttribute;
        // Function to attach an observer to monitor dynamic changes on the page
        let pageUpdateObserver = (func, obj, params) => {
            if (obj)
                (new MutationObserver(func))
                    .observe(obj, (params || { childList:true, subtree:true }));
        };
        // Short name for parentNode.removeChild
        let remove = (node) => {
            if (!node || !node.parentNode)
                return false;
            console.log('Removed node.');
            node.parentNode.removeChild(node);
        };
        // Yandex search ads in Google Chrome
        if ('attachShadow' in Element.prototype)
        {
            let _attachShadow = Element.prototype.attachShadow;
            Element.prototype.attachShadow = function()
            {
                let node = this,
                    root = _attachShadow.apply(node, arguments);
                pageUpdateObserver(
                    (ms) => {
                        for (let m of ms) if (m.addedNodes.length)
                            if (adWords.test(root.textContent))
                                remove(node.closest('.serp-item'));
                    }, root
                );
                return root;
            };
        }
        // prevent/defuse adblock detector
        setInterval(()=>{
            localStorage.ic = '';
            localStorage._mt__data = '';
        },100);
        let _doc_proto = ('cookie' in _Document.prototype) ? _Document.prototype : Object.getPrototypeOf(document);
        let _cookie = Object.getOwnPropertyDescriptor(_doc_proto, 'cookie');
        if (_cookie)
        {
            let _set_cookie = Function.prototype.call.bind(_cookie.set);
            _cookie.set = function(value)
            {
                if (/^(mda=|yp=|ys=|yabs-|__)/.test(value))
                {
                    // remove value, set expired
                    if (!value.startsWith('yp=')) {
                        value = value.replace(/^([^=]+=)[^;]+/,'$1').replace(/(expires=)[\w\s\d,]+/,'$1Thu, 01 Jan 1970 00');
                        console.log('expire cookie', value.match(/^[^=]+/)[0]);
                    } else {
                        let parts = value.split(';');
                        let values = parts[0].split('#').filter(part => /\.sp\./.test(part));
                        if (values.length)
                            values[0] = values[0].replace(/^yp=/, '');
                        parts[0] = `yp=${values.join('#')}`;
                        value = parts.join(';');
                        console.log(`set cookie ${parts[0]}`);
                    }
                    //return null;
                }
                return _set_cookie(this, value);
            };
            Object.defineProperty(_doc_proto, 'cookie', _cookie);
        }
        // other ads
        document.addEventListener(
            'DOMContentLoaded', function()
            {
                {   // Generic ads removal and fixes
                    let node = _querySelector('.serp-header');
                    if (node)
                        node.style.marginTop = '0';
                    for (node of _querySelectorAll(
                        '.serp-adv__head + .serp-item,'+
                        '#adbanner,'+
                        '.serp-adv,'+
                        '.b-spec-adv,'+
                        'div[class*="serp-adv__"]:not(.serp-adv__found):not(.serp-adv__displayed)'
                    )) remove(node);
                }
                // Search ads
                function removeSearchAds()
                {
                    for (let node of _querySelectorAll('.serp-item'))
                        if (_getAttribute.call(node, 'role') === 'complementary' ||
                            adWords.test((node.querySelector('.label')||{}).textContent))
                            remove(node);
                }
                // News ads
                function removeNewsAds()
                {
                    let node, block, item, items, mask, classes,
                        masks = [
                            { class: '.ads__wrapper', regex: /[^,]*?,[^,]*?\.ads__wrapper/ },
                            { class: '.ads__pool',    regex: /[^,]*?,[^,]*?\.ads__pool/ }
                        ];
                    for (node of _querySelectorAll('style[nonce]'))
                    {
                        classes = node.innerText.replace(/\{[^}]+\}+/ig, '|').split('|');
                        for (block of classes) for (mask of masks)
                            if (block.includes(mask.class))
                            {
                                block = block.match(mask.regex)[0];
                                items = _querySelectorAll(block);
                                for (item of items)
                                    remove(items[0]);
                            }
                    }
                }
                // Music ads
                function removeMusicAds()
                {
                    for (let node of _querySelectorAll('.ads-block'))
                        remove(node);
                }
                // Mail ads
                function removeMailAds()
                {
                    let slice = Array.prototype.slice,
                        nodes = slice.call(_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.');
                    }
                }
                // TV fixes
                function removeTVAds()
                {
                    for (let node of _querySelectorAll('div[class^="_"][data-reactid] > div'))
                        if (yadWord.test(node.textContent) || node.querySelector('iframe:not([src])'))
                        {
                            if (node.offsetWidth)
                            {
                                let pad = document.createElement('div');
                                _setAttribute.call(pad, 'style', `width:${node.offsetWidth}px`);
                                node.parentNode.appendChild(pad);
                            }
                            remove(node);
                        }
                }

                if (location.hostname.startsWith('mail.')) {
                    pageUpdateObserver(
                        function(ms, o)
                        {
                            let aside = _querySelector('.mail-Layout-Aside');
                            if (aside) {
                                o.disconnect();
                                pageUpdateObserver(removeMailAds, aside);
                            }
                        }, document.body
                    );
                    removeMailAds();
                } else if (location.hostname.startsWith('music.')) {
                    pageUpdateObserver(removeMusicAds, _querySelector('.sidebar'));
                    removeMusicAds();
                } else if (location.hostname.startsWith('news.')) {
                    pageUpdateObserver(removeNewsAds, document.body);
                    pageUpdateObserver(removePageAdsClass, document.body, { attributes:true, attributesFilter:['class'] });
                    removeNewsAds();
                    removePageAdsClass();
                } else if (location.hostname.startsWith('tv.')) {
                    pageUpdateObserver(removeTVAds, document.body);
                    removeTVAds();
                } else {
                    pageUpdateObserver(removeSearchAds, _querySelector('.main__content'));
                    removeSearchAds();
                }
            }
        );
    }

    // Yandex Link Tracking
    if (/^https?:\/\/([^.]+\.)*yandex\.[^\/]+/i.test(win.location.href))
    {
        // remove banner on the start page
        scriptLander(() => {
            let nt = new nullTools({log: false, trace: true});
            let AwapsJsonAPI_Json_prototype = {};
            let AwapsJsonAPI_Json = function(...args) {
                console.log('>> new AwapsJsonAPI.Json(', ...args, ')');
            };
            [
                'setID', 'addImageContent',
                'sendCounts', 'expand', 'refreshAd'
            ].forEach(name => AwapsJsonAPI_Json.prototype[name] = nt.proxy(nt.func(null, name)));
            AwapsJsonAPI_Json.prototype.checkBannerVisibility = nt.proxy(nt.func(true, 'checkBannerVisibility'));
            AwapsJsonAPI_Json.prototype.addIframeContent = nt.proxy(function(...args) {
                try {
                    let frame = args[1][0].parentNode;
                    frame.parentNode.removeChild(frame);
                    console.log(`Removed banner placeholder.`);
                } catch(ignore) {
                    console.log(`Can't locate frame object to remove.`);
                }
            });
            AwapsJsonAPI_Json.prototype.getHTML = nt.proxy(nt.func('', 'getHTML'));
            AwapsJsonAPI_Json.prototype = nt.proxy(AwapsJsonAPI_Json.prototype);
            AwapsJsonAPI_Json = nt.proxy(AwapsJsonAPI_Json);
            if ('AwapsJsonAPI' in win) {
                console.log('Oops! AwapsJsonAPI already defined.');
                let f = win.AwapsJsonAPI.Json;
                win.AwapsJsonAPI.Json = AwapsJsonAPI_Json;
                if (f && f.prototype)
                    f.prototype = AwapsJsonAPI_Json.prototype;
            } else {
                nt.define(win, 'AwapsJsonAPI', nt.proxy({
                    Json: AwapsJsonAPI_Json
                }));
            }

            let home = win.home || {};
            let parseExport = x => {
                if (!x)
                    return x;
                // remove banner placeholder
                if (x.banner && x.banner.cls) {
                    let _parent = `.${x.banner.cls.banner__parent}`;
                    document.addEventListener('DOMContentLoaded', () => {
                        for (let banner of document.querySelectorAll(_parent)) {
                            _setAttribute.call(banner, 'style', 'display:none!important');
                            console.log('Hid banner placeholder.');
                        }
                    }, false);
                }

                // remove banner data and some other stuff
                delete x.banner;
                delete x.consistency;
                delete x['i-bannerid'];
                delete x['i-counter'];
                delete x['ga-counter'];
                delete x['promo-curtain'];

                return x;
            };
            let home_export = parseExport(home.export);
            Object.defineProperty(home, 'export', {
                get: x => home_export,
                set: x => {
                    home_export = parseExport(x);
                }
            });
            nt.define(win, 'home', home);
        }, nullTools, '_setAttribute = Element.prototype.setAttribute');

        if ('attachShadow' in Element.prototype) {
            let fakeRoot = () => ({
                firstChild: null,
                appendChild: ()=>null,
                querySelector: ()=>null,
                querySelectorAll: ()=>null
            });
            Element.prototype.createShadowRoot = fakeRoot;
            let shadows = new WeakMap();
            let _attachShadow = Object.getOwnPropertyDescriptor(Element.prototype, 'attachShadow');
            let _call_attachShadow = _attachShadow.value;
            _attachShadow.value = function() {
                return shadows.set(this, fakeRoot()).get(this);
            };
            Object.defineProperty(Element.prototype, 'attachShadow', _attachShadow);
            let _shadowRoot = Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot');
            _shadowRoot.set = () => null;
            _shadowRoot.get = function() {
                return shadows.has(this) ? shadows.get(this) : void 0;
            };
            Object.defineProperty(Element.prototype, 'shadowRoot', _shadowRoot);
        }
        // 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 });
    }

    // https://greatest.deepsurf.us/en/scripts/21937-moonwalk-hdgo-kodik-fix v0.8 (adapted)
    document.addEventListener(
        'DOMContentLoaded', function()
        {//createPlayer();
            function log (name) {
                console.log(`Player FIX: Detected ${name} player in ${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);
                        };
                    }
                );
            } else if ('ADC' in win)
            {
                log('vjs-creatives plugin in');
                let replacer = (obj) => {
                    for (let name in obj)
                        if (obj[name] instanceof Function)
                            obj[name] = () => null;
                };
                replacer(win.ADC);
                replacer(win.currentAdSlot);
            }
            UberVK: {
                if (!inIFrame)
                    break UberVK;
                let oddNames = 'HD' in win &&
                    !Object.getOwnPropertyNames(win).every(n => !n.startsWith('_0x'));
                if (!oddNames)
                    break UberVK;
                log('UberVK');
                XMLHttpRequest.prototype.open = () => { throw 404; };
            }
        }, false
    );

    // piguiqproxy.com circumvention prevention
    scriptLander(
        () => {
            let _open = XMLHttpRequest.prototype.open;
            // blacklist of third-party domains requests to which are ignored
            let blacklist = /[/.@](amgload\.net|dsn-fishki\.ru|piguiqproxy\.com|rcdn\.pro)[:/]/i;
            // blacklist of domains where all third-party requests are ignored
            let ondomains = /(^|[/.@])oane\.ws($|[:/])/i;
            // highly suspicious URLs
            let suspicious = /^https?:\/\/[a-z0-9]{6}\.ru\/([a-z0-9\/]{45,}|[a-z0-9]{8,}|ad\/banner\/.+)$/i;

            XMLHttpRequest.prototype.open = function(method, url)
            {
                if (method === 'GET' &&
                    (blacklist.test(url) || suspicious.test(url) ||
                     ondomains.test(location.hostname) && !ondomains.test(url)))
                {
                    this.send = () => null;
                    this.setRequestHeader = () => 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)
    {
        let logger = function() { return params.log ? console.log(...arguments) : null; };
        if (!scope.contains(document.body))
            logger('[s] scope', scope);
        let remFunc = (params.hide ? scHide : scRemove),
            iterFunc = (params.siblings > 0 ? 'nextElementSibling' : 'previousElementSibling'),
            toRemove = [],
            siblings;
        for (let node of scope.querySelectorAll(selector))
        {
            // drill up to a parent node if specified, break if not found
            if (params.parent)
            {
                let old = node;
                node = node.closest(params.parent);
                if (node === null || node.contains(scope))
                {
                    logger('[s] went out of scope with', old);
                    continue;
                }
            }
            logger('[s] processing', node);
            if (toRemove.includes(node))
                continue;
            if (words.test(node.innerHTML))
            {
                // skip node if already marked for removal
                logger('[s] marked 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) break; // can't go any further - exit
                    logger('[s] adding sibling node', node);
                    toRemove.push(node);
                    siblings -= 1;
                }
            }
        }
        let toSkip = [];
        for (let node of toRemove)
            if (!toRemove.every(other => other === node || !node.contains(other)))
                toSkip.push(node);
        if (toRemove.length)
            logger(`[s] proceeding with ${params.hide?'hide':'removal'} of`, toRemove, `skip`, toSkip);
        for (let node of toRemove) if (!toSkip.includes(node))
            remFunc(node);
    }

    // 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)
    {
        let logger = function() { return params.log ? console.log(...arguments) : null; };
        params = params || {};
        logger(`[gardener] selector: '${selector}' detector: ${words} options: ${JSON.stringify(params)}`);
        let scope;
        let globalScope = [_de];
        let domLoaded = false;
        let getScope = root => root ? _de.querySelectorAll(root) : globalScope;
        let onevent = e => {
            logger(`[gardener] cleanup on ${Object.getPrototypeOf(e)} "${e.type}"`);
            for (let node of scope)
                scissors(selector, words, node, params);
        };
        let repeater = n => {
            if (!domLoaded && n) {
                setTimeout(repeater, 500, n - 1);
                scope = getScope(params.root);
                if (!scope) // exit if the root element is not present on the page
                    return 0;
                onevent({type: 'Repeater'});
            }
        };
        repeater(20);
        document.addEventListener(
            'DOMContentLoaded', (e) => {
                domLoaded = true;
                // narrow down scope to a specific element
                scope = getScope(params.root);
                if (!scope) // exit if the root element is not present on the page
                    return 0;
                logger('[g] scope', scope);
                // add observe mode if required
                if (params.observe)
                {
                    let params = { childList:true, subtree: true };
                    let observer = new MutationObserver(
                        function(ms)
                        {
                            for (let m of ms)
                                if (m.addedNodes.length)
                                    onevent(m);
                        }
                    );
                    for (let node of scope)
                        observer.observe(node, params);
                    logger('[g] observer enabled');
                }
                onevent(e);
            }, false);
        // wait for a full page load to do one extra cut
        win.addEventListener('load', onevent, false);
    }

    // wrap popular methods to open a new tab to catch specific behaviours
    function createWindowOpenWrapper(openFunc, onClickFunc)
    {
        let _createElement = _Document.prototype.createElement,
            _appendChild = Element.prototype.appendChild,
            fakeNative = (f) => (f.toString = () => `function ${f.name}() { [native code] }`);

        let nt = new nullTools();
        fakeNative(openFunc);

        let parser = _createElement.call(document, 'a');
        let openWhitelist = (url, parent) => {
            parser.href = url;
            return parser.hostname === 'www.imdb.com' || parser.hostname === 'www.kinopoisk.ru' ||
                parent.hostname === 'radikal.ru' && url === void 0;
        };

        let redefineOpen = (root) => {
            if ('open' in root) {
                let _open = root.open.bind(root);
                nt.define(root, 'open', (...args) => {
                    if (openWhitelist(args[0], location)) {
                        console.log('Whitelisted popup:', ...args);
                        return _open(...args);
                    }
                    return openFunc(...args);
                });
            }
        };
        redefineOpen(win);

        function createElement(name) {
            '[native code]';
            // jshint validthis:true
            let el = _createElement.apply(this, arguments);
            // redefine window.open in first-party frames
            if (el instanceof HTMLIFrameElement || el instanceof HTMLObjectElement)
                el.addEventListener('load', (e) => {
                    try {
                        redefineOpen(e.target.contentWindow);
                    } catch(ignore) {}
                }, false);
            return el;
        }
        fakeNative(createElement);

        let redefineCreateElement = (obj) => {
            for (let root of [obj.document, _Document.prototype]) if ('createElement' in root)
                nt.define(root, 'createElement', createElement);
        };
        redefineCreateElement(win);

        // wrap window.open in newly added first-party frames
        Element.prototype.appendChild = function appendChild()
        {
            '[native code]';
            let el = _appendChild.apply(this, arguments);
            if (el instanceof HTMLIFrameElement) {
                try {
                    redefineOpen(el.contentWindow);
                    redefineCreateElement(el.contentWindow);
                } catch(ignore) {}
            }
            return el;
        };
        fakeNative(Element.prototype.appendChild);
    }

    // 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()
    {
        // call sandbox-me if in iframe and not whitelisted
        if (inIFrame) {
            win.top.postMessage({ name: 'sandbox-me', href: win.location.href }, '*');
            return;
        }

        scriptLander(() => {
            let open = (...args) => {
                '[native code]';
                console.warn('Site attempted to open a new window', args);
                if (new RegExp(`^https?://${location.hostname}/`).test(args[0])) // skip extra click in case of blocked popunder
                    location.assign(args[0]);
                return {
                    document: {
                        write: () => {},
                        writeln: () => {}
                    },
                    location: {}
                };
            };

            let clickHandler = (e) => {
                let link = e.target;
                if (!link.parentNode || !e.isTrusted ||
                    (link.href && link.href.trim().toLowerCase().indexOf('javascript') === 0))
                {
                    e.preventDefault();
                    console.warn('Blocked suspicious click event', e, 'on', e.target);
                }
            };

            createWindowOpenWrapper(open, clickHandler);

            console.log('Popup prevention enabled.');
        }, nullTools, createWindowOpenWrapper);
    }

    // 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 preventPopunders()
    {
        // create "close_me" event to call high-level window.close()
        let eventName = `close_me_${Math.random().toString(36).substr(2)}`;
        let callClose = () => (console.log('close call'), window.close());
        window.addEventListener(eventName, callClose, true);

        scriptLander(() => {
            // get host of a provided URL with help of an anchor object
            // unfortunately new URL(url, window.location) generates wrong URL in some cases
            let parseURL = document.createElement('A');
            let getHost = (url) => (parseURL.href = url, parseURL.hostname);
            // site went to a new tab and attempts to unload
            // call for high-level close through event
            let closeWindow = () => window.dispatchEvent(new CustomEvent(eventName, {}));
            // check is URL local or goes to different site
            let isLocal = (url) => {
                if (url === location.pathname || url === location.href)
                    return true; // URL points to current pathname or full address
                let host = getHost(url);
                let site = location.hostname;
                return host !== '' && // URLs with unusual protocol may have empty 'host'
                    (site === host || site.endsWith(`.${host}`) || host.endsWith(`.${site}`));
            };

            let _open = window.open.bind(window);
            let open = (...args) => {
                '[native code]';
                let url = args[0];
                if (url && isLocal(url))
                    window.addEventListener('beforeunload', closeWindow, true);
                // jshint validthis:true
                return _open(...args);
            };

            let clickHandler = (e) => {
                if (!e.target.parentNode || !e.isTrusted)
                    window.addEventListener('beforeunload', closeWindow, true);
            };

            createWindowOpenWrapper(open, clickHandler);

            console.log("Background redirect prevention enabled.");
        }, `let eventName="${eventName}"`, nullTools, createWindowOpenWrapper);
    }

    // Mix between check for popups and popunders
    // Significantly more agressive than both and can't be used as universal solution
    function preventPopMix()
    {
        if (inIFrame)
        {
            win.top.postMessage({ name: 'sandbox-me', href: win.location.href }, '*');
            return;
        }

        // create "close_me" event to call high-level window.close()
        let eventName = `close_me_${Math.random().toString(36).substr(2)}`;
        let callClose = () => (console.log('close call'), window.close());
        window.addEventListener(eventName, callClose, true);

        scriptLander(() => {
            let _open = window.open,
                parseURL = document.createElement('A');
            // get host of a provided URL with help of an anchor object
            // unfortunately new URL(url, window.location) generates wrong URL in some cases
            let getHost = (url) => (parseURL.href = url, parseURL.host);
            // site went to a new tab and attempts to unload
            // call for high-level close through event
            let closeWindow = () => (_open(window.location,'_self'), window.dispatchEvent(new CustomEvent(eventName, {})));
            // check is URL local or goes to different site
            function isLocal(url)
            {
                let loc = window.location;
                if (url === loc.pathname || url === loc.href)
                    return true; // URL points to current pathname or full address
                let host = getHost(url),
                    site = loc.host;
                if (host === '')
                    return false; // URLs with unusual protocol may have empty 'host'
                if (host.length > site.length)
                    [site, host] = [host, site];
                return site.includes(host, site.length - host.length);
            }

            // add check for redirect for 5 seconds, then disable it
            function checkRedirect()
            {
                window.addEventListener('beforeunload', closeWindow, true);
                setTimeout(closeWindow=>window.removeEventListener('beforeunload', closeWindow, true), 5000, closeWindow);
            }

            function open(url, name)
            {
                '[native code]';
                if (url && isLocal(url) && (!name || name === '_blank'))
                {
                    console.warn('Suspicious local new window', arguments);
                    checkRedirect();
                    // jshint validthis:true
                    return _open.apply(this, arguments);
                }
                console.warn('Blocked attempt to open a new window', arguments);
                return {
                    document: {
                        write: () => {},
                        writeln: () => {}
                    }
                };
            }

            function clickHandler(e)
            {
                let link = e.target,
                    url = link.href||'';
                if (e.targetParentNode && e.isTrusted || link.target !== '_blank')
                {
                    console.log('Link', link, 'were created dinamically, but looks fine.');
                    return true;
                }
                if (isLocal(url) && link.target === '_blank')
                {
                    console.log('Suspicious local link', link);
                    checkRedirect();
                    return;
                }
                console.log('Blocked suspicious click on a link', link);
                e.stopPropagation();
                e.preventDefault();
            }

            createWindowOpenWrapper(open, clickHandler);

            console.log("Mixed popups prevention enabled.");
        }, `let eventName="${eventName}"`, createWindowOpenWrapper);
    }
    // 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)
    {
        window.addEventListener(
            'message', function(e)
            {
                if (!e.data || e.data.name !== 'sandbox-me' || !e.data.href)
                    return;
                let src = e.data.href;
                for (let frame of document.querySelectorAll('iframe'))
                    if (frame.contentWindow === e.source)
                    {
                        if (frame.hasAttribute('sandbox'))
                        {
                            if (!frame.sandbox.contains('allow-popups'))
                                return; // exit frame since it's already sandboxed and popups are blocked
                            // 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
        );
    }

    function selectiveEval() {
        scriptLander(() => {
            let nt = new nullTools();
            let _eval = win.eval.bind(window);
            nt.define(win, 'eval', function(...args) {
                if (/_0x|location\s*?=|location.href\s*?=|location.assign\(|open\(/i.test(args[0])) {
                    console.log(`Skipped eval of ${args[0].slice(0, 512)}\u2026`);
                    return null;
                }
                return _eval(...args);
            });
        }, nullTools);
    }

    // === 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',
            'radikal.ru', 'rumedia.ws',
            'seedoff.cc', 'seedoff.tv',
            'thepiratebay.org', 'torseed.net',
            'unionpeer.com',
            'zippyshare.com'
        ],
        now: preventPopups
    };
    // Popunders (background redirect)
    scripts.preventPopunders = {
        other: [
            'lostfilm-online.ru',
            'mediafire.com', 'megapeer.org', 'megapeer.ru',
            'perfectgirls.net'
        ],
        now: preventPopunders
    };
    // PopMix (both types of popups encountered on site)
    scripts['openload.co'] = {
        other: ['oload.tv', 'oload.info'],
        now: () => {
            let nt = new nullTools();
            nt.define(win, 'CNight', win.CoinHive);
            if (location.pathname.startsWith('/embed/'))
            {
                nt.define(win, 'BetterJsPop', {
                    add: ((a, b) => console.warn('BetterJsPop.add', a, b)),
                    config: ((o) => console.warn('BetterJsPop.config', o)),
                    Browser: { isChrome: true }
                });
                nt.define(win, 'isSandboxed', nt.func(null));
                nt.define(win, 'adblock', false);
                nt.define(win, 'adblock2', false);
            } else
                preventPopMix();
        }
    };
    scripts['turbobit.net'] = preventPopMix;

    // workaround for moradu.com/apu.php load error handler script, not sure which ad network is this
    scripts['tapochek.net'] = () => {
        let _appendChild = Object.getOwnPropertyDescriptor(_Node.prototype, 'appendChild');
        let _appendChild_value = _appendChild.value;
        _appendChild.value = function appendChild(node) {
            if (this === win.document.body)
                if ((node instanceof HTMLScriptElement || node instanceof HTMLStyleElement) &&
                    /^https?:\/\/[0-9a-f]{15}\.com\/\d+(\/|\.css)$/.test(node.src) ||
                    node instanceof HTMLDivElement && node.style.zIndex > 900000 &&
                    node.style.backgroundImage.includes('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'))
                    throw '...eenope!';
            return _appendChild_value.apply(this, arguments);
        };
        Object.defineProperty(_Node.prototype, 'appendChild', _appendChild);

        preventPopups();
    };

    scripts['rustorka.com'] = {
        other: ['rustorka.lib'],
        now: () => scriptLander(() => {
            let nt = new nullTools({trace: true});
            nt.define(win, 'syka', false);
            nt.define(win, '_692293176244', location.href);
            [
                'MTLuxup', 'MTAdSniper', 'MTutarg', 'MTUAatar', 'MTcityAds', 'MTmxMark',
                'MTmxMark2', 'MTmdnt', 'MTrfDumedia', 'MXsmTDS', 'MTritorno', 'MTadvice',
                'cyka', 'MTAdTraff', 'MTExebid', 'MXsockFound'
            ].forEach(p => nt.define(win, p, nt.func(null, p)));
            let _eval_def = Object.getOwnPropertyDescriptor(win, 'eval');
            if (!_eval_def)
                return;
            let _eval_val = _eval_def.value;
            _eval_def.value = (...args) => {
                if (args[0] && args[0].includes('antiadblock'))
                    return console.log('Anti-AdBlock script may run another day, but not today.');
                return _eval_val.apply(this, args);
            };
            Object.defineProperty(win, 'eval', _eval_def);
            win.open = (...args) => {
                console.warn(`Site attempted to open "${args[0]}" in a new window.`);
                location.replace(location.href);
                return null;
            };
            window.addEventListener('DOMContentLoaded', () => {
                let link = void 0;
                document.body.addEventListener('mousedown', e => {
                    link = e.target.closest('a, select, #fancybox-title-wrap');
                }, false);
                let _open = window.open.bind(window);
                let _getAttribute = Element.prototype.getAttribute;
                win.open = (...args) => {
                    let url = args[0];
                    let allow = false;
                    if (link instanceof HTMLAnchorElement) {
                        // third-party post links
                        let href = _getAttribute.call(link, 'href');
                        if (link.classList.contains('postLink') &&
                            !link.matches(`a[href*="${location.hostname}"]`) &&
                            (href === url || link.href === url)) {
                            return _open(...args);
                        }
                        // onclick # links
                        if (href === '#' && /window\.open/.test(_getAttribute.call(link, 'onclick'))) {
                            return _open(...args);
                        }
                        // force local links to load in the current window
                        if (href[0] === '/' || href.startsWith('./') || href.includes(`//${location.hostname}/`))
                            location.assign(href);
                    }
                    // list of image hostings under upload picture button (new comment)
                    if (link instanceof HTMLSelectElement &&
                        !url.includes(location.hostname) &&
                        link.value === url) {
                        return _open(...args);
                    }
                    // open screenshot in a new window
                    if (link instanceof HTMLSpanElement &&
                        link.id === 'fancybox-title-wrap') {
                        return _open(...args);
                    }
                    // looks like tabunder
                    if (link === null && url === location.href)
                        location.replace(url); // reload current page
                    // other cases
                    console.warn(`Site attempted to open "${url}" in a new window. Source: `, link);
                    return {};
                };
            }, true);
        }, nullTools)
    };

    // other
    scripts['1tv.ru'] = () => scriptLander(() => {
        let nt = new nullTools();
        nt.define(win, 'EUMPAntiblockConfig', nt.proxy({url: '//www.1tv.ru/favicon.ico'}));
        let disablePlugins = {
            'antiblock': false,
            'stat1tv': false
        };
        let _EUMPConfig = void 0;
        let _EUMPConfig_set = x => {
            if (x.plugins) {
                x.plugins = x.plugins.filter(plugin => (plugin in disablePlugins) ? !(disablePlugins[plugin] = true) : true);
                console.warn(`Player plugins: active [${x.plugins}], disabled [${Object.keys(disablePlugins).filter(x => disablePlugins[x])}]`);
            }
            _EUMPConfig = x;
        };
        if ('EUMPConfig' in win)
            _EUMPConfig_set(win.EUMPConfig);
        Object.defineProperty(win, 'EUMPConfig', {
            enumerable: true,
            get: x => _EUMPConfig,
            set: _EUMPConfig_set
        });
    }, nullTools);

    scripts['2picsun.ru'] = {
        other: [
            'pics2sun.ru', '3pics-img.ru'
        ],
        now: () => {
            Object.defineProperty(navigator, 'userAgent', {value: 'googlebot'});
        }
    };

    scripts['4pda.ru'] = {
        now: () => {
            // 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 }',
                    'article + aside * { display: none !important }',
                    `section[id] {${(
                        'position: absolute;'+
                        'width: 100%'
                    )}}`,
                    `#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);
                    }

                    // clear background in the download frame
                    if (location.pathname.startsWith('/forum/dl/')) {
                        let setBackground = node => _setAttribute.call(
                            node,
                            'style', (_getAttribute.call(node, 'style') || '') +
                            ';background-color:#4ebaf6!important'
                        );
                        setBackground(document.body);
                        for (let itm of document.querySelectorAll('body > div'))
                            if (!itm.querySelector('.dw-fdwlink, .content') && !itm.classList.contains('footer')) {
                                remove(itm);
                            } else {
                                setBackground(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 true;
                            }
                        };
                    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['adhands.ru'] = () => scriptLander(() => {
        let nt = new nullTools();
        try {
            let _adv;
            Object.defineProperty(win, 'adv', {
                get: () => _adv,
                set: (v) => {
                    console.log('Blocked advert on adhands.ru.');
                    nt.define(v, 'advert', '');
                    _adv = v;
                }
            });
        } catch (ignore) {
            if (!win.adv)
                console.log('Unable to locate advert on adhands.ru.');
            else {
                console.log('Blocked advert on adhands.ru.');
                nt.define(win.adv, 'advert', '');
            }
        }
    }, nullTools);

    scripts['all-episodes.tv'] = () => {
        let nt = new nullTools();
        nt.define(win, 'perX1', 2);
        createStyle('#advtss, #ad3, a[href*="/ad.admitad.com/"] { display:none!important }');
    };

    scripts['allhentai.ru'] = () => {
        selectiveEval();
        preventPopups();
        scriptLander(() => {
            let _onerror = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'onerror');
            if (!_onerror)
                return;
            _onerror.set = (...args) => console.log(args[0].toString());
            Object.defineProperty(HTMLElement.prototype, 'onerror', _onerror);
        });
    };

    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'] = () => {
        scriptLander(() => {
            let nt = new nullTools();
            let fishki = {};
            nt.define(fishki, 'adv', nt.proxy({
                afterAdblockCheck: nt.func(null),
                refreshFloat: nt.func(null)
            }));
            nt.define(fishki, 'is_adblock', false);
            nt.define(win, 'fishki', fishki);
        }, nullTools);
        gardener('.drag_list > .drag_element, .list-view > .paddingtop15, .post-wrap', /543769|Новости\sпартнеров|Полезная\sреклама/);
    };

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

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

    scripts['gismeteo.ru'] = {
        other: ['gismeteo.ua'],
        now: () => gardener('div > script', /AdvManager/i, { observe: true, parent: 'div' })
    };

    scripts['hdrezka.ag'] = () => {
        Object.defineProperty(win, 'ab', { value: false, enumerable: true });
        gardener('div[id][onclick][onmouseup][onmousedown]', /onmouseout/i);
    };

    scripts['hideip.me'] = {
        now: () => scriptLander(() => {
            let _innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
            let _set_innerHTML = _innerHTML.set;
            let _innerText = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerText');
            let _get_innerText = _innerText.get;
            let div = document.createElement('div');
            _innerHTML.set = function(...args) {
                _set_innerHTML.call(div, args[0].replace('i','a'));
                if (args[0] && /[рp][еe]кл/.test(_get_innerText.call(div))||
                    /(\d\d\d?\.){3}\d\d\d?\:\d/.test(_get_innerText.call(this)) ) {
                    console.log('Anti-Adblock killed.');
                    return true;
                }
                _set_innerHTML.apply(this, args);
            };
            Object.defineProperty(Element.prototype, 'innerHTML', _innerHTML);
            Object.defineProperty(win, 'adblock', {
                get: x => false,
                set: x => null,
                enumerable: true
            });
            let _$ = {};
            let _$_map = new WeakMap();
            let _gOPD = Object.getOwnPropertyDescriptor(Object, 'getOwnPropertyDescriptor');
            let _val_gOPD = _gOPD.value;
            _gOPD.value = function(...args) {
                let _res = _val_gOPD.apply(this, args);
                if (args[0] instanceof Window && (args[1] === '$' || args[1] === 'jQuery')) {
                    delete _res.get;
                    delete _res.set;
                    _res.value = win[args[1]];
                }
                return _res;
            };
            Object.defineProperty(Object, 'getOwnPropertyDescriptor', _gOPD);
            let getJQWrap = (n) => {
                let name = n;
                return {
                    enumerable: true,
                    get: x => _$[name],
                    set: x => {
                        if (_$_map.has(x)) {
                            _$[name]  = _$_map.get(x);
                            return true;
                        }
                        if (x === _$.$ || x === _$.jQuery) {
                            _$[name] = x;
                            return true;
                        }
                        _$[name] = new Proxy(x, {
                            apply: (t, o, args) => {
                                let _res = t.apply(o, args);
                                if (_$_map.has(_res.is)) {
                                    _res.is = _$_map.get(_res.is);
                                } else {
                                    let _is = _res.is;
                                    _res.is = function(...args) {
                                        if (args[0] === ':hidden')
                                            return false;
                                        return _is.apply(this, args);
                                    };
                                    _$_map.set(_is, _res.is);
                                }
                                return _res;
                            }
                        });
                        _$_map.set(x, _$[name]);
                        return true;
                    }
                };
            };
            Object.defineProperty(win, '$', getJQWrap('$'));
            Object.defineProperty(win, 'jQuery', getJQWrap('jQuery'));
            let _dP = Object.defineProperty;
            Object.defineProperty = function(...args) {
                if (args[0] instanceof Window && (args[1] === '$' || args[1] === 'jQuery'))
                    return void 0;
                return _dP.apply(this, args);
            };
        })
    };

    scripts['igra-prestoloff.cx'] = () => scriptLander(() => {
        let nt = new nullTools();
        /*jslint evil: true */ // yes, evil, I know
        let _write = document.write.bind(document);
        /*jslint evil: false */
        nt.define(document, 'write', t => {
            let id = t.match(/jwplayer\("(\w+)"\)/i);
            if (id && id[1]) {
                return _write(`<div id="${id[1]}"></div>${t}`);
            } else {
                return _write('');
            }
        });
    });

    scripts['imageban.ru'] = {
        now: () => {
            Object.defineProperty(win, 'V7x1J', { get: () => null });
            //preventPopunders();
        }
    };

    scripts['kinopoisk.ru'] = {
        now: () => {
            // set no-branding body style
            createStyle('body:not(#id) { background: #d5d5d5 url(/images/noBrandBg.jpg) 50% 0 no-repeat !important }');
        },
        dom: () => {
            (style => style ? style.parentNode.removeChild(style) : console.log('Unable to locate branding style.')
            )(_de.querySelector('#branding-style'));
        }
    };

    scripts['korrespondent.net'] = {
        now: () => scriptLander(() => {
            let nt = new nullTools();
            nt.define(win, 'holder', function(id) {
                let div = document.getElementById(id);
                if (!div)
                    return;
                if (div.parentNode.classList.contains('col__sidebar')) {
                    div.parentNode.appendChild(div);
                    div.style.height = '300px';
                }
            });
        }, nullTools),
        dom: () => {
            for (let frame of document.querySelectorAll('.unit-side-informer > iframe'))
                frame.parentNode.style.width = '1px';
        }
    };

    scripts['mail.ru'] = () => scriptLander(() => {
        let nt = new nullTools();
        // Trick to prevent mail.ru from removing 3rd-party styles
        nt.define(Object.prototype, 'restoreVisibility', nt.func(null), false);
        // Disable some of their counters
        nt.define(win, 'rb_counter', nt.func(null, 'rb_counter'));
        if (location.hostname !== 'e.mail.ru')
            nt.define(win, 'createRadar', nt.func(nt.func(null, 'aRadar'), 'createRadar'));
        else
            nt.define(win, 'aRadar', nt.func(null, 'aRadar'));

        // Disable page scrambler on mail.ru to let extensions easily block ads there
        function defineLocator(root)
        {
            let _locator;
            let fishnet = {
                apply: (target, thisArg, args) => {
                    console.log(`locator.${target._name}(${JSON.stringify(args).slice(1,-1)})`);
                    return target.apply(thisArg, args);
                }
            };

            function wrapLocator(locator)
            {
                if ('setup' in locator)
                {
                    let _setup = locator.setup;
                    locator.setup = function(o)
                    {
                        if ('enable' in o)
                        {
                            o.enable = false;
                            console.log('Disable mimic mode.');
                        }
                        if ('links' in o)
                        {
                            o.links = [];
                            console.log('Call with empty list of sheets.');
                        }
                        return _setup.call(this, o);
                    };
                    locator.insertSheet = () => console.log('Ignore insertSheet.');
                    locator.wrap = () => console.log('Ignore wrap.');
                }
                try {
                    let names = [];
                    for (let name in locator)
                        if (locator[name] instanceof Function) {
                            locator[name]._name = name;
                            locator[name] = new Proxy(locator[name], fishnet);
                            names.push(name);
                        }
                    console.log(`[locator] wrapped properties: ${names.join(', ')}`);
                } catch(e) {
                    console.log(e);
                }
                _locator = locator;
            }

            if ('locator' in root && root.locator)
            {
                console.log('Found existing "locator" object. :|');
                _locator = root.locator;
                wrapLocator(root.locator);
            }

            let loc_desc = Object.getOwnPropertyDescriptor(root, 'locator');
            if (!loc_desc || loc_desc.set !== wrapLocator)
                try {
                    Object.defineProperty(root, 'locator', {
                        set: wrapLocator,
                        get: () => _locator
                    });
                } catch (err) {
                    console.log('Unable to redefine "locator" object!!!', err);
                }
        }

        function defineDetector(mr)
        {
            let __ = mr._ || {};

            if ('HONEYPOT' in __)
            {
                console.log('Disarming existing detector instance. :|', JSON.stringify(__));
                nt.define(__, 'HONEYPOT', '.honeypot_fake_class_to_miss');
                nt.define(__, 'STUCK_IN_POT', false);
            }

            __ = new Proxy(__, {
                get: (t, p) => t[p],
                set: (t, p, v) => {
                    console.log(`mr._.${p} =`, v);
                    if (['HONEYPOT', 'STUCK_IN_POT'].includes(p))
                        console.log('Not changed.');
                    t[p] = v; // setter in nt.define will prevent this when needed
                    return true;
                }
            });
            Object.defineProperty(mr, '_', {
                enumerable: true,
                value: __
            });
        }

        if (location.hostname === 'e.mail.ru')
            defineLocator(win);
        else {
            try {
                let _mr;
                Object.defineProperty(win, 'mr', {
                    enumerable: true,
                    get: () => _mr,
                    set: (v) => {
                        if (v === _mr)
                            return true;
                        console.log('Trapped new "mr" object.');
                        defineLocator(v.mimic ? v.mimic : v);
                        defineDetector(v);
                        _mr = v;
                    }
                });
                if (!('mr' in win))
                    throw 'Wat!?';
            } catch (e) {
                console.log('Found existing "mr" object.', e instanceof TypeError ? '' : e);
                defineLocator(win.mr);
                defineDetector(win.mr);
            }
        }
    }, nullTools);

    scripts['megogo.net'] = {
        now: () => {
            let nt = new nullTools();
            nt.define(win, 'adBlock', false);
            nt.define(win, 'showAdBlockMessage', nt.func(null));
        }
    };

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

    scripts['overclockers.ru'] = {
        now: () => scriptLander(() => {
            let _innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
            let _set_innerHTML = _innerHTML.set;
            _innerHTML.set = function() {
                if (this === document.body) {
                    console.log('Anti-Adblock killed.');
                    return true;
                }
                _set_innerHTML.apply(this, arguments);
            };
            Object.defineProperty(Element.prototype, 'innerHTML', _innerHTML);
        })
    };
    scripts['forums.overclockers.ru'] = {
        now: () => {
            createStyle('.needblock {position: fixed; left: -10000px}');
            Object.defineProperty(win, 'adblck', {
                get: () => 'no',
                set: () => undefined,
                enumerable: true
            });
        }
    };

    scripts['pb.wtf'] = {
        other: ['piratbit.org', 'piratbit.ru'],
        now: () => {
            // line above topic content and images in the slider in the header
            gardener(
                'a[href^="/exit/"], a[href^="/fxt/"], a[href$="=="]',
                /img|Реклама|center/i,
                { root: '.release-navbar,#page_content', observe: true, parent: 'div,tr' }
            );
            // ads in comments
            gardener('img[data-name="PiraBo"]', /./i, {root:'#main_content .table', observe:true, parent:'tr'});
        }
    };

    scripts['pikabu.ru'] = () => gardener('.story', /story__author[^>]+>ads</i, {root: '.inner_wrap', observe: true});

    scripts['peka2.tv'] = () => {
        let bodyClass = 'body--branding';
        let checkNode = node => {
            for (let className of node.classList)
                if (className.includes('banner') || className === bodyClass) {
                    _removeAttribute.call(node, 'style');
                    node.classList.remove(className);
                    for (let attr of Array.from(node.attributes)) {
                        if (attr.name.startsWith('advert'))
                            _removeAttribute.call(node, attr.name);
                    }
                }
        };
        (new MutationObserver(ms => {
            let m, node;
            for (m of ms) for (node of m.addedNodes)
                if (node instanceof HTMLElement)
                    checkNode(node);
        })).observe(_de, {childList: true, subtree: true});
        (new MutationObserver(ms => {
            for (let m of ms)
                checkNode(m.target);
        })).observe(_de, {attributes: true, subtree: true, attributeFilter: ['class']});
    };

    scripts['qrz.ru'] = {
        now: () => {
            let nt = new nullTools();
            nt.define(win, 'ab', false);
            nt.define(win, 'tryMessage', nt.func(null));
        }
    };

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

    scripts['rbc.ru'] = {
        dom: () => {
            let _preventDefault = Event.prototype.preventDefault;
            Event.prototype.preventDefault = function preventDefault()
            {
                let t = this.target;
                if (t instanceof HTMLAnchorElement || t.closest('A'))
                    throw new Error('an.yandex redirect prevention');
                return _preventDefault.call(this);
            };

            function cleaner(nodes)
            {
                for (let node of nodes)
                {
                    if (!node.classList || !node.classList.contains('js-yandex-counter'))
                        continue;
                    node.classList.remove('js-yandex-counter');
                    node.removeAttribute('data-yandex-name');
                    node.removeAttribute('data-yandex-params');
                }
            }
            cleaner(_de.querySelectorAll('.js-yandex-counter'));

            (new MutationObserver(
                ms => { for (let m of ms) cleaner(m.addedNodes); }
            )).observe(_de, {childList: true, subtree: true});
        }
    };

    scripts['rp5.ru'] = {
        other: ['rp5.by', 'rp5.kz', 'rp5.ua'],
        now: () => gardener('div[id][class]', /\?AdvertMgmt=|adsbygoogle/, { root: '#content-wrapper', log: true })
    };

    scripts['rutube.ru'] = () => scriptLander(() => {
        let _parse = JSON.parse.bind(JSON);
        let _skip_enabled = false;
        JSON.parse = (...args) => {
            let res = _parse(...args),
                log = false;
            if (!res)
                return res;
            // parse player configuration
            if ('appearance' in res || 'video_balancer' in res) {
                log = true;
                if ('appearance' in res) {
                    res.appearance.forbid_seek = false;
                    res.appearance.forbid_timeline_preview = false;
                }
                _skip_enabled = !!res.remove_unseekable_blocks;
                res.advert = [];
                for (let limit of res.limits)
                    limit.limit = 0;
                res.yast = null;
                res.yast_live_online = null;
                Object.defineProperty(res, 'stat', {
                    get: x => [],
                    set: x => true,
                    enumerable: true
                });
            }

            // parse video configuration
            if ('video_url' in res) {
                log = true;
                if ('cuepoints' in res && !_skip_enabled)
                    for (let point of res.cuepoints) {
                        point.is_pause = false;
                        point.show_navigation = true;
                        point.forbid_seek = false;
                    }
            }

            if (log)
                console.log('[rutube]', res);
            return res;
        };
    });

    scripts['simpsonsua.com.ua'] = () => scriptLander(() => {
        let _addEventListener = Object.getPrototypeOf(HTMLDocument).prototype.addEventListener;
        document.addEventListener = function(event, callback) {
            if (event === 'DOMContentLoaded' && callback.toString().includes('show_warning'))
                return;
            return _addEventListener.apply(this, arguments);
        };
    });

    scripts['spaces.ru'] = () => {
        gardener('div:not(.f-c_fll) > a[href*="spaces.ru/?Cl="]', /./, { parent: 'div' });
        gardener('.js-banner_rotator', /./, { parent: '.widgets-group' });
    };

    scripts['spam-club.blogspot.co.uk'] = () => {
        let _clientHeight = Object.getOwnPropertyDescriptor(Element.prototype, 'clientHeight'),
            _clientWidth = Object.getOwnPropertyDescriptor(Element.prototype, 'clientWidth');
        let wrapGetter = (getter) => {
            let _getter = getter;
            return function()
            {
                let _size = _getter.apply(this, arguments);
                return _size ? _size : 1;
            };
        };
        _clientHeight.get = wrapGetter(_clientHeight.get);
        _clientWidth.get = wrapGetter(_clientWidth.get);
        Object.defineProperty(Element.prototype, 'clientHeight', _clientHeight);
        Object.defineProperty(Element.prototype, 'clientWidth', _clientWidth);
        let _onload = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'onload'),
            _set_onload = _onload.set;
        _onload.set = function()
        {
            if (this instanceof HTMLImageElement)
                return true;
            _set_onload.apply(this, arguments);
        };
        Object.defineProperty(HTMLElement.prototype, 'onload', _onload);
    };

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

    scripts['sports.ru'] = {
        now: () => {
            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);
        },
        dom: () => {
            (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['stealthz.ru'] = {
        dom: () => {
            // skip timeout
            let $ = document.querySelector.bind(document);
            let [timer_1, timer_2] = [$('#timer_1'), $('#timer_2')];
            if (!timer_1 || !timer_2)
                return;
            timer_1.style.display = 'none';
            timer_2.style.display = 'block';
        }
    };

    scripts['yap.ru'] = {
        other: ['yaplakal.com'],
        now: () => {
            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','media.eagleplatform.com','lenta.ru', 'quto.ru'],
        now: () => scriptLander(() => {
            // Prevent autoplay
            if (!('EaglePlayer' in win)) {
                let _EaglePlayer = void 0;
                Object.defineProperty(win, 'EaglePlayer', {
                    enumerable: true,
                    get: x => _EaglePlayer,
                    set: x => {
                        if (x === _EaglePlayer)
                            return true;
                        _EaglePlayer = x;
                        let _init = void 0;
                        Object.defineProperty(_EaglePlayer.prototype, 'init', {
                            enumerable: true,
                            set: x => _init = x,
                            get: function() {
                                let _self = this;
                                if (_self.options) {
                                    Object.defineProperty(this.options, 'autoplay', {
                                        enumerable: true,
                                        get: x => false,
                                        set: x => null
                                    });
                                }
                                if (_self.options && _self.options.el && inIFrame) {
                                    let addListener = (player) => {
                                        console.log('Attached autostop');
                                        player.addEventListener('canplay', e => {
                                            e.target.play = () => console.log('Applied autostop.');
                                            setTimeout(player => {
                                                delete player.play;
                                                console.log('Detached autostop');
                                            }, 1500, e.target);
                                        }, false);
                                    };
                                    (new MutationObserver(ms => {
                                        let m, node;
                                        for (m of ms) for (node of m.addedNodes)
                                            if (node instanceof HTMLVideoElement)
                                                addListener(node);
                                    })).observe(this.options.el, { childList: true, subtree: true });
                                }
                                return _init;
                            }
                        });
                    }
                });
                let _setAttribute = Element.prototype.setAttribute;
                let isAutoplay = /^autoplay$/i;
                Element.prototype.setAttribute = function setAttribute(name)
                {
                    if (!this._stopped && isAutoplay.test(name))
                    {
                        console.log('Prevented assigning autoplay attribute.');
                        return null;
                    }
                    return _setAttribute.apply(this, arguments);
                };
            } else if (inIFrame) {
                let _setAttribute = Element.prototype.setAttribute;
                let isAutoplay = /^autoplay$/i;
                Element.prototype.setAttribute = function setAttribute(name)
                {
                    if (!this._stopped && isAutoplay.test(name))
                    {
                        console.log('Prevented assigning autoplay attribute.');
                        this._stopped = true;
                        this.play = () => {
                            console.log('Prevented attempt to force-start playback.');
                            delete this.play;
                        };
                        return null;
                    }
                    return _setAttribute.apply(this, arguments);
                };
            }
            if (location.hostname.endsWith('.media.eagleplatform.com'))
                return;
            let CSSRuleProto = 'cssText' in CSSRule.prototype ? CSSRule.prototype : CSSStyleRule.prototype;
            let _cssText = Object.getOwnPropertyDescriptor(CSSRuleProto, 'cssText');
            let _cssText_get = _cssText.get;
            _cssText.configurable = false;
            _cssText.get = function()
            {
                let cssText = _cssText_get.call(this);
                if (cssText.includes('content:'))
                {
                    console.log('Blocked access to suspicious cssText:', cssText.slice(0,60), '\u2026', cssText.length);
                    return null;
                }
                return cssText;
            };
            Object.defineProperty(CSSRuleProto, 'cssText', _cssText);
            // fake global Adf object
            let nt = new nullTools();
            let Adf_banner = {};
            [
                'reloadssp', 'sspScroll',
                'sspRich', 'ssp'
            ].forEach(name => Adf_banner[name] = nt.proxy(() => new Promise((r,j) => r({status: true}))));
            nt.define(win, 'Adf', nt.proxy({
                banner: nt.proxy(Adf_banner)
            }));
            // extra script to remove partner news on gazeta.ru
            if (!location.hostname.includes('gazeta.ru'))
                return;
            (new MutationObserver(
                (ms) => {
                    let m, node, header;
                    for (m of ms) for (node of m.addedNodes)
                        if (node instanceof HTMLDivElement && node.matches('.sausage'))
                        {
                            header = node.querySelector('.sausage-header');
                            if (header && /новости\s+партн[её]ров/i.test(header.textContent))
                                node.style.display = 'none';
                        }
                }
            )).observe(document.documentElement, { childList:true, subtree: true });
        }, `let inIFrame = ${inIFrame}`, nullTools)
    };

    scripts['reactor.cc'] = {
        other: ['joyreactor.cc', 'pornreactor.cc'],
        now: () => {
            selectiveEval();
            scriptLander(() => {
                let nt = new nullTools();
                win.open = (function(){ throw new Error('Redirect prevention.'); }).bind(window);
                nt.define(win, 'Worker', function(){});
                nt.define(win, 'JRCH', win.CoinHive);
            }, nullTools);
        },
        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'] = () => {
        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: () => {
            let dis = document.querySelector('label[class*="cb-disable"]');
            if (dis)
                dis.click();
        },
        click: () => {
            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;
    // add alternative domain names if present and wrap functions into objects
    for (let name in scripts)
    {
        if (scripts[name] instanceof Function)
            scripts[name] = { now: scripts[name] };
        for (domain of (scripts[name].other||[]))
        {
            if (domain in scripts)
                console.log('Error in scripts list. Script for', name, 'replaced script for', domain);
            scripts[domain] = scripts[name];
        }
        delete scripts[name].other;
    }
    // look for current domain in the list and run appropriate code
    domain = document.domain;
    while (domain.indexOf('.') > -1)
    {
        if (domain in scripts) for (let when in scripts[domain])
            switch(when)
            {
                case 'now':
                    scripts[domain][when]();
                    break;
                case 'dom':
                    document.addEventListener('DOMContentLoaded', scripts[domain][when], false);
                    break;
                default:
                    document.addEventListener (when, scripts[domain][when], false);
            }
        domain = domain.slice(domain.indexOf('.') + 1);
    }

    // Batch script lander
    if (!skipLander)
        landScript(batchLand, batchPrepend);

    { // JS Fixes Tools Menu
        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 Tools';
            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
            );

            let sObjBtn = _createElement('button');
            sObjBtn.onclick = getStrangeObjectsList;
            sObjBtn.textContent = 'Print (in console) list of unusual window properties';
            inner.appendChild(_createElement('br'));
            inner.appendChild(sObjBtn);

            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
        );
    }
})();