summaryrefslogtreecommitdiff
path: root/data/extensions/uBlock0@raymondhill.net/js/resources/spoof-css.js
diff options
context:
space:
mode:
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.js163
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,
+ ],
+});