From 4755a6094e828d1303063994bf58d3f033f5cd74 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 24 Sep 2022 18:22:44 -0400 Subject: [PATCH] [mv3] Add support for more scriplets: abort-on-property-write, no-settimeout-if --- .../mv3/scriptlets/abort-on-property-read.js | 2 +- .../mv3/scriptlets/abort-on-property-write.js | 119 +++++++++++++++++ platform/mv3/scriptlets/no-settimeout-if.js | 123 ++++++++++++++++++ 3 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 platform/mv3/scriptlets/abort-on-property-write.js create mode 100644 platform/mv3/scriptlets/no-settimeout-if.js diff --git a/platform/mv3/scriptlets/abort-on-property-read.js b/platform/mv3/scriptlets/abort-on-property-read.js index 4896fe508..f9197515e 100644 --- a/platform/mv3/scriptlets/abort-on-property-read.js +++ b/platform/mv3/scriptlets/abort-on-property-read.js @@ -100,7 +100,7 @@ const scriptlet = ( makeProxy(owner, chain); const oe = window.onerror; window.onerror = function(msg, src, line, col, error) { - if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) { + if ( typeof msg === 'string' && msg.includes(magic) ) { return true; } if ( oe instanceof Function ) { diff --git a/platform/mv3/scriptlets/abort-on-property-write.js b/platform/mv3/scriptlets/abort-on-property-write.js new file mode 100644 index 000000000..8cbe8deb3 --- /dev/null +++ b/platform/mv3/scriptlets/abort-on-property-write.js @@ -0,0 +1,119 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 + + The scriptlets below are meant to be injected only into a + web page context. +*/ + +/* jshint esversion:11 */ + +'use strict'; + +/******************************************************************************/ + +/// name abort-on-property-write +/// alias aopw + +/******************************************************************************/ + +// Important! +// Isolate from global scope +(function() { + +/******************************************************************************/ + +// $rulesetId$ + +const argsMap = new Map(self.$argsMap$); + +const hostnamesMap = new Map(self.$hostnamesMap$); + +/******************************************************************************/ + +const magic = + String.fromCharCode(Date.now() % 26 + 97) + + Math.floor(Math.random() * 982451653 + 982451653).toString(36); + +const abort = function() { + throw new ReferenceError(magic); +}; + +const scriptlet = ( + prop = '' +) => { + let owner = window; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + owner = owner[prop.slice(0, pos)]; + if ( owner instanceof Object === false ) { return; } + prop = prop.slice(pos + 1); + } + delete owner[prop]; + Object.defineProperty(owner, prop, { + set: function() { + abort(); + } + }); + const oe = window.onerror; + window.onerror = function(msg, src, line, col, error) { + if ( typeof msg === 'string' && msg.includes(magic) ) { + return true; + } + if ( oe instanceof Function ) { + return oe(msg, src, line, col, error); + } + }.bind(); +}; + +/******************************************************************************/ + +let hn; +try { hn = document.location.hostname; } catch(ex) { } +while ( hn ) { + if ( hostnamesMap.has(hn) ) { + let argsHashes = hostnamesMap.get(hn); + if ( typeof argsHashes === 'number' ) { argsHashes = [ argsHashes ]; } + for ( const argsHash of argsHashes ) { + const details = argsMap.get(argsHash); + if ( details.n && details.n.includes(hn) ) { continue; } + try { scriptlet(...details.a); } catch(ex) {} + } + } + if ( hn === '*' ) { break; } + const pos = hn.indexOf('.'); + if ( pos !== -1 ) { + hn = hn.slice(pos + 1); + } else { + hn = '*'; + } +} + +/******************************************************************************/ + +argsMap.clear(); +hostnamesMap.clear(); + +/******************************************************************************/ + +})(); + +/******************************************************************************/ + diff --git a/platform/mv3/scriptlets/no-settimeout-if.js b/platform/mv3/scriptlets/no-settimeout-if.js new file mode 100644 index 000000000..c0331abc6 --- /dev/null +++ b/platform/mv3/scriptlets/no-settimeout-if.js @@ -0,0 +1,123 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 + + The scriptlets below are meant to be injected only into a + web page context. +*/ + +/* jshint esversion:11 */ + +'use strict'; + +/******************************************************************************/ + +/// name no-setTimeout-if +/// alias nostif + +/******************************************************************************/ + +// Important! +// Isolate from global scope +(function() { + +/******************************************************************************/ + +// $rulesetId$ + +const argsMap = new Map(self.$argsMap$); + +const hostnamesMap = new Map(self.$hostnamesMap$); + +/******************************************************************************/ + +const scriptlet = ( + needle = '', + delay = '', + +) => { + const needleNot = needle.charAt(0) === '!'; + if ( needleNot ) { needle = needle.slice(1); } + if ( delay === '' ) { delay = undefined; } + let delayNot = false; + if ( delay !== undefined ) { + delayNot = delay.charAt(0) === '!'; + if ( delayNot ) { delay = delay.slice(1); } + delay = parseInt(delay, 10); + } + if ( needle.startsWith('/') && needle.endsWith('/') ) { + needle = needle.slice(1,-1); + } else if ( needle !== '' ) { + needle = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + const reNeedle = new RegExp(needle); + const regexpTest = RegExp.prototype.test.call; + self.setTimeout = new Proxy(self.setTimeout, { + apply: function(target, thisArg, args) { + const a = String(args[0]); + const b = args[1]; + let defuse; + if ( needle !== '' ) { + defuse = regexpTest(reNeedle, a) !== needleNot; + } + if ( defuse !== false && delay !== undefined ) { + defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; + } + if ( defuse ) { + args[0] = function(){}; + } + return target.apply(thisArg, args); + } + }); +}; + +/******************************************************************************/ + +let hn; +try { hn = document.location.hostname; } catch(ex) { } +while ( hn ) { + if ( hostnamesMap.has(hn) ) { + let argsHashes = hostnamesMap.get(hn); + if ( typeof argsHashes === 'number' ) { argsHashes = [ argsHashes ]; } + for ( const argsHash of argsHashes ) { + const details = argsMap.get(argsHash); + if ( details.n && details.n.includes(hn) ) { continue; } + try { scriptlet(...details.a); } catch(ex) {} + } + } + if ( hn === '*' ) { break; } + const pos = hn.indexOf('.'); + if ( pos !== -1 ) { + hn = hn.slice(pos + 1); + } else { + hn = '*'; + } +} + +/******************************************************************************/ + +argsMap.clear(); +hostnamesMap.clear(); + +/******************************************************************************/ + +})(); + +/******************************************************************************/ +