Skip to content

Commit

Permalink
Rework nowoif scriptlet
Browse files Browse the repository at this point in the history
New official name: `no-window-open-if`.

The pattern will now be matched against all arguments passed
to `window.open()`: all the arguments are joined as a single
space-spearated string, and the result is used as the target
for matching the pattern.

To enable logging, used the extra parameters approach, i.e.
`log, 1`, which should come after the positional arguments
`pattern`, `delay`, and `decoy`.
  • Loading branch information
gorhill committed Jun 17, 2023
1 parent 29015b3 commit 0bd4b60
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 93 deletions.
98 changes: 94 additions & 4 deletions assets/resources/scriptlets.js
Expand Up @@ -46,6 +46,7 @@ function safeSelf() {
return scriptletGlobals.get('safeSelf');
}
const safe = {
'Object_defineProperty': Object.defineProperty.bind(Object),
'RegExp': self.RegExp,
'RegExp_test': self.RegExp.prototype.test,
'RegExp_exec': self.RegExp.prototype.exec,
Expand Down Expand Up @@ -355,6 +356,7 @@ function setConstantCore(
if ( typeof chain !== 'string' ) { return; }
if ( chain === '' ) { return; }
const options = details.options || [];
const safe = safeSelf();
function setConstant(chain, cValue) {
const trappedProp = (( ) => {
const pos = chain.lastIndexOf('.');
Expand All @@ -363,13 +365,12 @@ function setConstantCore(
})();
if ( trappedProp === '' ) { return; }
const thisScript = document.currentScript;
const objectDefineProperty = Object.defineProperty.bind(Object);
const cloakFunc = fn => {
objectDefineProperty(fn, 'name', { value: trappedProp });
safe.Object_defineProperty(fn, 'name', { value: trappedProp });
const proxy = new Proxy(fn, {
defineProperty(target, prop) {
if ( prop !== 'toString' ) {
return Reflect.deleteProperty(...arguments);
return Reflect.defineProperty(...arguments);
}
return true;
},
Expand Down Expand Up @@ -456,7 +457,7 @@ function setConstantCore(
}
}
try {
objectDefineProperty(owner, prop, {
safe.Object_defineProperty(owner, prop, {
configurable,
get() {
if ( prevGetter !== undefined ) {
Expand Down Expand Up @@ -1758,6 +1759,94 @@ function noXhrIf(

/******************************************************************************/

builtinScriptlets.push({
name: 'no-window-open-if.js',
aliases: [ 'nowoif.js' ],
fn: noWindowOpenIf,
dependencies: [
'get-extra-args.fn',
'pattern-to-regex.fn',
'safe-self.fn',
'should-log.fn',
],
});
function noWindowOpenIf(
pattern = '',
delay = '',
decoy = ''
) {
const targetMatchResult = pattern.startsWith('!') === false;
if ( targetMatchResult === false ) {
pattern = pattern.slice(1);
}
const rePattern = patternToRegex(pattern);
let autoRemoveAfter = parseInt(delay);
if ( isNaN(autoRemoveAfter) ) {
autoRemoveAfter = -1;
}
const extraArgs = getExtraArgs(Array.from(arguments), 3);
const safe = safeSelf();
const logLevel = shouldLog(extraArgs);
const createDecoy = function(tag, urlProp, url) {
const decoyElem = document.createElement(tag);
decoyElem[urlProp] = url;
decoyElem.style.setProperty('height','1px', 'important');
decoyElem.style.setProperty('position','fixed', 'important');
decoyElem.style.setProperty('top','-1px', 'important');
decoyElem.style.setProperty('width','1px', 'important');
document.body.appendChild(decoyElem);
setTimeout(( ) => { decoyElem.remove(); }, autoRemoveAfter * 1000);
return decoyElem;
};
window.open = new Proxy(window.open, {
apply: function(target, thisArg, args) {
const haystack = args.join(' ');
if ( logLevel ) {
safe.uboLog('window.open:', haystack);
}
if ( rePattern.test(haystack) !== targetMatchResult ) {
return Reflect.apply(target, thisArg, args);
}
if ( autoRemoveAfter < 0 ) { return null; }
const decoyElem = decoy === 'obj'
? createDecoy('object', 'data', ...args)
: createDecoy('iframe', 'src', ...args);
let popup = decoyElem.contentWindow;
if ( typeof popup === 'object' && popup !== null ) {
Object.defineProperty(popup, 'closed', { value: false });
} else {
const noopFunc = (function(){}).bind(self);
popup = new Proxy(self, {
get: function(target, prop) {
if ( prop === 'closed' ) { return false; }
const r = Reflect.get(...arguments);
if ( typeof r === 'function' ) { return noopFunc; }
return target[prop];
},
set: function() {
return Reflect.set(...arguments);
},
});
}
if ( logLevel ) {
popup = new Proxy(popup, {
get: function(target, prop) {
safe.uboLog('window.open / get', prop, '===', target[prop]);
return Reflect.get(...arguments);
},
set: function(target, prop, value) {
safe.uboLog('window.open / set', prop, '=', value);
return Reflect.set(...arguments);
},
});
}
return popup;
}
});
}

/******************************************************************************/

builtinScriptlets.push({
name: 'window-close-if.js',
fn: windowCloseIf,
Expand Down Expand Up @@ -2040,6 +2129,7 @@ function disableNewtabLinks() {
builtinScriptlets.push({
name: 'cookie-remover.js',
fn: cookieRemover,
world: 'ISOLATED',
dependencies: [
'pattern-to-regex.fn',
],
Expand Down
1 change: 0 additions & 1 deletion src/js/redirect-resources.js
Expand Up @@ -180,7 +180,6 @@ export default new Map([
alias: 'scorecardresearch.com/beacon.js',
} ],
[ 'window.open-defuser.js', {
alias: 'nowoif.js',
data: 'text',
} ],
]);
90 changes: 2 additions & 88 deletions src/web_accessible_resources/window.open-defuser.js
Expand Up @@ -21,95 +21,9 @@

(function() {
'use strict';
let arg1 = '{{1}}';
if ( arg1 === '{{1}}' ) { arg1 = ''; }
let arg2 = '{{2}}';
if ( arg2 === '{{2}}' ) { arg2 = ''; }
let arg3 = '{{3}}';
if ( arg3 === '{{3}}' ) { arg3 = ''; }
const log = /\blog\b/.test(arg3)
? console.log.bind(console)
: ( ) => { };
const newSyntax = /^[01]?$/.test(arg1) === false;
let pattern = '';
let targetResult = true;
let autoRemoveAfter = -1;
if ( newSyntax ) {
pattern = arg1;
if ( pattern.startsWith('!') ) {
targetResult = false;
pattern = pattern.slice(1);
}
autoRemoveAfter = parseInt(arg2);
if ( isNaN(autoRemoveAfter) ) {
autoRemoveAfter = -1;
}
} else {
pattern = arg2;
if ( arg1 === '0' ) {
targetResult = false;
}
}
if ( pattern === '' ) {
pattern = '.?';
} else if ( /^\/.+\/$/.test(pattern) ) {
pattern = pattern.slice(1,-1);
} else {
pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const rePattern = new RegExp(pattern);
const createDecoy = function(tag, urlProp, url) {
const decoy = document.createElement(tag);
decoy[urlProp] = url;
decoy.style.setProperty('height','1px', 'important');
decoy.style.setProperty('position','fixed', 'important');
decoy.style.setProperty('top','-1px', 'important');
decoy.style.setProperty('width','1px', 'important');
document.body.appendChild(decoy);
setTimeout(( ) => decoy.remove(), autoRemoveAfter * 1000);
return decoy;
};
window.open = new Proxy(window.open, {
apply: function(target, thisArg, args) {
log('window.open:', ...args);
const url = args[0];
if ( rePattern.test(url) !== targetResult ) {
return target.apply(thisArg, args);
}
if ( autoRemoveAfter < 0 ) { return null; }
const decoy = /\bobj\b/.test(arg3)
? createDecoy('object', 'data', url)
: createDecoy('iframe', 'src', url);
let popup = decoy.contentWindow;
if ( typeof popup === 'object' && popup !== null ) {
Object.defineProperty(popup, 'closed', { value: false });
} else {
const noopFunc = (function(){}).bind(self);
popup = new Proxy(self, {
get: function(target, prop) {
if ( prop === 'closed' ) { return false; }
const r = Reflect.get(...arguments);
if ( typeof r === 'function' ) { return noopFunc; }
return target[prop];
},
set: function() {
return Reflect.set(...arguments);
},
});
}
if ( /\blog\b/.test(arg3) ) {
popup = new Proxy(popup, {
get: function(target, prop) {
log('window.open / get', prop, '===', target[prop]);
return Reflect.get(...arguments);
},
set: function(target, prop, value) {
log('window.open / set', prop, '=', value);
return Reflect.set(...arguments);
},
});
}
return popup;
apply: function() {
return null;
}
});
})();

0 comments on commit 0bd4b60

Please sign in to comment.