1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-02 23:57:10 +02:00

Bring scriptlets up to date

This commit is contained in:
Raymond Hill 2023-05-19 15:35:38 -04:00
parent 1d63023263
commit eb2d6d1374
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
7 changed files with 599 additions and 295 deletions

View File

@ -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);
};
/******************************************************************************/

View File

@ -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);
};
/******************************************************************************/

View File

@ -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);
},
});
};

View File

@ -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);
},
});
};

View File

@ -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);
},
});
};

View File

@ -47,17 +47,83 @@ const entitiesMap = new Map(self.$entitiesMap$);
/******************************************************************************/
// 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 = (
chain = '',
cValue = ''
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 });
@ -105,13 +171,22 @@ const scriptlet = (
cValue = cloakFunc(function(){ return true; });
} else if ( cValue === 'falseFunc' ) {
cValue = cloakFunc(function(){ return false; });
} else if ( /^\d+$/.test(cValue) ) {
cValue = parseFloat(cValue);
} else if ( /^-?\d+$/.test(cValue) ) {
cValue = parseInt(cValue);
if ( isNaN(cValue) ) { return; }
if ( Math.abs(cValue) > 0x7FFF ) { return; }
} else {
return;
}
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; }
@ -124,7 +199,7 @@ const scriptlet = (
// 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; }
if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; }
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
let prevGetter, prevSetter;
if ( odesc instanceof Object ) {
@ -166,7 +241,9 @@ const scriptlet = (
return true;
},
getter: function() {
return cValue;
return document.currentScript === thisScript
? this.v
: cValue;
},
setter: function(a) {
if ( mustAbort(a) === false ) { return; }
@ -200,6 +277,10 @@ const scriptlet = (
});
};
trapChain(window, chain);
}
runAt(( ) => {
setConstant(chain, cValue);
}, options);
};
/******************************************************************************/

View File

@ -47,17 +47,83 @@ const hostnamesMap = new Map(self.$hostnamesMap$);
/******************************************************************************/
// 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 = (
chain = '',
cValue = ''
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 });
@ -105,13 +171,22 @@ const scriptlet = (
cValue = cloakFunc(function(){ return true; });
} else if ( cValue === 'falseFunc' ) {
cValue = cloakFunc(function(){ return false; });
} else if ( /^\d+$/.test(cValue) ) {
cValue = parseFloat(cValue);
} else if ( /^-?\d+$/.test(cValue) ) {
cValue = parseInt(cValue);
if ( isNaN(cValue) ) { return; }
if ( Math.abs(cValue) > 0x7FFF ) { return; }
} else {
return;
}
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; }
@ -124,7 +199,7 @@ const scriptlet = (
// 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; }
if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; }
const odesc = Object.getOwnPropertyDescriptor(owner, prop);
let prevGetter, prevSetter;
if ( odesc instanceof Object ) {
@ -166,7 +241,9 @@ const scriptlet = (
return true;
},
getter: function() {
return cValue;
return document.currentScript === thisScript
? this.v
: cValue;
},
setter: function(a) {
if ( mustAbort(a) === false ) { return; }
@ -200,6 +277,10 @@ const scriptlet = (
});
};
trapChain(window, chain);
}
runAt(( ) => {
setConstant(chain, cValue);
}, options);
};
/******************************************************************************/