From 0bd4b600cf78a988b8ed677780ec258518822663 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 17 Jun 2023 11:53:08 -0400 Subject: [PATCH] Rework `nowoif` scriptlet 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`. --- assets/resources/scriptlets.js | 98 ++++++++++++++++++- src/js/redirect-resources.js | 1 - .../window.open-defuser.js | 90 +---------------- 3 files changed, 96 insertions(+), 93 deletions(-) diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index 9128321db..e3a3368ed 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -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, @@ -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('.'); @@ -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; }, @@ -456,7 +457,7 @@ function setConstantCore( } } try { - objectDefineProperty(owner, prop, { + safe.Object_defineProperty(owner, prop, { configurable, get() { if ( prevGetter !== undefined ) { @@ -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, @@ -2040,6 +2129,7 @@ function disableNewtabLinks() { builtinScriptlets.push({ name: 'cookie-remover.js', fn: cookieRemover, + world: 'ISOLATED', dependencies: [ 'pattern-to-regex.fn', ], diff --git a/src/js/redirect-resources.js b/src/js/redirect-resources.js index dd10c7089..34a85de18 100644 --- a/src/js/redirect-resources.js +++ b/src/js/redirect-resources.js @@ -180,7 +180,6 @@ export default new Map([ alias: 'scorecardresearch.com/beacon.js', } ], [ 'window.open-defuser.js', { - alias: 'nowoif.js', data: 'text', } ], ]); diff --git a/src/web_accessible_resources/window.open-defuser.js b/src/web_accessible_resources/window.open-defuser.js index 7c4dccb63..7f12ece75 100644 --- a/src/web_accessible_resources/window.open-defuser.js +++ b/src/web_accessible_resources/window.open-defuser.js @@ -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; } }); })();