Skip to content

Commit

Permalink
Use separate arguments for CSS name and CSS value
Browse files Browse the repository at this point in the history
Add support for uBO spoof-css arguments
Get rid of excessive nesting
  • Loading branch information
AdamWr committed Jan 18, 2024
1 parent 7480077 commit c7ef889
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 64 deletions.
116 changes: 80 additions & 36 deletions src/scriptlets/spoof-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import {
* ### Syntax
*
* ```text
* example.org#%#//scriptlet('spoof-css'[, selectors[, properties[, shouldDebug]]])
* example.org#%#//scriptlet('spoof-css'[, selectors[, cssNameProperty[, cssNameValue[, shouldDebug]]]])
* ```
*
* - `selectors` — string of comma-separated selectors to match
* - `properties` — CSS property name and property value, separated by a comma
* - `cssPropertyName` — CSS property name, separated by a comma
* - `cssPropertyValue` — CSS property value, separated by a comma
* - `shouldDebug` — optional, defaults to `false`, if set to `true`, will trigger debugger statement
* when `getComputedStyle()` or `getBoundingClientRect()` methods is called
*
Expand All @@ -29,62 +30,101 @@ import {
* 1. Spoof CSS property value `display` to `block` for all elements with class `adsbygoogle`:
*
* ```adblock
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle', 'display, block')
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle', 'display', 'block')
* ```
*
* 2. Spoof CSS property value `display` to `block`, `visibility` to `visible` for all elements with class `adsbygoogle`:
*
* ```adblock
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle', 'display, block, visibility, visible')
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle', 'display, visibility', 'block, visible')
* ```
*
* 3. Spoof CSS property value `height` to `100` for all elements with class `adsbygoogle` and `advert`:
*
* ```adblock
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle, .advert', 'height, 100')
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle, .advert', 'height', '100')
* ```
*
* 4. Spoof CSS property value `height` to `100`, `display` to `block`
* for all elements with class `adsbygoogle` and `advert`:
*
* ```adblock
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle, .advert', 'height, 100, display, block')
* example.org#%#//scriptlet('spoof-css', '.adsbygoogle, .advert', 'height, display', '100, block')
* ```
*
* @added unknown.
*/
/* eslint-enable max-len */

export function spoofCSS(source, selectors, properties, shouldDebug = false) {
export function spoofCSS(source, selectors, cssPropertyName, cssPropertyValue, shouldDebug = false) {
if (!selectors) {
return;
}

const arrayOfProperties = properties.replace(/\s+/g, '').split(',');
const uboAliases = [
'spoof-css.js',
'ubo-spoof-css.js',
'ubo-spoof-css',
];

// getComputedStyle uses camelCase version of CSS properties
// for example, "clip-path" is displayed as "clipPath"
// so it's needed to convert CSS property to camelCase
const toCamelCase = (property) => {
const toUpperCase = (text) => text.charAt(1).toUpperCase();
return property.replace(/-[a-z]/g, toUpperCase);
};
function convertToCamelCase(cssProperty) {
if (!cssProperty.includes('-')) {
return cssProperty;
}
const splittedProperty = cssProperty.split('-');
const firstPart = splittedProperty[0];
const secondtPart = splittedProperty[1];
return firstPart + secondtPart[0].toUpperCase() + secondtPart.slice(1);
}

const propToValueMap = new Map();
for (let i = 0; i < arrayOfProperties.length; i += 2) {
if (arrayOfProperties[i] === '') {
break;

/**
* UBO spoof-css analog has it's own args sequence:
* (selectors, ...arguments)
* arguments contains property-name/property-value pairs, all separated by commas
*
* example.com##+js(spoof-css, a[href="x.com"]\, .ads\, .bottom, clip-path, none)
* example.com##+js(spoof-css, .ad, clip-path, none, display, block)
* example.com##+js(spoof-css, .ad, debug, 1)
*/
if (uboAliases.includes(source.name)) {
const { args } = source;
let arrayOfProperties = [];
// Check if one before last argument is 'debug'
const isDebug = args.at(-2);
if (isDebug === 'debug') {
// If it's debug, then we need to skip first (selectors) and last two arguments
arrayOfProperties = args.slice(1, -2);
} else {
// If it's not debug, then we need to skip only first (selectors) argument
arrayOfProperties = args.slice(1);
}
for (let i = 0; i < arrayOfProperties.length; i += 2) {
if (arrayOfProperties[i] === '') {
break;
}
propToValueMap.set(convertToCamelCase(arrayOfProperties[i]), arrayOfProperties[i + 1]);
}
} else {
const arrayOfCssNames = cssPropertyName.replace(/\s+/g, '').split(',');
const arrayOfCssValues = cssPropertyValue.replace(/\s+/g, '').split(',');

for (let i = 0; i < arrayOfCssNames.length; i += 1) {
if (arrayOfCssNames[i] === '') {
break;
}
propToValueMap.set(convertToCamelCase(arrayOfCssNames[i]), arrayOfCssValues[i]);
}
propToValueMap.set(toCamelCase(arrayOfProperties[i]), arrayOfProperties[i + 1]);
}

const spoofStyle = (cssProperty, realCssValue) => {
const property = cssProperty;
// For non existing properties return empty string
const realValue = realCssValue ?? '';
const shouldSpoof = propToValueMap.has(property);
const value = shouldSpoof ? propToValueMap.get(property) : realValue;
return value;
return propToValueMap.has(cssProperty)
? propToValueMap.get(cssProperty)
: realCssValue;
};

const setRectValue = (rect, prop, value) => {
Expand Down Expand Up @@ -115,23 +155,27 @@ export function spoofCSS(source, selectors, properties, shouldDebug = false) {
return style;
}
const proxiedStyle = new Proxy(style, {
get(target, prop, receiver) {
get(target, prop) {
const CSSStyleProp = target[prop];
if (typeof CSSStyleProp === 'function') {
if (prop === 'getPropertyValue') {
const getPropertyValueFunc = new Proxy(CSSStyleProp, {
apply(target, thisArg, args) {
const cssName = args[0];
const cssValue = thisArg[cssName];
return spoofStyle(cssName, cssValue);
},
get: getter,
});
return getPropertyValueFunc;
}

if (typeof CSSStyleProp !== 'function') {
return spoofStyle(prop, CSSStyleProp || '');
}

if (prop !== 'getPropertyValue') {
return CSSStyleProp.bind(target);
}
return spoofStyle(prop, Reflect.get(target, prop, receiver));

const getPropertyValueFunc = new Proxy(CSSStyleProp, {
apply(target, thisArg, args) {
const cssName = args[0];
const cssValue = thisArg[cssName];
return spoofStyle(cssName, cssValue);
},
get: getter,
});

return getPropertyValueFunc;
},
getOwnPropertyDescriptor(target, prop) {
if (propToValueMap.has(prop)) {
Expand Down
Loading

0 comments on commit c7ef889

Please sign in to comment.