mirror of
https://github.com/gorhill/uBlock.git
synced 2025-04-08 01:02:01 +08:00
273 lines
8.9 KiB
JavaScript
273 lines
8.9 KiB
JavaScript
/*******************************************************************************
|
|
|
|
uBlock Origin - a comprehensive, efficient content blocker
|
|
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
|
|
|
|
*/
|
|
|
|
import { matchesStackTraceFn } from './stack-trace.js';
|
|
import { proxyApplyFn } from './proxy-apply.js';
|
|
import { registerScriptlet } from './base.js';
|
|
import { safeSelf } from './safe-self.js';
|
|
|
|
/******************************************************************************/
|
|
|
|
function objectFindOwnerFn(
|
|
root,
|
|
path,
|
|
prune = false
|
|
) {
|
|
const safe = safeSelf();
|
|
let owner = root;
|
|
let chain = path;
|
|
for (;;) {
|
|
if ( typeof owner !== 'object' || owner === null ) { return false; }
|
|
const pos = chain.indexOf('.');
|
|
if ( pos === -1 ) {
|
|
if ( prune === false ) {
|
|
return safe.Object_hasOwn(owner, chain);
|
|
}
|
|
let modified = false;
|
|
if ( chain === '*' ) {
|
|
for ( const key in owner ) {
|
|
if ( safe.Object_hasOwn(owner, key) === false ) { continue; }
|
|
delete owner[key];
|
|
modified = true;
|
|
}
|
|
} else if ( safe.Object_hasOwn(owner, chain) ) {
|
|
delete owner[chain];
|
|
modified = true;
|
|
}
|
|
return modified;
|
|
}
|
|
const prop = chain.slice(0, pos);
|
|
const next = chain.slice(pos + 1);
|
|
let found = false;
|
|
if ( prop === '[-]' && Array.isArray(owner) ) {
|
|
let i = owner.length;
|
|
while ( i-- ) {
|
|
if ( objectFindOwnerFn(owner[i], next) === false ) { continue; }
|
|
owner.splice(i, 1);
|
|
found = true;
|
|
}
|
|
return found;
|
|
}
|
|
if ( prop === '{-}' && owner instanceof Object ) {
|
|
for ( const key of Object.keys(owner) ) {
|
|
if ( objectFindOwnerFn(owner[key], next) === false ) { continue; }
|
|
delete owner[key];
|
|
found = true;
|
|
}
|
|
return found;
|
|
}
|
|
if (
|
|
prop === '[]' && Array.isArray(owner) ||
|
|
prop === '{}' && owner instanceof Object ||
|
|
prop === '*' && owner instanceof Object
|
|
) {
|
|
for ( const key of Object.keys(owner) ) {
|
|
if (objectFindOwnerFn(owner[key], next, prune) === false ) { continue; }
|
|
found = true;
|
|
}
|
|
return found;
|
|
}
|
|
if ( safe.Object_hasOwn(owner, prop) === false ) { return false; }
|
|
owner = owner[prop];
|
|
chain = chain.slice(pos + 1);
|
|
}
|
|
}
|
|
registerScriptlet(objectFindOwnerFn, {
|
|
name: 'object-find-owner.fn',
|
|
dependencies: [
|
|
safeSelf,
|
|
],
|
|
});
|
|
|
|
/******************************************************************************/
|
|
|
|
// 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.
|
|
//
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/1545
|
|
// - Add support for "remove everything if needle matches" case
|
|
|
|
export function objectPruneFn(
|
|
obj,
|
|
rawPrunePaths,
|
|
rawNeedlePaths,
|
|
stackNeedleDetails = { matchAll: true },
|
|
extraArgs = {}
|
|
) {
|
|
if ( typeof rawPrunePaths !== 'string' ) { return; }
|
|
const safe = safeSelf();
|
|
const prunePaths = rawPrunePaths !== ''
|
|
? safe.String_split.call(rawPrunePaths, / +/)
|
|
: [];
|
|
const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== ''
|
|
? safe.String_split.call(rawNeedlePaths, / +/)
|
|
: [];
|
|
if ( stackNeedleDetails.matchAll !== true ) {
|
|
if ( matchesStackTraceFn(stackNeedleDetails, extraArgs.logstack) === false ) {
|
|
return;
|
|
}
|
|
}
|
|
if ( objectPruneFn.mustProcess === undefined ) {
|
|
objectPruneFn.mustProcess = (root, needlePaths) => {
|
|
for ( const needlePath of needlePaths ) {
|
|
if ( objectFindOwnerFn(root, needlePath) === false ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
}
|
|
if ( prunePaths.length === 0 ) { return; }
|
|
let outcome = 'nomatch';
|
|
if ( objectPruneFn.mustProcess(obj, needlePaths) ) {
|
|
for ( const path of prunePaths ) {
|
|
if ( objectFindOwnerFn(obj, path, true) ) {
|
|
outcome = 'match';
|
|
}
|
|
}
|
|
}
|
|
if ( outcome === 'match' ) { return obj; }
|
|
}
|
|
registerScriptlet(objectPruneFn, {
|
|
name: 'object-prune.fn',
|
|
dependencies: [
|
|
matchesStackTraceFn,
|
|
objectFindOwnerFn,
|
|
safeSelf,
|
|
],
|
|
});
|
|
|
|
/******************************************************************************/
|
|
|
|
function trustedPruneInboundObject(
|
|
entryPoint = '',
|
|
argPos = '',
|
|
rawPrunePaths = '',
|
|
rawNeedlePaths = ''
|
|
) {
|
|
if ( entryPoint === '' ) { return; }
|
|
let context = globalThis;
|
|
let prop = entryPoint;
|
|
for (;;) {
|
|
const pos = prop.indexOf('.');
|
|
if ( pos === -1 ) { break; }
|
|
context = context[prop.slice(0, pos)];
|
|
if ( context instanceof Object === false ) { return; }
|
|
prop = prop.slice(pos+1);
|
|
}
|
|
if ( typeof context[prop] !== 'function' ) { return; }
|
|
const argIndex = parseInt(argPos);
|
|
if ( isNaN(argIndex) ) { return; }
|
|
if ( argIndex < 1 ) { return; }
|
|
const safe = safeSelf();
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 4);
|
|
const needlePaths = [];
|
|
if ( rawPrunePaths !== '' ) {
|
|
needlePaths.push(...safe.String_split.call(rawPrunePaths, / +/));
|
|
}
|
|
if ( rawNeedlePaths !== '' ) {
|
|
needlePaths.push(...safe.String_split.call(rawNeedlePaths, / +/));
|
|
}
|
|
const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true });
|
|
const mustProcess = root => {
|
|
for ( const needlePath of needlePaths ) {
|
|
if ( objectFindOwnerFn(root, needlePath) === false ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
context[prop] = new Proxy(context[prop], {
|
|
apply: function(target, thisArg, args) {
|
|
const targetArg = argIndex <= args.length
|
|
? args[argIndex-1]
|
|
: undefined;
|
|
if ( targetArg instanceof Object && mustProcess(targetArg) ) {
|
|
let objBefore = targetArg;
|
|
if ( extraArgs.dontOverwrite ) {
|
|
try {
|
|
objBefore = safe.JSON_parse(safe.JSON_stringify(targetArg));
|
|
} catch {
|
|
objBefore = undefined;
|
|
}
|
|
}
|
|
if ( objBefore !== undefined ) {
|
|
const objAfter = objectPruneFn(
|
|
objBefore,
|
|
rawPrunePaths,
|
|
rawNeedlePaths,
|
|
stackNeedle,
|
|
extraArgs
|
|
);
|
|
args[argIndex-1] = objAfter || objBefore;
|
|
}
|
|
}
|
|
return Reflect.apply(target, thisArg, args);
|
|
},
|
|
});
|
|
}
|
|
registerScriptlet(trustedPruneInboundObject, {
|
|
name: 'trusted-prune-inbound-object.js',
|
|
requiresTrust: true,
|
|
dependencies: [
|
|
objectFindOwnerFn,
|
|
objectPruneFn,
|
|
safeSelf,
|
|
],
|
|
});
|
|
|
|
/******************************************************************************/
|
|
|
|
function trustedPruneOutboundObject(
|
|
propChain = '',
|
|
rawPrunePaths = '',
|
|
rawNeedlePaths = ''
|
|
) {
|
|
if ( propChain === '' ) { return; }
|
|
const safe = safeSelf();
|
|
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
|
|
proxyApplyFn(propChain, function(context) {
|
|
const objBefore = context.reflect();
|
|
if ( objBefore instanceof Object === false ) { return objBefore; }
|
|
const objAfter = objectPruneFn(
|
|
objBefore,
|
|
rawPrunePaths,
|
|
rawNeedlePaths,
|
|
{ matchAll: true },
|
|
extraArgs
|
|
);
|
|
return objAfter || objBefore;
|
|
});
|
|
}
|
|
registerScriptlet(trustedPruneOutboundObject, {
|
|
name: 'trusted-prune-outbound-object.js',
|
|
requiresTrust: true,
|
|
dependencies: [
|
|
objectPruneFn,
|
|
proxyApplyFn,
|
|
safeSelf,
|
|
],
|
|
});
|
|
|
|
/******************************************************************************/
|