diff options
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/spoof-css.js')
-rw-r--r-- | data/extensions/uBlock0@raymondhill.net/js/resources/spoof-css.js | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/spoof-css.js b/data/extensions/uBlock0@raymondhill.net/js/resources/spoof-css.js new file mode 100644 index 0000000..7cc7b9f --- /dev/null +++ b/data/extensions/uBlock0@raymondhill.net/js/resources/spoof-css.js @@ -0,0 +1,163 @@ +/******************************************************************************* + + 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 { registerScriptlet } from './base.js'; +import { safeSelf } from './safe-self.js'; + +/** + * @scriptlet spoof-css.js + * + * @description + * Spoof the value of CSS properties. + * + * @param selector + * A CSS selector for the element(s) to target. + * + * @param [property, value, ...] + * A list of property-value pairs of the style properties to spoof to the + * specified values. +* + * */ + +export function spoofCSS( + selector, + ...args +) { + if ( typeof selector !== 'string' ) { return; } + if ( selector === '' ) { return; } + const toCamelCase = s => s.replace(/-[a-z]/g, s => s.charAt(1).toUpperCase()); + const propToValueMap = new Map(); + const privatePropToValueMap = new Map(); + for ( let i = 0; i < args.length; i += 2 ) { + const prop = toCamelCase(args[i+0]); + if ( prop === '' ) { break; } + const value = args[i+1]; + if ( typeof value !== 'string' ) { break; } + if ( prop.charCodeAt(0) === 0x5F /* _ */ ) { + privatePropToValueMap.set(prop, value); + } else { + propToValueMap.set(prop, value); + } + } + const safe = safeSelf(); + const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args); + const instanceProperties = [ 'cssText', 'length', 'parentRule' ]; + const spoofStyle = (prop, real) => { + const normalProp = toCamelCase(prop); + const shouldSpoof = propToValueMap.has(normalProp); + const value = shouldSpoof ? propToValueMap.get(normalProp) : real; + if ( shouldSpoof ) { + safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`); + } + return value; + }; + const cloackFunc = (fn, thisArg, name) => { + const trap = fn.bind(thisArg); + Object.defineProperty(trap, 'name', { value: name }); + Object.defineProperty(trap, 'toString', { + value: ( ) => `function ${name}() { [native code] }` + }); + return trap; + }; + self.getComputedStyle = new Proxy(self.getComputedStyle, { + apply: function(target, thisArg, args) { + // eslint-disable-next-line no-debugger + if ( privatePropToValueMap.has('_debug') ) { debugger; } + const style = Reflect.apply(target, thisArg, args); + const targetElements = new WeakSet(document.querySelectorAll(selector)); + if ( targetElements.has(args[0]) === false ) { return style; } + const proxiedStyle = new Proxy(style, { + get(target, prop) { + if ( typeof target[prop] === 'function' ) { + if ( prop === 'getPropertyValue' ) { + return cloackFunc(function getPropertyValue(prop) { + return spoofStyle(prop, target[prop]); + }, target, 'getPropertyValue'); + } + return cloackFunc(target[prop], target, prop); + } + if ( instanceProperties.includes(prop) ) { + return Reflect.get(target, prop); + } + return spoofStyle(prop, Reflect.get(target, prop)); + }, + getOwnPropertyDescriptor(target, prop) { + if ( propToValueMap.has(prop) ) { + return { + configurable: true, + enumerable: true, + value: propToValueMap.get(prop), + writable: true, + }; + } + return Reflect.getOwnPropertyDescriptor(target, prop); + }, + }); + return proxiedStyle; + }, + get(target, prop) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop); + }, + }); + Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, { + apply: function(target, thisArg, args) { + // eslint-disable-next-line no-debugger + if ( privatePropToValueMap.has('_debug') ) { debugger; } + const rect = Reflect.apply(target, thisArg, args); + const targetElements = new WeakSet(document.querySelectorAll(selector)); + if ( targetElements.has(thisArg) === false ) { return rect; } + let { x, y, height, width } = rect; + if ( privatePropToValueMap.has('_rectx') ) { + x = parseFloat(privatePropToValueMap.get('_rectx')); + } + if ( privatePropToValueMap.has('_recty') ) { + y = parseFloat(privatePropToValueMap.get('_recty')); + } + if ( privatePropToValueMap.has('_rectw') ) { + width = parseFloat(privatePropToValueMap.get('_rectw')); + } else if ( propToValueMap.has('width') ) { + width = parseFloat(propToValueMap.get('width')); + } + if ( privatePropToValueMap.has('_recth') ) { + height = parseFloat(privatePropToValueMap.get('_recth')); + } else if ( propToValueMap.has('height') ) { + height = parseFloat(propToValueMap.get('height')); + } + return new self.DOMRect(x, y, width, height); + }, + get(target, prop) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop); + }, + }); +} +registerScriptlet(spoofCSS, { + name: 'spoof-css.js', + dependencies: [ + safeSelf, + ], +}); |