mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-20 01:12:38 +01:00
Bring scriptlets up to date
This commit is contained in:
parent
1d63023263
commit
eb2d6d1374
@ -56,15 +56,70 @@ const regexpFromArg = arg => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Dependencies
|
||||
|
||||
const scriptletGlobals = new Map();
|
||||
|
||||
function safeSelf() {
|
||||
if ( scriptletGlobals.has('safeSelf') ) {
|
||||
return scriptletGlobals.get('safeSelf');
|
||||
}
|
||||
const safe = {
|
||||
'RegExp': self.RegExp,
|
||||
'RegExp_test': self.RegExp.prototype.test,
|
||||
'RegExp_exec': self.RegExp.prototype.exec,
|
||||
'addEventListener': self.EventTarget.prototype.addEventListener,
|
||||
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
||||
};
|
||||
scriptletGlobals.set('safeSelf', safe);
|
||||
return safe;
|
||||
}
|
||||
|
||||
function runAt(fn, when) {
|
||||
const intFromReadyState = state => {
|
||||
const targets = {
|
||||
'loading': 1,
|
||||
'interactive': 2, 'end': 2, '2': 2,
|
||||
'complete': 3, 'idle': 3, '3': 3,
|
||||
};
|
||||
const tokens = Array.isArray(state) ? state : [ state ];
|
||||
for ( const token of tokens ) {
|
||||
const prop = `${token}`;
|
||||
if ( targets.hasOwnProperty(prop) === false ) { continue; }
|
||||
return targets[prop];
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const runAt = intFromReadyState(when);
|
||||
if ( intFromReadyState(document.readyState) >= runAt ) {
|
||||
fn(); return;
|
||||
}
|
||||
const onStateChange = ( ) => {
|
||||
if ( intFromReadyState(document.readyState) < runAt ) { return; }
|
||||
fn();
|
||||
safe.removeEventListener.apply(document, args);
|
||||
};
|
||||
const safe = safeSelf();
|
||||
const args = [ 'readystatechange', onStateChange, { capture: true } ];
|
||||
safe.addEventListener.apply(document, args);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const scriptlet = (
|
||||
needle1 = '',
|
||||
needle2 = ''
|
||||
arg1 = '',
|
||||
arg2 = ''
|
||||
) => {
|
||||
const reNeedle1 = regexpFromArg(needle1);
|
||||
const reNeedle2 = regexpFromArg(needle2);
|
||||
self.EventTarget.prototype.addEventListener = new Proxy(
|
||||
self.EventTarget.prototype.addEventListener,
|
||||
{
|
||||
const details = typeof arg1 !== 'object'
|
||||
? { type: arg1, pattern: arg2 }
|
||||
: arg1;
|
||||
const { type = '', pattern = '' } = details;
|
||||
if ( typeof type !== 'string' ) { return; }
|
||||
if ( typeof pattern !== 'string' ) { return; }
|
||||
const reType = regexpFromArg(type);
|
||||
const rePattern = regexpFromArg(pattern);
|
||||
const trapEddEventListeners = ( ) => {
|
||||
const eventListenerHandler = {
|
||||
apply: function(target, thisArg, args) {
|
||||
let type, handler;
|
||||
try {
|
||||
@ -73,14 +128,21 @@ const scriptlet = (
|
||||
} catch(ex) {
|
||||
}
|
||||
if (
|
||||
reNeedle1.test(type) === false ||
|
||||
reNeedle2.test(handler) === false
|
||||
reType.test(type) === false ||
|
||||
rePattern.test(handler) === false
|
||||
) {
|
||||
return target.apply(thisArg, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
self.EventTarget.prototype.addEventListener = new Proxy(
|
||||
self.EventTarget.prototype.addEventListener,
|
||||
eventListenerHandler
|
||||
);
|
||||
};
|
||||
runAt(( ) => {
|
||||
trapEddEventListeners();
|
||||
}, details.runAt);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -57,15 +57,70 @@ const regexpFromArg = arg => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Dependencies
|
||||
|
||||
const scriptletGlobals = new Map();
|
||||
|
||||
function safeSelf() {
|
||||
if ( scriptletGlobals.has('safeSelf') ) {
|
||||
return scriptletGlobals.get('safeSelf');
|
||||
}
|
||||
const safe = {
|
||||
'RegExp': self.RegExp,
|
||||
'RegExp_test': self.RegExp.prototype.test,
|
||||
'RegExp_exec': self.RegExp.prototype.exec,
|
||||
'addEventListener': self.EventTarget.prototype.addEventListener,
|
||||
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
||||
};
|
||||
scriptletGlobals.set('safeSelf', safe);
|
||||
return safe;
|
||||
}
|
||||
|
||||
function runAt(fn, when) {
|
||||
const intFromReadyState = state => {
|
||||
const targets = {
|
||||
'loading': 1,
|
||||
'interactive': 2, 'end': 2, '2': 2,
|
||||
'complete': 3, 'idle': 3, '3': 3,
|
||||
};
|
||||
const tokens = Array.isArray(state) ? state : [ state ];
|
||||
for ( const token of tokens ) {
|
||||
const prop = `${token}`;
|
||||
if ( targets.hasOwnProperty(prop) === false ) { continue; }
|
||||
return targets[prop];
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const runAt = intFromReadyState(when);
|
||||
if ( intFromReadyState(document.readyState) >= runAt ) {
|
||||
fn(); return;
|
||||
}
|
||||
const onStateChange = ( ) => {
|
||||
if ( intFromReadyState(document.readyState) < runAt ) { return; }
|
||||
fn();
|
||||
safe.removeEventListener.apply(document, args);
|
||||
};
|
||||
const safe = safeSelf();
|
||||
const args = [ 'readystatechange', onStateChange, { capture: true } ];
|
||||
safe.addEventListener.apply(document, args);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const scriptlet = (
|
||||
needle1 = '',
|
||||
needle2 = ''
|
||||
arg1 = '',
|
||||
arg2 = ''
|
||||
) => {
|
||||
const reNeedle1 = regexpFromArg(needle1);
|
||||
const reNeedle2 = regexpFromArg(needle2);
|
||||
self.EventTarget.prototype.addEventListener = new Proxy(
|
||||
self.EventTarget.prototype.addEventListener,
|
||||
{
|
||||
const details = typeof arg1 !== 'object'
|
||||
? { type: arg1, pattern: arg2 }
|
||||
: arg1;
|
||||
const { type = '', pattern = '' } = details;
|
||||
if ( typeof type !== 'string' ) { return; }
|
||||
if ( typeof pattern !== 'string' ) { return; }
|
||||
const reType = regexpFromArg(type);
|
||||
const rePattern = regexpFromArg(pattern);
|
||||
const trapEddEventListeners = ( ) => {
|
||||
const eventListenerHandler = {
|
||||
apply: function(target, thisArg, args) {
|
||||
let type, handler;
|
||||
try {
|
||||
@ -74,14 +129,21 @@ const scriptlet = (
|
||||
} catch(ex) {
|
||||
}
|
||||
if (
|
||||
reNeedle1.test(type) === false ||
|
||||
reNeedle2.test(handler) === false
|
||||
reType.test(type) === false ||
|
||||
rePattern.test(handler) === false
|
||||
) {
|
||||
return target.apply(thisArg, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
self.EventTarget.prototype.addEventListener = new Proxy(
|
||||
self.EventTarget.prototype.addEventListener,
|
||||
eventListenerHandler
|
||||
);
|
||||
};
|
||||
runAt(( ) => {
|
||||
trapEddEventListeners();
|
||||
}, details.runAt);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -83,7 +83,13 @@ const scriptlet = (
|
||||
args[0] = function(){};
|
||||
}
|
||||
return target.apply(thisArg, args);
|
||||
}
|
||||
},
|
||||
get(target, prop, receiver) {
|
||||
if ( prop === 'toString' ) {
|
||||
return target.toString.bind(target);
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -83,7 +83,13 @@ const scriptlet = (
|
||||
args[0] = function(){};
|
||||
}
|
||||
return target.apply(thisArg, args);
|
||||
}
|
||||
},
|
||||
get(target, prop, receiver) {
|
||||
if ( prop === 'toString' ) {
|
||||
return target.toString.bind(target);
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -83,7 +83,13 @@ const scriptlet = (
|
||||
args[0] = function(){};
|
||||
}
|
||||
return target.apply(thisArg, args);
|
||||
}
|
||||
},
|
||||
get(target, prop, receiver) {
|
||||
if ( prop === 'toString' ) {
|
||||
return target.toString.bind(target);
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -47,159 +47,240 @@ const entitiesMap = new Map(self.$entitiesMap$);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const scriptlet = (
|
||||
chain = '',
|
||||
cValue = ''
|
||||
) => {
|
||||
if ( chain === '' ) { return; }
|
||||
const trappedProp = (( ) => {
|
||||
const pos = chain.lastIndexOf('.');
|
||||
if ( pos === -1 ) { return chain; }
|
||||
return chain.slice(pos+1);
|
||||
})();
|
||||
if ( trappedProp === '' ) { return; }
|
||||
const objectDefineProperty = Object.defineProperty.bind(Object);
|
||||
const cloakFunc = fn => {
|
||||
objectDefineProperty(fn, 'name', { value: trappedProp });
|
||||
const proxy = new Proxy(fn, {
|
||||
defineProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
get(target, prop) {
|
||||
if ( prop === 'toString' ) {
|
||||
return function() {
|
||||
return `function ${trappedProp}() { [native code] }`;
|
||||
}.bind(null);
|
||||
}
|
||||
return Reflect.get(...arguments);
|
||||
},
|
||||
});
|
||||
return proxy;
|
||||
};
|
||||
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 === "''" ) {
|
||||
cValue = '';
|
||||
} else if ( cValue === '[]' ) {
|
||||
cValue = [];
|
||||
} else if ( cValue === '{}' ) {
|
||||
cValue = {};
|
||||
} else if ( cValue === 'noopFunc' ) {
|
||||
cValue = cloakFunc(function(){});
|
||||
} else if ( cValue === 'trueFunc' ) {
|
||||
cValue = cloakFunc(function(){ return true; });
|
||||
} else if ( cValue === 'falseFunc' ) {
|
||||
cValue = cloakFunc(function(){ return false; });
|
||||
} else if ( /^\d+$/.test(cValue) ) {
|
||||
cValue = parseFloat(cValue);
|
||||
if ( isNaN(cValue) ) { return; }
|
||||
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
||||
} else {
|
||||
return;
|
||||
// Dependencies
|
||||
|
||||
const scriptletGlobals = new Map();
|
||||
|
||||
function safeSelf() {
|
||||
if ( scriptletGlobals.has('safeSelf') ) {
|
||||
return scriptletGlobals.get('safeSelf');
|
||||
}
|
||||
let aborted = false;
|
||||
const mustAbort = function(v) {
|
||||
if ( aborted ) { return true; }
|
||||
aborted =
|
||||
(v !== undefined && v !== null) &&
|
||||
(cValue !== undefined && cValue !== null) &&
|
||||
(typeof v !== typeof cValue);
|
||||
return aborted;
|
||||
const safe = {
|
||||
'RegExp': self.RegExp,
|
||||
'RegExp_test': self.RegExp.prototype.test,
|
||||
'RegExp_exec': self.RegExp.prototype.exec,
|
||||
'addEventListener': self.EventTarget.prototype.addEventListener,
|
||||
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
||||
};
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
||||
// Support multiple trappers for the same property.
|
||||
const trapProp = function(owner, prop, configurable, handler) {
|
||||
if ( handler.init(owner[prop]) === false ) { return; }
|
||||
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
||||
let prevGetter, prevSetter;
|
||||
if ( odesc instanceof Object ) {
|
||||
owner[prop] = cValue;
|
||||
if ( odesc.get instanceof Function ) {
|
||||
prevGetter = odesc.get;
|
||||
}
|
||||
if ( odesc.set instanceof Function ) {
|
||||
prevSetter = odesc.set;
|
||||
}
|
||||
scriptletGlobals.set('safeSelf', safe);
|
||||
return safe;
|
||||
}
|
||||
|
||||
function runAt(fn, when) {
|
||||
const intFromReadyState = state => {
|
||||
const targets = {
|
||||
'loading': 1,
|
||||
'interactive': 2, 'end': 2, '2': 2,
|
||||
'complete': 3, 'idle': 3, '3': 3,
|
||||
};
|
||||
const tokens = Array.isArray(state) ? state : [ state ];
|
||||
for ( const token of tokens ) {
|
||||
const prop = `${token}`;
|
||||
if ( targets.hasOwnProperty(prop) === false ) { continue; }
|
||||
return targets[prop];
|
||||
}
|
||||
try {
|
||||
objectDefineProperty(owner, prop, {
|
||||
configurable,
|
||||
get() {
|
||||
if ( prevGetter !== undefined ) {
|
||||
prevGetter();
|
||||
return 0;
|
||||
};
|
||||
const runAt = intFromReadyState(when);
|
||||
if ( intFromReadyState(document.readyState) >= runAt ) {
|
||||
fn(); return;
|
||||
}
|
||||
const onStateChange = ( ) => {
|
||||
if ( intFromReadyState(document.readyState) < runAt ) { return; }
|
||||
fn();
|
||||
safe.removeEventListener.apply(document, args);
|
||||
};
|
||||
const safe = safeSelf();
|
||||
const args = [ 'readystatechange', onStateChange, { capture: true } ];
|
||||
safe.addEventListener.apply(document, args);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const scriptlet = (
|
||||
arg1 = '',
|
||||
arg2 = '',
|
||||
arg3 = ''
|
||||
) => {
|
||||
const details = typeof arg1 !== 'object'
|
||||
? { prop: arg1, value: arg2 }
|
||||
: arg1;
|
||||
if ( arg3 !== '' ) {
|
||||
if ( /^\d$/.test(arg3) ) {
|
||||
details.options = [ arg3 ];
|
||||
} else {
|
||||
details.options = Array.from(arguments).slice(2);
|
||||
}
|
||||
}
|
||||
const { prop: chain = '', value: cValue = '' } = details;
|
||||
if ( typeof chain !== 'string' ) { return; }
|
||||
if ( chain === '' ) { return; }
|
||||
const options = details.options || [];
|
||||
function setConstant(chain, cValue) {
|
||||
const trappedProp = (( ) => {
|
||||
const pos = chain.lastIndexOf('.');
|
||||
if ( pos === -1 ) { return chain; }
|
||||
return chain.slice(pos+1);
|
||||
})();
|
||||
if ( trappedProp === '' ) { return; }
|
||||
const thisScript = document.currentScript;
|
||||
const objectDefineProperty = Object.defineProperty.bind(Object);
|
||||
const cloakFunc = fn => {
|
||||
objectDefineProperty(fn, 'name', { value: trappedProp });
|
||||
const proxy = new Proxy(fn, {
|
||||
defineProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
return handler.getter(); // cValue
|
||||
return true;
|
||||
},
|
||||
set(a) {
|
||||
if ( prevSetter !== undefined ) {
|
||||
prevSetter(a);
|
||||
deleteProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
handler.setter(a);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
get(target, prop) {
|
||||
if ( prop === 'toString' ) {
|
||||
return function() {
|
||||
return `function ${trappedProp}() { [native code] }`;
|
||||
}.bind(null);
|
||||
}
|
||||
return Reflect.get(...arguments);
|
||||
},
|
||||
});
|
||||
} catch(ex) {
|
||||
return proxy;
|
||||
};
|
||||
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 === "''" ) {
|
||||
cValue = '';
|
||||
} else if ( cValue === '[]' ) {
|
||||
cValue = [];
|
||||
} else if ( cValue === '{}' ) {
|
||||
cValue = {};
|
||||
} else if ( cValue === 'noopFunc' ) {
|
||||
cValue = cloakFunc(function(){});
|
||||
} else if ( cValue === 'trueFunc' ) {
|
||||
cValue = cloakFunc(function(){ return true; });
|
||||
} else if ( cValue === 'falseFunc' ) {
|
||||
cValue = cloakFunc(function(){ return false; });
|
||||
} else if ( /^-?\d+$/.test(cValue) ) {
|
||||
cValue = parseInt(cValue);
|
||||
if ( isNaN(cValue) ) { return; }
|
||||
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
const trapChain = function(owner, chain) {
|
||||
const pos = chain.indexOf('.');
|
||||
if ( pos === -1 ) {
|
||||
trapProp(owner, chain, false, {
|
||||
if ( options.includes('asFunction') ) {
|
||||
cValue = ( ) => cValue;
|
||||
} else if ( options.includes('asCallback') ) {
|
||||
cValue = ( ) => (( ) => cValue);
|
||||
} else if ( options.includes('asResolved') ) {
|
||||
cValue = Promise.resolve(cValue);
|
||||
} else if ( options.includes('asRejected') ) {
|
||||
cValue = Promise.reject(cValue);
|
||||
}
|
||||
let aborted = false;
|
||||
const mustAbort = function(v) {
|
||||
if ( aborted ) { return true; }
|
||||
aborted =
|
||||
(v !== undefined && v !== null) &&
|
||||
(cValue !== undefined && cValue !== null) &&
|
||||
(typeof v !== typeof cValue);
|
||||
return aborted;
|
||||
};
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
||||
// Support multiple trappers for the same property.
|
||||
const trapProp = function(owner, prop, configurable, handler) {
|
||||
if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; }
|
||||
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
||||
let prevGetter, prevSetter;
|
||||
if ( odesc instanceof Object ) {
|
||||
owner[prop] = cValue;
|
||||
if ( odesc.get instanceof Function ) {
|
||||
prevGetter = odesc.get;
|
||||
}
|
||||
if ( odesc.set instanceof Function ) {
|
||||
prevSetter = odesc.set;
|
||||
}
|
||||
}
|
||||
try {
|
||||
objectDefineProperty(owner, prop, {
|
||||
configurable,
|
||||
get() {
|
||||
if ( prevGetter !== undefined ) {
|
||||
prevGetter();
|
||||
}
|
||||
return handler.getter(); // cValue
|
||||
},
|
||||
set(a) {
|
||||
if ( prevSetter !== undefined ) {
|
||||
prevSetter(a);
|
||||
}
|
||||
handler.setter(a);
|
||||
}
|
||||
});
|
||||
} catch(ex) {
|
||||
}
|
||||
};
|
||||
const trapChain = function(owner, chain) {
|
||||
const pos = chain.indexOf('.');
|
||||
if ( pos === -1 ) {
|
||||
trapProp(owner, chain, false, {
|
||||
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;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const prop = chain.slice(0, pos);
|
||||
const v = owner[prop];
|
||||
chain = chain.slice(pos + 1);
|
||||
if ( v instanceof Object || typeof v === 'object' && v !== null ) {
|
||||
trapChain(v, chain);
|
||||
return;
|
||||
}
|
||||
trapProp(owner, prop, true, {
|
||||
v: undefined,
|
||||
init: function(v) {
|
||||
if ( mustAbort(v) ) { return false; }
|
||||
this.v = v;
|
||||
return true;
|
||||
},
|
||||
getter: function() {
|
||||
return cValue;
|
||||
return this.v;
|
||||
},
|
||||
setter: function(a) {
|
||||
if ( mustAbort(a) === false ) { return; }
|
||||
cValue = a;
|
||||
this.v = a;
|
||||
if ( a instanceof Object ) {
|
||||
trapChain(a, chain);
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const prop = chain.slice(0, pos);
|
||||
const v = owner[prop];
|
||||
chain = chain.slice(pos + 1);
|
||||
if ( v instanceof Object || typeof v === 'object' && v !== null ) {
|
||||
trapChain(v, chain);
|
||||
return;
|
||||
}
|
||||
trapProp(owner, prop, true, {
|
||||
v: undefined,
|
||||
init: function(v) {
|
||||
this.v = v;
|
||||
return true;
|
||||
},
|
||||
getter: function() {
|
||||
return this.v;
|
||||
},
|
||||
setter: function(a) {
|
||||
this.v = a;
|
||||
if ( a instanceof Object ) {
|
||||
trapChain(a, chain);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
trapChain(window, chain);
|
||||
};
|
||||
trapChain(window, chain);
|
||||
}
|
||||
runAt(( ) => {
|
||||
setConstant(chain, cValue);
|
||||
}, options);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -47,159 +47,240 @@ const hostnamesMap = new Map(self.$hostnamesMap$);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const scriptlet = (
|
||||
chain = '',
|
||||
cValue = ''
|
||||
) => {
|
||||
if ( chain === '' ) { return; }
|
||||
const trappedProp = (( ) => {
|
||||
const pos = chain.lastIndexOf('.');
|
||||
if ( pos === -1 ) { return chain; }
|
||||
return chain.slice(pos+1);
|
||||
})();
|
||||
if ( trappedProp === '' ) { return; }
|
||||
const objectDefineProperty = Object.defineProperty.bind(Object);
|
||||
const cloakFunc = fn => {
|
||||
objectDefineProperty(fn, 'name', { value: trappedProp });
|
||||
const proxy = new Proxy(fn, {
|
||||
defineProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
get(target, prop) {
|
||||
if ( prop === 'toString' ) {
|
||||
return function() {
|
||||
return `function ${trappedProp}() { [native code] }`;
|
||||
}.bind(null);
|
||||
}
|
||||
return Reflect.get(...arguments);
|
||||
},
|
||||
});
|
||||
return proxy;
|
||||
};
|
||||
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 === "''" ) {
|
||||
cValue = '';
|
||||
} else if ( cValue === '[]' ) {
|
||||
cValue = [];
|
||||
} else if ( cValue === '{}' ) {
|
||||
cValue = {};
|
||||
} else if ( cValue === 'noopFunc' ) {
|
||||
cValue = cloakFunc(function(){});
|
||||
} else if ( cValue === 'trueFunc' ) {
|
||||
cValue = cloakFunc(function(){ return true; });
|
||||
} else if ( cValue === 'falseFunc' ) {
|
||||
cValue = cloakFunc(function(){ return false; });
|
||||
} else if ( /^\d+$/.test(cValue) ) {
|
||||
cValue = parseFloat(cValue);
|
||||
if ( isNaN(cValue) ) { return; }
|
||||
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
||||
} else {
|
||||
return;
|
||||
// Dependencies
|
||||
|
||||
const scriptletGlobals = new Map();
|
||||
|
||||
function safeSelf() {
|
||||
if ( scriptletGlobals.has('safeSelf') ) {
|
||||
return scriptletGlobals.get('safeSelf');
|
||||
}
|
||||
let aborted = false;
|
||||
const mustAbort = function(v) {
|
||||
if ( aborted ) { return true; }
|
||||
aborted =
|
||||
(v !== undefined && v !== null) &&
|
||||
(cValue !== undefined && cValue !== null) &&
|
||||
(typeof v !== typeof cValue);
|
||||
return aborted;
|
||||
const safe = {
|
||||
'RegExp': self.RegExp,
|
||||
'RegExp_test': self.RegExp.prototype.test,
|
||||
'RegExp_exec': self.RegExp.prototype.exec,
|
||||
'addEventListener': self.EventTarget.prototype.addEventListener,
|
||||
'removeEventListener': self.EventTarget.prototype.removeEventListener,
|
||||
};
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
||||
// Support multiple trappers for the same property.
|
||||
const trapProp = function(owner, prop, configurable, handler) {
|
||||
if ( handler.init(owner[prop]) === false ) { return; }
|
||||
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
||||
let prevGetter, prevSetter;
|
||||
if ( odesc instanceof Object ) {
|
||||
owner[prop] = cValue;
|
||||
if ( odesc.get instanceof Function ) {
|
||||
prevGetter = odesc.get;
|
||||
}
|
||||
if ( odesc.set instanceof Function ) {
|
||||
prevSetter = odesc.set;
|
||||
}
|
||||
scriptletGlobals.set('safeSelf', safe);
|
||||
return safe;
|
||||
}
|
||||
|
||||
function runAt(fn, when) {
|
||||
const intFromReadyState = state => {
|
||||
const targets = {
|
||||
'loading': 1,
|
||||
'interactive': 2, 'end': 2, '2': 2,
|
||||
'complete': 3, 'idle': 3, '3': 3,
|
||||
};
|
||||
const tokens = Array.isArray(state) ? state : [ state ];
|
||||
for ( const token of tokens ) {
|
||||
const prop = `${token}`;
|
||||
if ( targets.hasOwnProperty(prop) === false ) { continue; }
|
||||
return targets[prop];
|
||||
}
|
||||
try {
|
||||
objectDefineProperty(owner, prop, {
|
||||
configurable,
|
||||
get() {
|
||||
if ( prevGetter !== undefined ) {
|
||||
prevGetter();
|
||||
return 0;
|
||||
};
|
||||
const runAt = intFromReadyState(when);
|
||||
if ( intFromReadyState(document.readyState) >= runAt ) {
|
||||
fn(); return;
|
||||
}
|
||||
const onStateChange = ( ) => {
|
||||
if ( intFromReadyState(document.readyState) < runAt ) { return; }
|
||||
fn();
|
||||
safe.removeEventListener.apply(document, args);
|
||||
};
|
||||
const safe = safeSelf();
|
||||
const args = [ 'readystatechange', onStateChange, { capture: true } ];
|
||||
safe.addEventListener.apply(document, args);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const scriptlet = (
|
||||
arg1 = '',
|
||||
arg2 = '',
|
||||
arg3 = ''
|
||||
) => {
|
||||
const details = typeof arg1 !== 'object'
|
||||
? { prop: arg1, value: arg2 }
|
||||
: arg1;
|
||||
if ( arg3 !== '' ) {
|
||||
if ( /^\d$/.test(arg3) ) {
|
||||
details.options = [ arg3 ];
|
||||
} else {
|
||||
details.options = Array.from(arguments).slice(2);
|
||||
}
|
||||
}
|
||||
const { prop: chain = '', value: cValue = '' } = details;
|
||||
if ( typeof chain !== 'string' ) { return; }
|
||||
if ( chain === '' ) { return; }
|
||||
const options = details.options || [];
|
||||
function setConstant(chain, cValue) {
|
||||
const trappedProp = (( ) => {
|
||||
const pos = chain.lastIndexOf('.');
|
||||
if ( pos === -1 ) { return chain; }
|
||||
return chain.slice(pos+1);
|
||||
})();
|
||||
if ( trappedProp === '' ) { return; }
|
||||
const thisScript = document.currentScript;
|
||||
const objectDefineProperty = Object.defineProperty.bind(Object);
|
||||
const cloakFunc = fn => {
|
||||
objectDefineProperty(fn, 'name', { value: trappedProp });
|
||||
const proxy = new Proxy(fn, {
|
||||
defineProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
return handler.getter(); // cValue
|
||||
return true;
|
||||
},
|
||||
set(a) {
|
||||
if ( prevSetter !== undefined ) {
|
||||
prevSetter(a);
|
||||
deleteProperty(target, prop) {
|
||||
if ( prop !== 'toString' ) {
|
||||
return Reflect.deleteProperty(...arguments);
|
||||
}
|
||||
handler.setter(a);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
get(target, prop) {
|
||||
if ( prop === 'toString' ) {
|
||||
return function() {
|
||||
return `function ${trappedProp}() { [native code] }`;
|
||||
}.bind(null);
|
||||
}
|
||||
return Reflect.get(...arguments);
|
||||
},
|
||||
});
|
||||
} catch(ex) {
|
||||
return proxy;
|
||||
};
|
||||
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 === "''" ) {
|
||||
cValue = '';
|
||||
} else if ( cValue === '[]' ) {
|
||||
cValue = [];
|
||||
} else if ( cValue === '{}' ) {
|
||||
cValue = {};
|
||||
} else if ( cValue === 'noopFunc' ) {
|
||||
cValue = cloakFunc(function(){});
|
||||
} else if ( cValue === 'trueFunc' ) {
|
||||
cValue = cloakFunc(function(){ return true; });
|
||||
} else if ( cValue === 'falseFunc' ) {
|
||||
cValue = cloakFunc(function(){ return false; });
|
||||
} else if ( /^-?\d+$/.test(cValue) ) {
|
||||
cValue = parseInt(cValue);
|
||||
if ( isNaN(cValue) ) { return; }
|
||||
if ( Math.abs(cValue) > 0x7FFF ) { return; }
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
const trapChain = function(owner, chain) {
|
||||
const pos = chain.indexOf('.');
|
||||
if ( pos === -1 ) {
|
||||
trapProp(owner, chain, false, {
|
||||
if ( options.includes('asFunction') ) {
|
||||
cValue = ( ) => cValue;
|
||||
} else if ( options.includes('asCallback') ) {
|
||||
cValue = ( ) => (( ) => cValue);
|
||||
} else if ( options.includes('asResolved') ) {
|
||||
cValue = Promise.resolve(cValue);
|
||||
} else if ( options.includes('asRejected') ) {
|
||||
cValue = Promise.reject(cValue);
|
||||
}
|
||||
let aborted = false;
|
||||
const mustAbort = function(v) {
|
||||
if ( aborted ) { return true; }
|
||||
aborted =
|
||||
(v !== undefined && v !== null) &&
|
||||
(cValue !== undefined && cValue !== null) &&
|
||||
(typeof v !== typeof cValue);
|
||||
return aborted;
|
||||
};
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/156
|
||||
// Support multiple trappers for the same property.
|
||||
const trapProp = function(owner, prop, configurable, handler) {
|
||||
if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; }
|
||||
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
|
||||
let prevGetter, prevSetter;
|
||||
if ( odesc instanceof Object ) {
|
||||
owner[prop] = cValue;
|
||||
if ( odesc.get instanceof Function ) {
|
||||
prevGetter = odesc.get;
|
||||
}
|
||||
if ( odesc.set instanceof Function ) {
|
||||
prevSetter = odesc.set;
|
||||
}
|
||||
}
|
||||
try {
|
||||
objectDefineProperty(owner, prop, {
|
||||
configurable,
|
||||
get() {
|
||||
if ( prevGetter !== undefined ) {
|
||||
prevGetter();
|
||||
}
|
||||
return handler.getter(); // cValue
|
||||
},
|
||||
set(a) {
|
||||
if ( prevSetter !== undefined ) {
|
||||
prevSetter(a);
|
||||
}
|
||||
handler.setter(a);
|
||||
}
|
||||
});
|
||||
} catch(ex) {
|
||||
}
|
||||
};
|
||||
const trapChain = function(owner, chain) {
|
||||
const pos = chain.indexOf('.');
|
||||
if ( pos === -1 ) {
|
||||
trapProp(owner, chain, false, {
|
||||
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;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const prop = chain.slice(0, pos);
|
||||
const v = owner[prop];
|
||||
chain = chain.slice(pos + 1);
|
||||
if ( v instanceof Object || typeof v === 'object' && v !== null ) {
|
||||
trapChain(v, chain);
|
||||
return;
|
||||
}
|
||||
trapProp(owner, prop, true, {
|
||||
v: undefined,
|
||||
init: function(v) {
|
||||
if ( mustAbort(v) ) { return false; }
|
||||
this.v = v;
|
||||
return true;
|
||||
},
|
||||
getter: function() {
|
||||
return cValue;
|
||||
return this.v;
|
||||
},
|
||||
setter: function(a) {
|
||||
if ( mustAbort(a) === false ) { return; }
|
||||
cValue = a;
|
||||
this.v = a;
|
||||
if ( a instanceof Object ) {
|
||||
trapChain(a, chain);
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
const prop = chain.slice(0, pos);
|
||||
const v = owner[prop];
|
||||
chain = chain.slice(pos + 1);
|
||||
if ( v instanceof Object || typeof v === 'object' && v !== null ) {
|
||||
trapChain(v, chain);
|
||||
return;
|
||||
}
|
||||
trapProp(owner, prop, true, {
|
||||
v: undefined,
|
||||
init: function(v) {
|
||||
this.v = v;
|
||||
return true;
|
||||
},
|
||||
getter: function() {
|
||||
return this.v;
|
||||
},
|
||||
setter: function(a) {
|
||||
this.v = a;
|
||||
if ( a instanceof Object ) {
|
||||
trapChain(a, chain);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
trapChain(window, chain);
|
||||
};
|
||||
trapChain(window, chain);
|
||||
}
|
||||
runAt(( ) => {
|
||||
setConstant(chain, cValue);
|
||||
}, options);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
Loading…
Reference in New Issue
Block a user