-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
697 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
import { | ||
hit, | ||
} from '../helpers/index'; | ||
|
||
/* eslint-disable max-len */ | ||
/** | ||
* @scriptlet spoof-css | ||
* | ||
* @description | ||
* Spoof CSS property value when `getComputedStyle()` or `getBoundingClientRect()` methods is called. | ||
* | ||
* Related UBO scriptlet: | ||
* https://github.com/gorhill/uBlock/wiki/Resources-Library#spoof-cssjs- | ||
* | ||
* ### Syntax | ||
* | ||
* ```text | ||
* example.org#%#//scriptlet('spoof-css'[, selectors[, properties[, shouldDebug]]]) | ||
* ``` | ||
* | ||
* - `selectors` — string of comma-separated selectors to match | ||
* - `properties` — CSS property name and 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 | ||
* | ||
* | ||
* ### Examples | ||
* | ||
* 1. Spoof CSS property value `display` to `block` for all elements with class `adsbygoogle`: | ||
* | ||
* ```adblock | ||
* 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') | ||
* ``` | ||
* | ||
* 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') | ||
* ``` | ||
* | ||
* 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') | ||
* ``` | ||
* | ||
* @added unknown. | ||
*/ | ||
/* eslint-enable max-len */ | ||
|
||
export function spoofCSS(source, selectors, properties, shouldDebug = false) { | ||
if (!selectors) { | ||
return; | ||
} | ||
|
||
const arrayOfProperties = properties.replace(/\s+/g, '').split(','); | ||
|
||
// 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); | ||
}; | ||
|
||
const propToValueMap = new Map(); | ||
for (let i = 0; i < arrayOfProperties.length; i += 2) { | ||
if (arrayOfProperties[i] === '') { | ||
break; | ||
} | ||
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; | ||
}; | ||
|
||
const setRectValue = (rect, prop, value) => { | ||
Object.defineProperty( | ||
rect, | ||
prop, | ||
{ | ||
value: parseFloat(value), | ||
}, | ||
); | ||
}; | ||
|
||
const getter = (target, prop, receiver) => { | ||
hit(source); | ||
if (prop === 'toString') { | ||
return target.toString.bind(target); | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
}; | ||
|
||
const getComputedStyleWrapper = (target, thisArg, args) => { | ||
if (shouldDebug) { | ||
debugger; // eslint-disable-line no-debugger | ||
} | ||
const style = Reflect.apply(target, thisArg, args); | ||
const targetElements = new WeakSet(document.querySelectorAll(selectors)); | ||
if (!targetElements.has(args[0])) { | ||
return style; | ||
} | ||
const proxiedStyle = new Proxy(style, { | ||
get(target, prop, receiver) { | ||
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; | ||
} | ||
return CSSStyleProp.bind(target); | ||
} | ||
return spoofStyle(prop, Reflect.get(target, prop, receiver)); | ||
}, | ||
getOwnPropertyDescriptor(target, prop) { | ||
if (propToValueMap.has(prop)) { | ||
return { | ||
configurable: true, | ||
enumerable: true, | ||
value: propToValueMap.get(prop), | ||
writable: true, | ||
}; | ||
} | ||
return Reflect.getOwnPropertyDescriptor(target, prop); | ||
}, | ||
}); | ||
hit(source); | ||
return proxiedStyle; | ||
}; | ||
|
||
const getComputedStyleHandler = { | ||
apply: getComputedStyleWrapper, | ||
get: getter, | ||
}; | ||
|
||
window.getComputedStyle = new Proxy(window.getComputedStyle, getComputedStyleHandler); | ||
|
||
const getBoundingClientRectWrapper = (target, thisArg, args) => { | ||
if (shouldDebug) { | ||
debugger; // eslint-disable-line no-debugger | ||
} | ||
const rect = Reflect.apply(target, thisArg, args); | ||
const targetElements = new WeakSet(document.querySelectorAll(selectors)); | ||
if (!targetElements.has(thisArg)) { | ||
return rect; | ||
} | ||
|
||
const { | ||
top, | ||
bottom, | ||
height, | ||
width, | ||
left, | ||
right, | ||
} = rect; | ||
|
||
const newDOMRect = new window.DOMRect(rect.x, rect.y, top, bottom, width, height, left, right); | ||
|
||
if (propToValueMap.has('top')) { | ||
setRectValue(newDOMRect, 'top', propToValueMap.get('top')); | ||
} | ||
if (propToValueMap.has('bottom')) { | ||
setRectValue(newDOMRect, 'bottom', propToValueMap.get('bottom')); | ||
} | ||
if (propToValueMap.has('left')) { | ||
setRectValue(newDOMRect, 'left', propToValueMap.get('left')); | ||
} | ||
if (propToValueMap.has('right')) { | ||
setRectValue(newDOMRect, 'right', propToValueMap.get('right')); | ||
} | ||
if (propToValueMap.has('height')) { | ||
setRectValue(newDOMRect, 'height', propToValueMap.get('height')); | ||
} | ||
if (propToValueMap.has('width')) { | ||
setRectValue(newDOMRect, 'width', propToValueMap.get('width')); | ||
} | ||
hit(source); | ||
return newDOMRect; | ||
}; | ||
|
||
const getBoundingClientRectHandler = { | ||
apply: getBoundingClientRectWrapper, | ||
get: getter, | ||
}; | ||
|
||
window.Element.prototype.getBoundingClientRect = new Proxy( | ||
window.Element.prototype.getBoundingClientRect, | ||
getBoundingClientRectHandler, | ||
); | ||
} | ||
|
||
spoofCSS.names = [ | ||
'spoof-css', | ||
// aliases are needed for matching the related scriptlet converted into our syntax | ||
'spoof-css.js', | ||
'ubo-spoof-css.js', | ||
'ubo-spoof-css', | ||
]; | ||
|
||
spoofCSS.injections = [ | ||
hit, | ||
]; |
Oops, something went wrong.