2019-07-06 18:36:28 +02:00
|
|
|
/*******************************************************************************
|
|
|
|
|
|
|
|
uBlock Origin - a browser extension to block requests.
|
|
|
|
Copyright (C) 2019-present Raymond Hill
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
|
|
|
|
Home: https://github.com/gorhill/uBlock
|
|
|
|
|
|
|
|
The scriptlets below are meant to be injected only into a
|
|
|
|
web page context.
|
|
|
|
*/
|
|
|
|
|
2019-07-08 14:56:36 +02:00
|
|
|
// The lines below are skipped by the resource parser. Purpose is clean
|
|
|
|
// jshinting.
|
|
|
|
(function() {
|
|
|
|
// >>>> start of private namespace
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
|
|
|
|
/// abort-current-inline-script.js
|
|
|
|
/// alias acis.js
|
|
|
|
(function() {
|
|
|
|
const target = '{{1}}';
|
|
|
|
if ( target === '' || target === '{{1}}' ) { return; }
|
|
|
|
const needle = '{{2}}';
|
|
|
|
let reText = '.?';
|
|
|
|
if ( needle !== '' && needle !== '{{2}}' ) {
|
|
|
|
reText = /^\/.+\/$/.test(needle)
|
|
|
|
? needle.slice(1,-1)
|
|
|
|
: needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
const thisScript = document.currentScript;
|
|
|
|
const re = new RegExp(reText);
|
|
|
|
const chain = target.split('.');
|
|
|
|
let owner = window;
|
|
|
|
let prop;
|
|
|
|
for (;;) {
|
|
|
|
prop = chain.shift();
|
|
|
|
if ( chain.length === 0 ) { break; }
|
|
|
|
owner = owner[prop];
|
|
|
|
if ( owner instanceof Object === false ) { return; }
|
|
|
|
}
|
2020-02-09 16:35:18 +01:00
|
|
|
let value;
|
2020-02-24 19:40:17 +01:00
|
|
|
let desc = Object.getOwnPropertyDescriptor(owner, prop);
|
2020-02-09 16:35:18 +01:00
|
|
|
if (
|
|
|
|
desc instanceof Object === false ||
|
|
|
|
desc.get instanceof Function === false
|
|
|
|
) {
|
|
|
|
value = owner[prop];
|
2020-02-24 19:40:17 +01:00
|
|
|
desc = undefined;
|
2020-02-09 16:35:18 +01:00
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
const magic = String.fromCharCode(Date.now() % 26 + 97) +
|
|
|
|
Math.floor(Math.random() * 982451653 + 982451653).toString(36);
|
|
|
|
const validate = function() {
|
|
|
|
const e = document.currentScript;
|
|
|
|
if (
|
|
|
|
e instanceof HTMLScriptElement &&
|
|
|
|
e.src === '' &&
|
|
|
|
e !== thisScript &&
|
|
|
|
re.test(e.textContent)
|
|
|
|
) {
|
|
|
|
throw new ReferenceError(magic);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Object.defineProperty(owner, prop, {
|
|
|
|
get: function() {
|
|
|
|
validate();
|
2020-02-24 19:40:17 +01:00
|
|
|
return desc instanceof Object
|
|
|
|
? desc.get()
|
|
|
|
: value;
|
2019-07-06 18:36:28 +02:00
|
|
|
},
|
|
|
|
set: function(a) {
|
|
|
|
validate();
|
2020-02-24 19:40:17 +01:00
|
|
|
if ( desc instanceof Object ) {
|
|
|
|
desc.set(a);
|
|
|
|
} else {
|
|
|
|
value = a;
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
const oe = window.onerror;
|
|
|
|
window.onerror = function(msg) {
|
|
|
|
if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if ( oe instanceof Function ) {
|
|
|
|
return oe.apply(this, arguments);
|
|
|
|
}
|
|
|
|
}.bind();
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
/// abort-on-property-read.js
|
|
|
|
/// alias aopr.js
|
|
|
|
(function() {
|
|
|
|
const magic = String.fromCharCode(Date.now() % 26 + 97) +
|
|
|
|
Math.floor(Math.random() * 982451653 + 982451653).toString(36);
|
|
|
|
const abort = function() {
|
|
|
|
throw new ReferenceError(magic);
|
|
|
|
};
|
|
|
|
const makeProxy = function(owner, chain) {
|
|
|
|
const pos = chain.indexOf('.');
|
|
|
|
if ( pos === -1 ) {
|
|
|
|
const desc = Object.getOwnPropertyDescriptor(owner, chain);
|
|
|
|
if ( !desc || desc.get !== abort ) {
|
|
|
|
Object.defineProperty(owner, chain, {
|
|
|
|
get: abort,
|
|
|
|
set: function(){}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const prop = chain.slice(0, pos);
|
|
|
|
let v = owner[prop];
|
2019-07-22 13:32:39 +02:00
|
|
|
chain = chain.slice(pos + 1);
|
2019-07-06 18:36:28 +02:00
|
|
|
if ( v ) {
|
|
|
|
makeProxy(v, chain);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const desc = Object.getOwnPropertyDescriptor(owner, prop);
|
|
|
|
if ( desc && desc.set !== undefined ) { return; }
|
|
|
|
Object.defineProperty(owner, prop, {
|
|
|
|
get: function() { return v; },
|
|
|
|
set: function(a) {
|
|
|
|
v = a;
|
|
|
|
if ( a instanceof Object ) {
|
|
|
|
makeProxy(a, chain);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const owner = window;
|
|
|
|
let chain = '{{1}}';
|
|
|
|
makeProxy(owner, chain);
|
|
|
|
const oe = window.onerror;
|
|
|
|
window.onerror = function(msg, src, line, col, error) {
|
|
|
|
if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if ( oe instanceof Function ) {
|
|
|
|
return oe(msg, src, line, col, error);
|
|
|
|
}
|
|
|
|
}.bind();
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
/// abort-on-property-write.js
|
|
|
|
/// alias aopw.js
|
|
|
|
(function() {
|
|
|
|
const magic = String.fromCharCode(Date.now() % 26 + 97) +
|
|
|
|
Math.floor(Math.random() * 982451653 + 982451653).toString(36);
|
|
|
|
let prop = '{{1}}';
|
|
|
|
let owner = window;
|
|
|
|
for (;;) {
|
|
|
|
const pos = prop.indexOf('.');
|
|
|
|
if ( pos === -1 ) { break; }
|
|
|
|
owner = owner[prop.slice(0, pos)];
|
|
|
|
if ( owner instanceof Object === false ) { return; }
|
|
|
|
prop = prop.slice(pos + 1);
|
|
|
|
}
|
|
|
|
delete owner[prop];
|
|
|
|
Object.defineProperty(owner, prop, {
|
|
|
|
set: function() {
|
|
|
|
throw new ReferenceError(magic);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const oe = window.onerror;
|
|
|
|
window.onerror = function(msg, src, line, col, error) {
|
|
|
|
if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if ( oe instanceof Function ) {
|
|
|
|
return oe(msg, src, line, col, error);
|
|
|
|
}
|
|
|
|
}.bind();
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2020-09-22 15:59:04 +02:00
|
|
|
/// abort-on-stack-trace.js
|
|
|
|
/// alias aost.js
|
|
|
|
// Status is currently experimental
|
|
|
|
(function() {
|
|
|
|
let chain = '{{1}}';
|
|
|
|
let needle = '{{2}}';
|
|
|
|
let logLevel = '{{3}}';
|
|
|
|
const reRegexEscape = /[.*+?^${}()|[\]\\]/g;
|
|
|
|
if ( needle === '' || needle === '{{2}}' ) {
|
|
|
|
needle = '^';
|
|
|
|
} else if ( /^\/.+\/$/.test(needle) ) {
|
|
|
|
needle = needle.slice(1,-1);
|
|
|
|
} else {
|
|
|
|
needle = needle.replace(reRegexEscape, '\\$&');
|
|
|
|
}
|
2020-09-23 12:54:44 +02:00
|
|
|
const reNeedle = new RegExp(needle);
|
2020-09-22 15:59:04 +02:00
|
|
|
const magic = String.fromCharCode(Math.random() * 26 + 97) +
|
|
|
|
Math.floor(
|
|
|
|
(0.25 + Math.random() * 0.75) * Number.MAX_SAFE_INTEGER
|
|
|
|
).toString(36).slice(-8);
|
|
|
|
const log = console.log.bind(console);
|
|
|
|
const ErrorCtor = self.Error;
|
|
|
|
const mustAbort = function(err) {
|
|
|
|
let docURL = self.location.href;
|
|
|
|
const pos = docURL.indexOf('#');
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
docURL = docURL.slice(0, pos);
|
|
|
|
}
|
2020-09-23 12:54:44 +02:00
|
|
|
// Normalize stack trace
|
|
|
|
const lines = [];
|
|
|
|
for ( let line of err.stack.split(/[\n\r]+/) ) {
|
|
|
|
if ( line.includes(magic) ) { continue; }
|
|
|
|
line = line.trim();
|
|
|
|
let match = /(.*?@)?(\S+)(:\d+):\d+\)?$/.exec(line);
|
|
|
|
if ( match === null ) { continue; }
|
|
|
|
let url = match[2];
|
|
|
|
if ( url.startsWith('(') ) { url = url.slice(1); }
|
|
|
|
if ( url === docURL ) {
|
2020-09-23 15:41:00 +02:00
|
|
|
url = 'inlineScript';
|
2020-09-23 12:54:44 +02:00
|
|
|
} else if ( url.startsWith('<anonymous>') ) {
|
|
|
|
url = 'injectedScript';
|
|
|
|
}
|
|
|
|
let fn = match[1] !== undefined
|
|
|
|
? match[1].slice(0, -1)
|
|
|
|
: line.slice(0, match.index).trim();
|
|
|
|
if ( fn.startsWith('at') ) { fn = fn.slice(2).trim(); }
|
|
|
|
let rowcol = match[3];
|
|
|
|
lines.push(' ' + `${fn} ${url}${rowcol}:1`.trim());
|
|
|
|
}
|
|
|
|
lines[0] = `stackDepth:${lines.length-1}`;
|
|
|
|
const stack = lines.join('\t');
|
2020-09-22 15:59:04 +02:00
|
|
|
const r = reNeedle.test(stack);
|
|
|
|
if (
|
|
|
|
logLevel === '1' ||
|
|
|
|
logLevel === '2' && r ||
|
|
|
|
logLevel === '3' && !r
|
|
|
|
) {
|
2020-09-23 12:54:44 +02:00
|
|
|
log(stack.replace(/\t/g, '\n'));
|
2020-09-22 15:59:04 +02:00
|
|
|
}
|
|
|
|
return r;
|
|
|
|
};
|
|
|
|
const makeProxy = function(owner, chain) {
|
|
|
|
const pos = chain.indexOf('.');
|
|
|
|
if ( pos === -1 ) {
|
|
|
|
let v = owner[chain];
|
|
|
|
Object.defineProperty(owner, chain, {
|
|
|
|
get: function() {
|
|
|
|
if ( mustAbort(new ErrorCtor(magic)) ) {
|
|
|
|
throw new ReferenceError(magic);
|
|
|
|
}
|
|
|
|
return v;
|
|
|
|
},
|
|
|
|
set: function(a) {
|
|
|
|
if ( mustAbort(new ErrorCtor(magic)) ) {
|
|
|
|
throw new ReferenceError(magic);
|
|
|
|
}
|
|
|
|
v = a;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const prop = chain.slice(0, pos);
|
|
|
|
let v = owner[prop];
|
|
|
|
chain = chain.slice(pos + 1);
|
|
|
|
if ( v ) {
|
|
|
|
makeProxy(v, chain);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const desc = Object.getOwnPropertyDescriptor(owner, prop);
|
|
|
|
if ( desc && desc.set !== undefined ) { return; }
|
|
|
|
Object.defineProperty(owner, prop, {
|
|
|
|
get: function() { return v; },
|
|
|
|
set: function(a) {
|
|
|
|
v = a;
|
|
|
|
if ( a instanceof Object ) {
|
|
|
|
makeProxy(a, chain);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const owner = window;
|
|
|
|
makeProxy(owner, chain);
|
|
|
|
const oe = window.onerror;
|
|
|
|
window.onerror = function(msg, src, line, col, error) {
|
|
|
|
if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if ( oe instanceof Function ) {
|
|
|
|
return oe(msg, src, line, col, error);
|
|
|
|
}
|
|
|
|
}.bind();
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
/// addEventListener-defuser.js
|
|
|
|
/// alias aeld.js
|
|
|
|
(function() {
|
|
|
|
let needle1 = '{{1}}';
|
|
|
|
if ( needle1 === '' || needle1 === '{{1}}' ) {
|
|
|
|
needle1 = '.?';
|
|
|
|
} else if ( /^\/.+\/$/.test(needle1) ) {
|
|
|
|
needle1 = needle1.slice(1,-1);
|
|
|
|
} else {
|
|
|
|
needle1 = needle1.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
needle1 = new RegExp(needle1);
|
|
|
|
let needle2 = '{{2}}';
|
|
|
|
if ( needle2 === '' || needle2 === '{{2}}' ) {
|
|
|
|
needle2 = '.?';
|
|
|
|
} else if ( /^\/.+\/$/.test(needle2) ) {
|
|
|
|
needle2 = needle2.slice(1,-1);
|
|
|
|
} else {
|
|
|
|
needle2 = needle2.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
needle2 = new RegExp(needle2);
|
|
|
|
self.EventTarget.prototype.addEventListener = new Proxy(
|
|
|
|
self.EventTarget.prototype.addEventListener,
|
|
|
|
{
|
|
|
|
apply: function(target, thisArg, args) {
|
2020-07-16 15:55:06 +02:00
|
|
|
const type = String(args[0]);
|
2019-07-06 18:36:28 +02:00
|
|
|
const handler = String(args[1]);
|
|
|
|
if (
|
|
|
|
needle1.test(type) === false ||
|
|
|
|
needle2.test(handler) === false
|
|
|
|
) {
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
/// addEventListener-logger.js
|
|
|
|
/// alias aell.js
|
|
|
|
(function() {
|
|
|
|
const log = console.log.bind(console);
|
|
|
|
self.EventTarget.prototype.addEventListener = new Proxy(
|
|
|
|
self.EventTarget.prototype.addEventListener,
|
|
|
|
{
|
|
|
|
apply: function(target, thisArg, args) {
|
2020-07-16 15:55:06 +02:00
|
|
|
const type = String(args[0]);
|
2019-07-06 18:36:28 +02:00
|
|
|
const handler = String(args[1]);
|
2020-04-28 17:19:26 +02:00
|
|
|
log('uBO: addEventListener("%s", %s)', type, handler);
|
2019-07-06 18:36:28 +02:00
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-09-09 20:06:23 +02:00
|
|
|
/// json-prune.js
|
2020-04-28 15:47:03 +02:00
|
|
|
//
|
|
|
|
// When no "prune paths" argument is provided, the scriptlet is
|
|
|
|
// used for logging purpose and the "needle paths" argument is
|
|
|
|
// used to filter logging output.
|
2019-09-09 20:06:23 +02:00
|
|
|
(function() {
|
|
|
|
const rawPrunePaths = '{{1}}';
|
|
|
|
const rawNeedlePaths = '{{2}}';
|
|
|
|
const prunePaths = rawPrunePaths !== '{{1}}' && rawPrunePaths !== ''
|
|
|
|
? rawPrunePaths.split(/ +/)
|
|
|
|
: [];
|
2020-04-28 15:47:03 +02:00
|
|
|
let needlePaths;
|
|
|
|
let log, reLogNeedle;
|
|
|
|
if ( prunePaths.length !== 0 ) {
|
|
|
|
needlePaths = prunePaths.length !== 0 &&
|
|
|
|
rawNeedlePaths !== '{{2}}' && rawNeedlePaths !== ''
|
|
|
|
? rawNeedlePaths.split(/ +/)
|
|
|
|
: [];
|
|
|
|
} else {
|
|
|
|
log = console.log.bind(console);
|
|
|
|
let needle;
|
|
|
|
if ( rawNeedlePaths === '' || rawNeedlePaths === '{{2}}' ) {
|
|
|
|
needle = '.?';
|
|
|
|
} else if ( rawNeedlePaths.charAt(0) === '/' && rawNeedlePaths.slice(-1) === '/' ) {
|
|
|
|
needle = rawNeedlePaths.slice(1, -1);
|
|
|
|
} else {
|
|
|
|
needle = rawNeedlePaths.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
reLogNeedle = new RegExp(needle);
|
|
|
|
}
|
2020-06-26 16:03:48 +02:00
|
|
|
const findOwner = function(root, path, prune = false) {
|
2019-09-09 20:06:23 +02:00
|
|
|
let owner = root;
|
|
|
|
let chain = path;
|
|
|
|
for (;;) {
|
2020-09-11 14:30:11 +02:00
|
|
|
if ( typeof owner !== 'object' || owner === null ) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-09-09 20:06:23 +02:00
|
|
|
const pos = chain.indexOf('.');
|
|
|
|
if ( pos === -1 ) {
|
2020-06-26 16:03:48 +02:00
|
|
|
const found = owner.hasOwnProperty(chain);
|
|
|
|
if ( found === false ) { return false; }
|
|
|
|
if ( prune ) {
|
|
|
|
delete owner[chain];
|
|
|
|
}
|
|
|
|
return true;
|
2019-09-09 20:06:23 +02:00
|
|
|
}
|
|
|
|
const prop = chain.slice(0, pos);
|
2020-06-26 16:03:48 +02:00
|
|
|
if (
|
|
|
|
prop === '[]' && Array.isArray(owner) ||
|
|
|
|
prop === '*' && owner instanceof Object
|
|
|
|
) {
|
|
|
|
const next = chain.slice(pos + 1);
|
|
|
|
let found = false;
|
2020-09-11 14:30:11 +02:00
|
|
|
for ( const key of Object.keys(owner) ) {
|
|
|
|
found = findOwner(owner[key], next, prune) || found;
|
2020-06-26 16:03:48 +02:00
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
if ( owner.hasOwnProperty(prop) === false ) { return false; }
|
2019-09-09 20:06:23 +02:00
|
|
|
owner = owner[prop];
|
|
|
|
chain = chain.slice(pos + 1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const mustProcess = function(root) {
|
|
|
|
for ( const needlePath of needlePaths ) {
|
2020-06-26 16:03:48 +02:00
|
|
|
if ( findOwner(root, needlePath) === false ) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-09-09 20:06:23 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
2020-11-08 14:45:33 +01:00
|
|
|
const pruner = function(o) {
|
|
|
|
if ( log !== undefined ) {
|
|
|
|
const json = JSON.stringify(o, null, 2);
|
|
|
|
if ( reLogNeedle.test(json) ) {
|
|
|
|
log('uBO:', location.hostname, json);
|
|
|
|
}
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
if ( mustProcess(o) === false ) { return o; }
|
|
|
|
for ( const path of prunePaths ) {
|
|
|
|
findOwner(o, path, true);
|
|
|
|
}
|
|
|
|
return o;
|
|
|
|
};
|
2019-09-09 20:06:23 +02:00
|
|
|
JSON.parse = new Proxy(JSON.parse, {
|
|
|
|
apply: function() {
|
2020-11-08 14:45:33 +01:00
|
|
|
return pruner(Reflect.apply(...arguments));
|
|
|
|
},
|
|
|
|
});
|
|
|
|
Response.prototype.json = new Proxy(Response.prototype.json, {
|
|
|
|
apply: function() {
|
|
|
|
return Reflect.apply(...arguments).then(o => pruner(o));
|
2019-09-09 20:06:23 +02:00
|
|
|
},
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-07-10 14:11:51 +02:00
|
|
|
// Imported from:
|
|
|
|
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L126
|
|
|
|
//
|
|
|
|
// Speed up or down setInterval, 3 optional arguments.
|
|
|
|
// The payload matcher, a string literal or a JavaScript RegExp, defaults
|
|
|
|
// to match all.
|
|
|
|
// delayMatcher
|
|
|
|
// The delay matcher, an integer, defaults to 1000.
|
|
|
|
// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by
|
|
|
|
// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up
|
|
|
|
// 20 times. Speed up and down both cap at 50 times.
|
2019-07-06 18:36:28 +02:00
|
|
|
/// nano-setInterval-booster.js
|
|
|
|
/// alias nano-sib.js
|
|
|
|
(function() {
|
|
|
|
let needle = '{{1}}';
|
|
|
|
let delay = parseInt('{{2}}', 10);
|
|
|
|
let boost = parseFloat('{{3}}');
|
|
|
|
if ( needle === '' || needle === '{{1}}' ) {
|
|
|
|
needle = '.?';
|
|
|
|
} else if ( needle.charAt(0) === '/' && needle.slice(-1) === '/' ) {
|
|
|
|
needle = needle.slice(1, -1);
|
|
|
|
} else {
|
|
|
|
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
needle = new RegExp(needle);
|
|
|
|
if ( isNaN(delay) || !isFinite(delay) ) {
|
|
|
|
delay = 1000;
|
|
|
|
}
|
|
|
|
if ( isNaN(boost) || !isFinite(boost) ) {
|
|
|
|
boost = 0.05;
|
|
|
|
}
|
|
|
|
if ( boost < 0.02 ) {
|
|
|
|
boost = 0.02;
|
|
|
|
}
|
|
|
|
if ( boost > 50 ) {
|
|
|
|
boost = 50;
|
|
|
|
}
|
|
|
|
window.setInterval = new Proxy(window.setInterval, {
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
const a = args[0];
|
|
|
|
const b = args[1];
|
|
|
|
if ( b === delay && needle.test(a.toString()) ) {
|
|
|
|
args[1] = b * boost;
|
|
|
|
}
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-07-10 14:11:51 +02:00
|
|
|
// Imported from:
|
|
|
|
// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L82
|
|
|
|
//
|
|
|
|
// Speed up or down setTimeout, 3 optional arguments.
|
|
|
|
// funcMatcher
|
|
|
|
// The payload matcher, a string literal or a JavaScript RegExp, defaults
|
|
|
|
// to match all.
|
|
|
|
// delayMatcher
|
|
|
|
// The delay matcher, an integer, defaults to 1000.
|
|
|
|
// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by
|
|
|
|
// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up
|
|
|
|
// 20 times. Speed up and down both cap at 50 times.
|
2019-07-06 18:36:28 +02:00
|
|
|
/// nano-setTimeout-booster.js
|
|
|
|
/// alias nano-stb.js
|
|
|
|
(function() {
|
|
|
|
let needle = '{{1}}';
|
|
|
|
let delay = parseInt('{{2}}', 10);
|
|
|
|
let boost = parseFloat('{{3}}');
|
|
|
|
if ( needle === '' || needle === '{{1}}' ) {
|
|
|
|
needle = '.?';
|
|
|
|
} else if ( needle.startsWith('/') && needle.endsWith('/') ) {
|
|
|
|
needle = needle.slice(1, -1);
|
|
|
|
} else {
|
|
|
|
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
needle = new RegExp(needle);
|
|
|
|
if ( isNaN(delay) || !isFinite(delay) ) {
|
|
|
|
delay = 1000;
|
|
|
|
}
|
|
|
|
if ( isNaN(boost) || !isFinite(boost) ) {
|
|
|
|
boost = 0.05;
|
|
|
|
}
|
|
|
|
if ( boost < 0.02 ) {
|
|
|
|
boost = 0.02;
|
|
|
|
}
|
|
|
|
if ( boost > 50 ) {
|
|
|
|
boost = 50;
|
|
|
|
}
|
|
|
|
window.setTimeout = new Proxy(window.setTimeout, {
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
const a = args[0];
|
|
|
|
const b = args[1];
|
|
|
|
if ( b === delay && needle.test(a.toString()) ) {
|
|
|
|
args[1] = b * boost;
|
|
|
|
}
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
/// noeval-if.js
|
|
|
|
(function() {
|
|
|
|
let needle = '{{1}}';
|
|
|
|
if ( needle === '' || needle === '{{1}}' ) {
|
|
|
|
needle = '.?';
|
|
|
|
} else if ( needle.slice(0,1) === '/' && needle.slice(-1) === '/' ) {
|
|
|
|
needle = needle.slice(1,-1);
|
|
|
|
} else {
|
|
|
|
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
needle = new RegExp(needle);
|
|
|
|
window.eval = new Proxy(window.eval, { // jshint ignore: line
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
const a = args[0];
|
|
|
|
if ( needle.test(a.toString()) === false ) {
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2020-12-11 14:29:23 +01:00
|
|
|
/// no-fetch-if.js
|
|
|
|
(function() {
|
|
|
|
let arg1 = '{{1}}';
|
|
|
|
if ( arg1 === '{{1}}' ) { arg1 = ''; }
|
|
|
|
const needles = [];
|
|
|
|
for ( const condition of arg1.split(/\s+/) ) {
|
2020-12-11 15:28:29 +01:00
|
|
|
if ( condition === '' ) { continue; }
|
2020-12-11 14:29:23 +01:00
|
|
|
const pos = condition.indexOf(':');
|
|
|
|
let key, value;
|
|
|
|
if ( pos !== -1 ) {
|
|
|
|
key = condition.slice(0, pos);
|
|
|
|
value = condition.slice(pos + 1);
|
|
|
|
} else {
|
|
|
|
key = 'url';
|
|
|
|
value = condition;
|
|
|
|
}
|
|
|
|
if ( value === '' ) {
|
|
|
|
value = '^';
|
|
|
|
} else if ( value.startsWith('/') && value.endsWith('/') ) {
|
|
|
|
value = value.slice(1, -1);
|
|
|
|
} else {
|
|
|
|
value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
needles.push({ key, re: new RegExp(value) });
|
|
|
|
}
|
2020-12-11 15:28:29 +01:00
|
|
|
const log = needles.length === 0 ? console.log.bind(console) : undefined;
|
2020-12-11 14:29:23 +01:00
|
|
|
self.fetch = new Proxy(self.fetch, {
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
let proceed = true;
|
|
|
|
try {
|
|
|
|
const url = args[0] instanceof self.Request
|
|
|
|
? args[0].url
|
|
|
|
: args[0];
|
|
|
|
const props = new Map([ [ 'url', url ] ]);
|
|
|
|
const init = args[1];
|
|
|
|
if ( init instanceof Object ) {
|
|
|
|
for ( const prop in init ) {
|
|
|
|
if ( init.hasOwnProperty(prop) === false ) { continue; }
|
|
|
|
props.set( prop, init[prop]);
|
|
|
|
}
|
|
|
|
}
|
2020-12-11 15:28:29 +01:00
|
|
|
if ( log !== undefined ) {
|
|
|
|
const out = Array.from(props)
|
|
|
|
.map(a => `${a[0]}:${a[1]}`)
|
|
|
|
.join(' ');
|
|
|
|
log(`uBO: fetch(${out})`);
|
|
|
|
}
|
|
|
|
proceed = needles.length === 0;
|
2020-12-11 14:29:23 +01:00
|
|
|
for ( const { key, re } of needles ) {
|
|
|
|
if (
|
|
|
|
props.has(key) === false ||
|
|
|
|
re.test(props.get(key)) === false
|
|
|
|
) {
|
|
|
|
proceed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(ex) {
|
|
|
|
}
|
|
|
|
return proceed
|
|
|
|
? Reflect.apply(target, thisArg, args)
|
|
|
|
: Promise.resolve(new Response());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
/// remove-attr.js
|
|
|
|
/// alias ra.js
|
|
|
|
(function() {
|
|
|
|
const token = '{{1}}';
|
|
|
|
if ( token === '' || token === '{{1}}' ) { return; }
|
|
|
|
const tokens = token.split(/\s*\|\s*/);
|
|
|
|
let selector = '{{2}}';
|
|
|
|
if ( selector === '' || selector === '{{2}}' ) {
|
|
|
|
selector = `[${tokens.join('],[')}]`;
|
|
|
|
}
|
2020-12-11 15:28:29 +01:00
|
|
|
const rmattr = function() {
|
2019-07-06 18:36:28 +02:00
|
|
|
try {
|
|
|
|
const nodes = document.querySelectorAll(selector);
|
|
|
|
for ( const node of nodes ) {
|
|
|
|
for ( const attr of tokens ) {
|
|
|
|
node.removeAttribute(attr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(ex) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if ( document.readyState === 'loading' ) {
|
2020-12-11 15:28:29 +01:00
|
|
|
window.addEventListener('DOMContentLoaded', rmattr, { once: true });
|
2019-07-06 18:36:28 +02:00
|
|
|
} else {
|
|
|
|
rmattr();
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2020-03-18 14:44:18 +01:00
|
|
|
/// remove-class.js
|
|
|
|
/// alias rc.js
|
|
|
|
(function() {
|
|
|
|
const token = '{{1}}';
|
|
|
|
if ( token === '' || token === '{{1}}' ) { return; }
|
|
|
|
const tokens = token.split(/\s*\|\s*/);
|
|
|
|
let selector = '{{2}}';
|
|
|
|
if ( selector === '' || selector === '{{2}}' ) {
|
|
|
|
selector = '.' + tokens.map(a => CSS.escape(a)).join(',.');
|
|
|
|
}
|
|
|
|
const rmclass = function() {
|
|
|
|
try {
|
|
|
|
const nodes = document.querySelectorAll(selector);
|
|
|
|
for ( const node of nodes ) {
|
|
|
|
node.classList.remove(...tokens);
|
|
|
|
}
|
|
|
|
} catch(ex) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if ( document.readyState === 'loading' ) {
|
|
|
|
window.addEventListener(
|
|
|
|
'DOMContentLoaded',
|
|
|
|
rmclass,
|
|
|
|
{ capture: true, once: true }
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
rmclass();
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2020-04-15 16:06:53 +02:00
|
|
|
/// no-requestAnimationFrame-if.js
|
|
|
|
/// alias norafif.js
|
|
|
|
(function() {
|
|
|
|
let needle = '{{1}}';
|
|
|
|
if ( needle === '{{1}}' ) { needle = ''; }
|
|
|
|
const needleNot = needle.charAt(0) === '!';
|
|
|
|
if ( needleNot ) { needle = needle.slice(1); }
|
|
|
|
if ( needle.startsWith('/') && needle.endsWith('/') ) {
|
|
|
|
needle = needle.slice(1, -1);
|
|
|
|
} else if ( needle !== '' ) {
|
|
|
|
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
const log = needleNot === false && needle === '' ? console.log : undefined;
|
|
|
|
const reNeedle = new RegExp(needle);
|
|
|
|
window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, {
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
const a = String(args[0]);
|
|
|
|
let defuse = false;
|
|
|
|
if ( log !== undefined ) {
|
|
|
|
log('uBO: requestAnimationFrame("%s")', a);
|
|
|
|
} else {
|
|
|
|
defuse = reNeedle.test(a) !== needleNot;
|
|
|
|
}
|
|
|
|
if ( defuse ) {
|
|
|
|
args[0] = function(){};
|
|
|
|
}
|
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-07-06 18:36:28 +02:00
|
|
|
/// set-constant.js
|
2019-07-29 16:16:36 +02:00
|
|
|
/// alias set.js
|
2019-07-06 18:36:28 +02:00
|
|
|
(function() {
|
2020-07-19 14:16:40 +02:00
|
|
|
const chain = '{{1}}';
|
2019-07-06 18:36:28 +02:00
|
|
|
let cValue = '{{2}}';
|
2020-07-19 14:16:40 +02:00
|
|
|
const thisScript = document.currentScript;
|
2019-07-06 18:36:28 +02:00
|
|
|
if ( cValue === 'undefined' ) {
|
|
|
|
cValue = undefined;
|
|
|
|
} else if ( cValue === 'false' ) {
|
|
|
|
cValue = false;
|
|
|
|
} else if ( cValue === 'true' ) {
|
|
|
|
cValue = true;
|
|
|
|
} else if ( cValue === 'null' ) {
|
|
|
|
cValue = null;
|
|
|
|
} else if ( cValue === 'noopFunc' ) {
|
|
|
|
cValue = function(){};
|
|
|
|
} else if ( cValue === 'trueFunc' ) {
|
|
|
|
cValue = function(){ return true; };
|
|
|
|
} else if ( cValue === 'falseFunc' ) {
|
|
|
|
cValue = function(){ return false; };
|
|
|
|
} else if ( /^\d+$/.test(cValue) ) {
|
|
|
|
cValue = parseFloat(cValue);
|
|
|
|
if ( isNaN(cValue) ) { return; }
|
|
|
|
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
|
|
|
} else if ( cValue === "''" ) {
|
|
|
|
cValue = '';
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let aborted = false;
|
|
|
|
const mustAbort = function(v) {
|
|
|
|
if ( aborted ) { return true; }
|
2020-01-21 16:57:55 +01:00
|
|
|
aborted =
|
|
|
|
(v !== undefined && v !== null) &&
|
|
|
|
(cValue !== undefined && cValue !== null) &&
|
|
|
|
(typeof v !== typeof cValue);
|
2019-07-06 18:36:28 +02:00
|
|
|
return aborted;
|
|
|
|
};
|
2020-07-19 14:16:40 +02:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
|
|
|
// Support multiple trappers for the same property.
|
2020-10-12 17:00:30 +02:00
|
|
|
const trapProp = function(owner, prop, configurable, handler) {
|
2020-07-19 14:16:40 +02:00
|
|
|
if ( handler.init(owner[prop]) === false ) { return; }
|
|
|
|
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
|
|
|
let prevGetter, prevSetter;
|
|
|
|
if ( odesc instanceof Object ) {
|
2020-10-20 11:23:10 +02:00
|
|
|
if ( odesc.configurable === false ) { return; }
|
2020-07-19 14:16:40 +02:00
|
|
|
if ( odesc.get instanceof Function ) {
|
|
|
|
prevGetter = odesc.get;
|
|
|
|
}
|
|
|
|
if ( odesc.set instanceof Function ) {
|
|
|
|
prevSetter = odesc.set;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Object.defineProperty(owner, prop, {
|
2020-10-12 17:00:30 +02:00
|
|
|
configurable,
|
2020-07-19 14:16:40 +02:00
|
|
|
get() {
|
|
|
|
if ( prevGetter !== undefined ) {
|
|
|
|
prevGetter();
|
|
|
|
}
|
2020-08-20 15:53:11 +02:00
|
|
|
return handler.getter(); // cValue
|
2020-07-19 14:16:40 +02:00
|
|
|
},
|
|
|
|
set(a) {
|
|
|
|
if ( prevSetter !== undefined ) {
|
|
|
|
prevSetter(a);
|
|
|
|
}
|
|
|
|
handler.setter(a);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const trapChain = function(owner, chain) {
|
2019-07-06 18:36:28 +02:00
|
|
|
const pos = chain.indexOf('.');
|
|
|
|
if ( pos === -1 ) {
|
2020-10-12 17:00:30 +02:00
|
|
|
trapProp(owner, chain, false, {
|
2020-07-19 14:16:40 +02:00
|
|
|
v: undefined,
|
|
|
|
init: function(v) {
|
|
|
|
if ( mustAbort(v) ) { return false; }
|
|
|
|
this.v = v;
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
getter: function() {
|
|
|
|
return document.currentScript === thisScript
|
|
|
|
? this.v
|
|
|
|
: cValue;
|
|
|
|
},
|
|
|
|
setter: function(a) {
|
|
|
|
if ( mustAbort(a) === false ) { return; }
|
|
|
|
cValue = a;
|
|
|
|
}
|
|
|
|
});
|
2019-07-06 18:36:28 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const prop = chain.slice(0, pos);
|
2020-07-19 14:16:40 +02:00
|
|
|
const v = owner[prop];
|
2019-07-06 18:36:28 +02:00
|
|
|
chain = chain.slice(pos + 1);
|
2020-07-19 14:16:40 +02:00
|
|
|
if ( v instanceof Object || typeof v === 'object' && v !== null ) {
|
|
|
|
trapChain(v, chain);
|
2019-07-06 18:36:28 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-10-12 17:00:30 +02:00
|
|
|
trapProp(owner, prop, true, {
|
2020-07-19 14:16:40 +02:00
|
|
|
v: undefined,
|
|
|
|
init: function(v) {
|
|
|
|
this.v = v;
|
|
|
|
return true;
|
2019-07-06 18:36:28 +02:00
|
|
|
},
|
2020-07-19 14:16:40 +02:00
|
|
|
getter: function() {
|
|
|
|
return this.v;
|
|
|
|
},
|
|
|
|
setter: function(a) {
|
|
|
|
this.v = a;
|
2019-07-06 18:36:28 +02:00
|
|
|
if ( a instanceof Object ) {
|
2020-07-19 14:16:40 +02:00
|
|
|
trapChain(a, chain);
|
2019-07-06 18:36:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2020-07-19 14:16:40 +02:00
|
|
|
trapChain(window, chain);
|
2019-07-06 18:36:28 +02:00
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-09-15 17:01:50 +02:00
|
|
|
/// no-setInterval-if.js
|
|
|
|
/// alias nosiif.js
|
2019-07-06 18:36:28 +02:00
|
|
|
(function() {
|
2019-08-22 15:32:46 +02:00
|
|
|
let needle = '{{1}}';
|
2019-08-24 19:54:31 +02:00
|
|
|
const needleNot = needle.charAt(0) === '!';
|
|
|
|
if ( needleNot ) { needle = needle.slice(1); }
|
|
|
|
let delay = '{{2}}';
|
2020-07-02 17:47:49 +02:00
|
|
|
if ( delay === '{{2}}' ) { delay = undefined; }
|
|
|
|
let delayNot = false;
|
|
|
|
if ( delay !== undefined ) {
|
|
|
|
delayNot = delay.charAt(0) === '!';
|
|
|
|
if ( delayNot ) { delay = delay.slice(1); }
|
|
|
|
delay = parseInt(delay, 10);
|
|
|
|
}
|
2019-08-22 15:32:46 +02:00
|
|
|
if ( needle === '' || needle === '{{1}}' ) {
|
2019-08-25 15:03:24 +02:00
|
|
|
needle = '';
|
2019-08-22 15:32:46 +02:00
|
|
|
} else if ( needle.startsWith('/') && needle.endsWith('/') ) {
|
|
|
|
needle = needle.slice(1,-1);
|
|
|
|
} else {
|
|
|
|
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
2020-07-02 17:47:49 +02:00
|
|
|
const log = needleNot === false && needle === '' && delay === undefined
|
2019-08-22 15:32:46 +02:00
|
|
|
? console.log
|
|
|
|
: undefined;
|
2019-08-25 15:03:24 +02:00
|
|
|
const reNeedle = new RegExp(needle);
|
2019-07-06 18:36:28 +02:00
|
|
|
window.setInterval = new Proxy(window.setInterval, {
|
|
|
|
apply: function(target, thisArg, args) {
|
2019-08-22 15:32:46 +02:00
|
|
|
const a = String(args[0]);
|
2019-07-06 18:36:28 +02:00
|
|
|
const b = args[1];
|
2019-08-22 15:32:46 +02:00
|
|
|
if ( log !== undefined ) {
|
|
|
|
log('uBO: setInterval("%s", %s)', a, b);
|
2019-09-15 17:01:50 +02:00
|
|
|
} else {
|
2020-07-02 17:47:49 +02:00
|
|
|
let defuse;
|
|
|
|
if ( needle !== '' ) {
|
|
|
|
defuse = reNeedle.test(a) !== needleNot;
|
|
|
|
}
|
|
|
|
if ( defuse !== false && delay !== undefined ) {
|
|
|
|
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
|
|
|
}
|
|
|
|
if ( defuse ) {
|
|
|
|
args[0] = function(){};
|
|
|
|
}
|
2019-08-22 15:32:46 +02:00
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2019-09-15 17:01:50 +02:00
|
|
|
/// no-setTimeout-if.js
|
|
|
|
/// alias nostif.js
|
2020-12-11 14:29:23 +01:00
|
|
|
/// alias setTimeout-defuser.js
|
2019-07-06 18:36:28 +02:00
|
|
|
(function() {
|
2019-08-22 15:32:46 +02:00
|
|
|
let needle = '{{1}}';
|
2019-08-24 19:54:31 +02:00
|
|
|
const needleNot = needle.charAt(0) === '!';
|
|
|
|
if ( needleNot ) { needle = needle.slice(1); }
|
|
|
|
let delay = '{{2}}';
|
2020-07-02 17:47:49 +02:00
|
|
|
if ( delay === '{{2}}' ) { delay = undefined; }
|
|
|
|
let delayNot = false;
|
|
|
|
if ( delay !== undefined ) {
|
|
|
|
delayNot = delay.charAt(0) === '!';
|
|
|
|
if ( delayNot ) { delay = delay.slice(1); }
|
|
|
|
delay = parseInt(delay, 10);
|
|
|
|
}
|
2019-08-22 15:32:46 +02:00
|
|
|
if ( needle === '' || needle === '{{1}}' ) {
|
2019-08-25 15:03:24 +02:00
|
|
|
needle = '';
|
2019-08-22 15:32:46 +02:00
|
|
|
} else if ( needle.startsWith('/') && needle.endsWith('/') ) {
|
|
|
|
needle = needle.slice(1,-1);
|
|
|
|
} else {
|
|
|
|
needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
2020-07-02 17:47:49 +02:00
|
|
|
const log = needleNot === false && needle === '' && delay === undefined
|
2019-08-22 15:32:46 +02:00
|
|
|
? console.log
|
|
|
|
: undefined;
|
2019-08-25 15:03:24 +02:00
|
|
|
const reNeedle = new RegExp(needle);
|
2019-07-06 18:36:28 +02:00
|
|
|
window.setTimeout = new Proxy(window.setTimeout, {
|
|
|
|
apply: function(target, thisArg, args) {
|
2019-08-22 15:32:46 +02:00
|
|
|
const a = String(args[0]);
|
2019-07-06 18:36:28 +02:00
|
|
|
const b = args[1];
|
2019-08-22 15:32:46 +02:00
|
|
|
if ( log !== undefined ) {
|
|
|
|
log('uBO: setTimeout("%s", %s)', a, b);
|
2019-09-15 17:01:50 +02:00
|
|
|
} else {
|
2020-07-02 17:47:49 +02:00
|
|
|
let defuse;
|
|
|
|
if ( needle !== '' ) {
|
|
|
|
defuse = reNeedle.test(a) !== needleNot;
|
|
|
|
}
|
|
|
|
if ( defuse !== false && delay !== undefined ) {
|
|
|
|
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
|
|
|
|
}
|
|
|
|
if ( defuse ) {
|
|
|
|
args[0] = function(){};
|
|
|
|
}
|
2019-08-22 15:32:46 +02:00
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
return target.apply(thisArg, args);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
/// webrtc-if.js
|
|
|
|
(function() {
|
|
|
|
let good = '{{1}}';
|
|
|
|
if ( good.startsWith('/') && good.endsWith('/') ) {
|
|
|
|
good = good.slice(1, -1);
|
|
|
|
} else {
|
|
|
|
good = good.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
}
|
|
|
|
let reGood;
|
|
|
|
try {
|
|
|
|
reGood = new RegExp(good);
|
|
|
|
} catch(ex) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const rtcName = window.RTCPeerConnection
|
|
|
|
? 'RTCPeerConnection'
|
|
|
|
: (window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : '');
|
|
|
|
if ( rtcName === '' ) { return; }
|
|
|
|
const log = console.log.bind(console);
|
|
|
|
const neuteredPeerConnections = new WeakSet();
|
|
|
|
const isGoodConfig = function(instance, config) {
|
|
|
|
if ( neuteredPeerConnections.has(instance) ) { return false; }
|
|
|
|
if ( config instanceof Object === false ) { return true; }
|
|
|
|
if ( Array.isArray(config.iceServers) === false ) { return true; }
|
|
|
|
for ( const server of config.iceServers ) {
|
|
|
|
const urls = typeof server.urls === 'string'
|
|
|
|
? [ server.urls ]
|
|
|
|
: server.urls;
|
|
|
|
if ( Array.isArray(urls) ) {
|
|
|
|
for ( const url of urls ) {
|
|
|
|
if ( reGood.test(url) ) { return true; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( typeof server.username === 'string' ) {
|
|
|
|
if ( reGood.test(server.username) ) { return true; }
|
|
|
|
}
|
|
|
|
if ( typeof server.credential === 'string' ) {
|
|
|
|
if ( reGood.test(server.credential) ) { return true; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
neuteredPeerConnections.add(instance);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
const peerConnectionCtor = window[rtcName];
|
|
|
|
const peerConnectionProto = peerConnectionCtor.prototype;
|
|
|
|
peerConnectionProto.createDataChannel =
|
|
|
|
new Proxy(peerConnectionProto.createDataChannel, {
|
|
|
|
apply: function(target, thisArg, args) {
|
|
|
|
if ( isGoodConfig(target, args[1]) === false ) {
|
2020-04-28 17:19:26 +02:00
|
|
|
log('uBO:', args[1]);
|
2019-07-11 15:45:53 +02:00
|
|
|
return Reflect.apply(target, thisArg, args.slice(0, 1));
|
2019-07-06 18:36:28 +02:00
|
|
|
}
|
2019-07-11 15:45:53 +02:00
|
|
|
return Reflect.apply(target, thisArg, args);
|
2019-07-06 18:36:28 +02:00
|
|
|
},
|
|
|
|
});
|
|
|
|
window[rtcName] =
|
|
|
|
new Proxy(peerConnectionCtor, {
|
|
|
|
construct: function(target, args) {
|
|
|
|
if ( isGoodConfig(target, args[0]) === false ) {
|
2020-04-28 17:19:26 +02:00
|
|
|
log('uBO:', args[0]);
|
2019-07-11 15:45:53 +02:00
|
|
|
return Reflect.construct(target);
|
2019-07-06 18:36:28 +02:00
|
|
|
}
|
2019-07-11 15:45:53 +02:00
|
|
|
return Reflect.construct(target, args);
|
2019-07-06 18:36:28 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/gorhill/uBlock/issues/1228
|
|
|
|
/// window.name-defuser
|
|
|
|
(function() {
|
|
|
|
if ( window === window.top ) {
|
|
|
|
window.name = '';
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// Experimental: Generic nuisance overlay buster.
|
|
|
|
// if this works well and proves to be useful, this may end up
|
|
|
|
// as a stock tool in uBO's popup panel.
|
|
|
|
/// overlay-buster.js
|
|
|
|
(function() {
|
|
|
|
if ( window !== window.top ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var tstart;
|
|
|
|
var ttl = 30000;
|
|
|
|
var delay = 0;
|
|
|
|
var delayStep = 50;
|
|
|
|
var buster = function() {
|
|
|
|
var docEl = document.documentElement,
|
|
|
|
bodyEl = document.body,
|
|
|
|
vw = Math.min(docEl.clientWidth, window.innerWidth),
|
|
|
|
vh = Math.min(docEl.clientHeight, window.innerHeight),
|
|
|
|
tol = Math.min(vw, vh) * 0.05,
|
|
|
|
el = document.elementFromPoint(vw/2, vh/2),
|
|
|
|
style, rect;
|
|
|
|
for (;;) {
|
|
|
|
if ( el === null || el.parentNode === null || el === bodyEl ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
style = window.getComputedStyle(el);
|
|
|
|
if ( parseInt(style.zIndex, 10) >= 1000 || style.position === 'fixed' ) {
|
|
|
|
rect = el.getBoundingClientRect();
|
|
|
|
if ( rect.left <= tol && rect.top <= tol && (vw - rect.right) <= tol && (vh - rect.bottom) < tol ) {
|
|
|
|
el.parentNode.removeChild(el);
|
|
|
|
tstart = Date.now();
|
|
|
|
el = document.elementFromPoint(vw/2, vh/2);
|
|
|
|
bodyEl.style.setProperty('overflow', 'auto', 'important');
|
|
|
|
docEl.style.setProperty('overflow', 'auto', 'important');
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
el = el.parentNode;
|
|
|
|
}
|
|
|
|
if ( (Date.now() - tstart) < ttl ) {
|
|
|
|
delay = Math.min(delay + delayStep, 1000);
|
|
|
|
setTimeout(buster, delay);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
var domReady = function(ev) {
|
|
|
|
if ( ev ) {
|
|
|
|
document.removeEventListener(ev.type, domReady);
|
|
|
|
}
|
|
|
|
tstart = Date.now();
|
|
|
|
setTimeout(buster, delay);
|
|
|
|
};
|
|
|
|
if ( document.readyState === 'loading' ) {
|
|
|
|
document.addEventListener('DOMContentLoaded', domReady);
|
|
|
|
} else {
|
|
|
|
domReady();
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/8
|
|
|
|
/// alert-buster.js
|
|
|
|
(function() {
|
|
|
|
window.alert = function(a) {
|
|
|
|
console.info(a);
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/58
|
2019-07-06 19:53:36 +02:00
|
|
|
/// gpt-defuser.js
|
2019-07-06 18:36:28 +02:00
|
|
|
(function() {
|
|
|
|
const noopfn = function() {
|
|
|
|
};
|
|
|
|
let props = '_resetGPT resetGPT resetAndLoadGPTRecovery _resetAndLoadGPTRecovery setupGPT setupGPTuo';
|
|
|
|
props = props.split(/\s+/);
|
|
|
|
while ( props.length ) {
|
|
|
|
var prop = props.pop();
|
|
|
|
if ( typeof window[prop] === 'function' ) {
|
2019-07-22 13:32:39 +02:00
|
|
|
window[prop] = noopfn;
|
2019-07-06 18:36:28 +02:00
|
|
|
} else {
|
|
|
|
Object.defineProperty(window, prop, {
|
|
|
|
get: function() { return noopfn; },
|
|
|
|
set: noopfn
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// Prevent web pages from using RTCPeerConnection(), and report attempts in console.
|
|
|
|
/// nowebrtc.js
|
|
|
|
(function() {
|
|
|
|
var rtcName = window.RTCPeerConnection ? 'RTCPeerConnection' : (
|
|
|
|
window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : ''
|
|
|
|
);
|
|
|
|
if ( rtcName === '' ) { return; }
|
|
|
|
var log = console.log.bind(console);
|
|
|
|
var pc = function(cfg) {
|
|
|
|
log('Document tried to create an RTCPeerConnection: %o', cfg);
|
|
|
|
};
|
|
|
|
const noop = function() {
|
|
|
|
};
|
|
|
|
pc.prototype = {
|
|
|
|
close: noop,
|
|
|
|
createDataChannel: noop,
|
|
|
|
createOffer: noop,
|
|
|
|
setRemoteDescription: noop,
|
|
|
|
toString: function() {
|
|
|
|
return '[object RTCPeerConnection]';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
var z = window[rtcName];
|
|
|
|
window[rtcName] = pc.bind(window);
|
|
|
|
if ( z.prototype ) {
|
|
|
|
z.prototype.createDataChannel = function() {
|
|
|
|
return {
|
|
|
|
close: function() {},
|
|
|
|
send: function() {}
|
|
|
|
};
|
|
|
|
}.bind(null);
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/88
|
|
|
|
/// golem.de.js
|
|
|
|
(function() {
|
|
|
|
const rael = window.addEventListener;
|
|
|
|
window.addEventListener = function(a, b) {
|
|
|
|
rael(...arguments);
|
|
|
|
let haystack;
|
|
|
|
try {
|
|
|
|
haystack = b.toString();
|
|
|
|
} catch(ex) {
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
typeof haystack === 'string' &&
|
|
|
|
/^\s*function\s*\(\)\s*\{\s*window\.clearTimeout\(r\)\s*\}\s*$/.test(haystack)
|
|
|
|
) {
|
|
|
|
b();
|
|
|
|
}
|
|
|
|
}.bind(window);
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://forums.lanik.us/viewtopic.php?f=64&t=32278
|
|
|
|
// https://www.reddit.com/r/chrome/comments/58eix6/ublock_origin_not_working_on_certain_sites/
|
|
|
|
/// upmanager-defuser.js
|
|
|
|
(function() {
|
|
|
|
var onerror = window.onerror;
|
|
|
|
window.onerror = function(msg, source, lineno, colno, error) {
|
|
|
|
if ( typeof msg === 'string' && msg.indexOf('upManager') !== -1 ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if ( onerror instanceof Function ) {
|
|
|
|
onerror.call(window, msg, source, lineno, colno, error);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Object.defineProperty(window, 'upManager', { value: function() {} });
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/110
|
|
|
|
/// smartadserver.com.js
|
|
|
|
(function() {
|
|
|
|
Object.defineProperties(window, {
|
|
|
|
SmartAdObject: { value: function(){} },
|
|
|
|
SmartAdServerAjax: { value: function(){} },
|
|
|
|
smartAd: { value: { LoadAds: function() {}, Register: function() {} } }
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/reek/anti-adblock-killer/issues/3774#issuecomment-348536138
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/883
|
|
|
|
/// adfly-defuser.js
|
|
|
|
(function() {
|
|
|
|
// Based on AdsBypasser
|
|
|
|
// License:
|
|
|
|
// https://github.com/adsbypasser/adsbypasser/blob/master/LICENSE
|
|
|
|
var isDigit = /^\d$/;
|
|
|
|
var handler = function(encodedURL) {
|
|
|
|
var var1 = "", var2 = "", i;
|
|
|
|
for (i = 0; i < encodedURL.length; i++) {
|
|
|
|
if (i % 2 === 0) {
|
|
|
|
var1 = var1 + encodedURL.charAt(i);
|
|
|
|
} else {
|
|
|
|
var2 = encodedURL.charAt(i) + var2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var data = (var1 + var2).split("");
|
|
|
|
for (i = 0; i < data.length; i++) {
|
|
|
|
if (isDigit.test(data[i])) {
|
|
|
|
for (var ii = i + 1; ii < data.length; ii++) {
|
|
|
|
if (isDigit.test(data[ii])) {
|
|
|
|
var temp = parseInt(data[i],10) ^ parseInt(data[ii],10);
|
|
|
|
if (temp < 10) {
|
|
|
|
data[i] = temp.toString();
|
|
|
|
}
|
|
|
|
i = ii;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data = data.join("");
|
|
|
|
var decodedURL = window.atob(data).slice(16, -16);
|
|
|
|
window.stop();
|
|
|
|
window.onbeforeunload = null;
|
|
|
|
window.location.href = decodedURL;
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
var val;
|
|
|
|
var flag = true;
|
|
|
|
window.Object.defineProperty(window, "ysmm", {
|
|
|
|
configurable: false,
|
|
|
|
set: function(value) {
|
|
|
|
if (flag) {
|
|
|
|
flag = false;
|
|
|
|
try {
|
|
|
|
if (typeof value === "string") {
|
|
|
|
handler(value);
|
|
|
|
}
|
|
|
|
} catch (err) { }
|
|
|
|
}
|
|
|
|
val = value;
|
|
|
|
},
|
|
|
|
get: function() {
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
window.console.error("Failed to set up Adfly bypasser!");
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/913
|
|
|
|
/// disable-newtab-links.js
|
|
|
|
(function() {
|
|
|
|
document.addEventListener('click', function(ev) {
|
|
|
|
var target = ev.target;
|
|
|
|
while ( target !== null ) {
|
|
|
|
if ( target.localName === 'a' && target.hasAttribute('target') ) {
|
|
|
|
ev.stopPropagation();
|
|
|
|
ev.preventDefault();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
target = target.parentNode;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
/// damoh-defuser.js
|
|
|
|
(function() {
|
|
|
|
var handled = new WeakSet();
|
|
|
|
var asyncTimer;
|
|
|
|
var cleanVideo = function() {
|
|
|
|
asyncTimer = undefined;
|
|
|
|
var v = document.querySelector('video');
|
|
|
|
if ( v === null ) { return; }
|
|
|
|
if ( handled.has(v) ) { return; }
|
|
|
|
handled.add(v);
|
|
|
|
v.pause();
|
|
|
|
v.controls = true;
|
|
|
|
var el = v.querySelector('meta[itemprop="contentURL"][content]');
|
|
|
|
if ( el === null ) { return; }
|
|
|
|
v.src = el.getAttribute('content');
|
|
|
|
el = v.querySelector('meta[itemprop="thumbnailUrl"][content]');
|
|
|
|
if ( el !== null ) { v.poster = el.getAttribute('content'); }
|
|
|
|
};
|
|
|
|
var cleanVideoAsync = function() {
|
|
|
|
if ( asyncTimer !== undefined ) { return; }
|
|
|
|
asyncTimer = window.requestAnimationFrame(cleanVideo);
|
|
|
|
};
|
|
|
|
var observer = new MutationObserver(cleanVideoAsync);
|
|
|
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
2020-10-02 18:20:30 +02:00
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/5184
|
2019-07-06 18:36:28 +02:00
|
|
|
/// twitch-videoad.js
|
|
|
|
(function() {
|
|
|
|
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
|
|
|
|
var realFetch = window.fetch;
|
|
|
|
window.fetch = function(input) {
|
|
|
|
if ( arguments.length >= 2 && typeof input === 'string' && input.includes('/access_token') ) {
|
|
|
|
var url = new URL(arguments[0]);
|
2020-10-02 18:20:30 +02:00
|
|
|
url.searchParams.delete('platform');
|
2019-07-06 18:36:28 +02:00
|
|
|
arguments[0] = url.href;
|
|
|
|
}
|
|
|
|
return realFetch.apply(this, arguments);
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/uBlockOrigin/uAssets/issues/2912
|
|
|
|
/// fingerprint2.js
|
|
|
|
(function() {
|
2020-04-18 15:45:07 +02:00
|
|
|
let browserId = '';
|
|
|
|
for ( let i = 0; i < 8; i++ ) {
|
|
|
|
browserId += (Math.random() * 0x10000 + 0x1000 | 0).toString(16).slice(-4);
|
|
|
|
}
|
|
|
|
const fp2 = function(){};
|
|
|
|
fp2.get = function(opts, cb) {
|
|
|
|
if ( !cb ) { cb = opts; }
|
|
|
|
setTimeout(( ) => { cb(browserId, []); }, 1);
|
|
|
|
};
|
2019-07-06 18:36:28 +02:00
|
|
|
fp2.prototype = {
|
2020-04-18 15:45:07 +02:00
|
|
|
get: fp2.get
|
2019-07-06 18:36:28 +02:00
|
|
|
};
|
|
|
|
window.Fingerprint2 = fp2;
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
// https://github.com/NanoAdblocker/NanoFilters/issues/149
|
|
|
|
/// cookie-remover.js
|
|
|
|
(function() {
|
2020-06-24 23:18:14 +02:00
|
|
|
const needle = '{{1}}';
|
|
|
|
let reName = /./;
|
2019-07-06 18:36:28 +02:00
|
|
|
if ( /^\/.+\/$/.test(needle) ) {
|
|
|
|
reName = new RegExp(needle.slice(1,-1));
|
|
|
|
} else if ( needle !== '' && needle !== '{{1}}' ) {
|
|
|
|
reName = new RegExp(needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
|
|
}
|
2020-06-24 23:18:14 +02:00
|
|
|
const removeCookie = function() {
|
2019-07-06 18:36:28 +02:00
|
|
|
document.cookie.split(';').forEach(cookieStr => {
|
|
|
|
let pos = cookieStr.indexOf('=');
|
|
|
|
if ( pos === -1 ) { return; }
|
|
|
|
let cookieName = cookieStr.slice(0, pos).trim();
|
|
|
|
if ( !reName.test(cookieName) ) { return; }
|
|
|
|
let part1 = cookieName + '=';
|
|
|
|
let part2a = '; domain=' + document.location.hostname;
|
|
|
|
let part2b = '; domain=.' + document.location.hostname;
|
2020-06-24 23:18:14 +02:00
|
|
|
let part2c, part2d;
|
2019-07-06 18:36:28 +02:00
|
|
|
let domain = document.domain;
|
2020-06-24 23:18:14 +02:00
|
|
|
if ( domain ) {
|
|
|
|
if ( domain !== document.location.hostname ) {
|
|
|
|
part2c = '; domain=.' + domain;
|
|
|
|
}
|
|
|
|
if ( domain.startsWith('www.') ) {
|
|
|
|
part2d = '; domain=' + domain.replace('www', '');
|
|
|
|
}
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
let part3 = '; path=/';
|
|
|
|
let part4 = '; Max-Age=-1000; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
|
|
|
document.cookie = part1 + part4;
|
|
|
|
document.cookie = part1 + part2a + part4;
|
|
|
|
document.cookie = part1 + part2b + part4;
|
|
|
|
document.cookie = part1 + part3 + part4;
|
|
|
|
document.cookie = part1 + part2a + part3 + part4;
|
|
|
|
document.cookie = part1 + part2b + part3 + part4;
|
|
|
|
if ( part2c !== undefined ) {
|
|
|
|
document.cookie = part1 + part2c + part3 + part4;
|
|
|
|
}
|
2020-06-24 23:18:14 +02:00
|
|
|
if ( part2d !== undefined ) {
|
|
|
|
document.cookie = part1 + part2d + part3 + part4;
|
|
|
|
}
|
2019-07-06 18:36:28 +02:00
|
|
|
});
|
|
|
|
};
|
|
|
|
removeCookie();
|
|
|
|
window.addEventListener('beforeunload', removeCookie);
|
|
|
|
})();
|
2019-07-08 14:56:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// These lines below are skipped by the resource parser.
|
|
|
|
// <<<< end of private namespace
|
|
|
|
})();
|