Skip to content

Commit 5f5e3d7

Browse files
committed
Improve spoof-css scriptlet
Added special properties to spoof output of getBoundingClientRect().
1 parent c8174d6 commit 5f5e3d7

File tree

3 files changed

+170
-116
lines changed

3 files changed

+170
-116
lines changed

assets/resources/run-at.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ registerScriptlet(runAt, {
8282

8383
/******************************************************************************/
8484

85-
function runAtHtmlElementFn(fn) {
85+
export function runAtHtmlElementFn(fn) {
8686
if ( document.documentElement ) {
8787
fn();
8888
return;

assets/resources/scriptlets.js

Lines changed: 3 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ import './cookie.js';
2727
import './localstorage.js';
2828
import './run-at.js';
2929
import './safe-self.js';
30+
import './spoof-css.js';
31+
32+
import { runAt, runAtHtmlElementFn } from './run-at.js';
3033

3134
import { getAllCookiesFn } from './cookie.js';
3235
import { getAllLocalStorageFn } from './localstorage.js';
3336
import { registeredScriptlets } from './base.js';
34-
import { runAt } from './run-at.js';
3537
import { safeSelf } from './safe-self.js';
3638

3739
// Externally added to the private namespace in which scriptlets execute.
@@ -3269,120 +3271,6 @@ function callNothrow(
32693271
});
32703272
}
32713273

3272-
3273-
/******************************************************************************/
3274-
3275-
builtinScriptlets.push({
3276-
name: 'spoof-css.js',
3277-
fn: spoofCSS,
3278-
dependencies: [
3279-
'safe-self.fn',
3280-
],
3281-
});
3282-
function spoofCSS(
3283-
selector,
3284-
...args
3285-
) {
3286-
if ( typeof selector !== 'string' ) { return; }
3287-
if ( selector === '' ) { return; }
3288-
const toCamelCase = s => s.replace(/-[a-z]/g, s => s.charAt(1).toUpperCase());
3289-
const propToValueMap = new Map();
3290-
for ( let i = 0; i < args.length; i += 2 ) {
3291-
if ( typeof args[i+0] !== 'string' ) { break; }
3292-
if ( args[i+0] === '' ) { break; }
3293-
if ( typeof args[i+1] !== 'string' ) { break; }
3294-
propToValueMap.set(toCamelCase(args[i+0]), args[i+1]);
3295-
}
3296-
const safe = safeSelf();
3297-
const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args);
3298-
const canDebug = scriptletGlobals.canDebug;
3299-
const shouldDebug = canDebug && propToValueMap.get('debug') || 0;
3300-
const instanceProperties = [ 'cssText', 'length', 'parentRule' ];
3301-
const spoofStyle = (prop, real) => {
3302-
const normalProp = toCamelCase(prop);
3303-
const shouldSpoof = propToValueMap.has(normalProp);
3304-
const value = shouldSpoof ? propToValueMap.get(normalProp) : real;
3305-
if ( shouldSpoof ) {
3306-
safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`);
3307-
}
3308-
return value;
3309-
};
3310-
const cloackFunc = (fn, thisArg, name) => {
3311-
const trap = fn.bind(thisArg);
3312-
Object.defineProperty(trap, 'name', { value: name });
3313-
Object.defineProperty(trap, 'toString', {
3314-
value: ( ) => `function ${name}() { [native code] }`
3315-
});
3316-
return trap;
3317-
};
3318-
self.getComputedStyle = new Proxy(self.getComputedStyle, {
3319-
apply: function(target, thisArg, args) {
3320-
// eslint-disable-next-line no-debugger
3321-
if ( shouldDebug !== 0 ) { debugger; }
3322-
const style = Reflect.apply(target, thisArg, args);
3323-
const targetElements = new WeakSet(document.querySelectorAll(selector));
3324-
if ( targetElements.has(args[0]) === false ) { return style; }
3325-
const proxiedStyle = new Proxy(style, {
3326-
get(target, prop) {
3327-
if ( typeof target[prop] === 'function' ) {
3328-
if ( prop === 'getPropertyValue' ) {
3329-
return cloackFunc(function getPropertyValue(prop) {
3330-
return spoofStyle(prop, target[prop]);
3331-
}, target, 'getPropertyValue');
3332-
}
3333-
return cloackFunc(target[prop], target, prop);
3334-
}
3335-
if ( instanceProperties.includes(prop) ) {
3336-
return Reflect.get(target, prop);
3337-
}
3338-
return spoofStyle(prop, Reflect.get(target, prop));
3339-
},
3340-
getOwnPropertyDescriptor(target, prop) {
3341-
if ( propToValueMap.has(prop) ) {
3342-
return {
3343-
configurable: true,
3344-
enumerable: true,
3345-
value: propToValueMap.get(prop),
3346-
writable: true,
3347-
};
3348-
}
3349-
return Reflect.getOwnPropertyDescriptor(target, prop);
3350-
},
3351-
});
3352-
return proxiedStyle;
3353-
},
3354-
get(target, prop) {
3355-
if ( prop === 'toString' ) {
3356-
return target.toString.bind(target);
3357-
}
3358-
return Reflect.get(target, prop);
3359-
},
3360-
});
3361-
Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, {
3362-
apply: function(target, thisArg, args) {
3363-
// eslint-disable-next-line no-debugger
3364-
if ( shouldDebug !== 0 ) { debugger; }
3365-
const rect = Reflect.apply(target, thisArg, args);
3366-
const targetElements = new WeakSet(document.querySelectorAll(selector));
3367-
if ( targetElements.has(thisArg) === false ) { return rect; }
3368-
let { height, width } = rect;
3369-
if ( propToValueMap.has('width') ) {
3370-
width = parseFloat(propToValueMap.get('width'));
3371-
}
3372-
if ( propToValueMap.has('height') ) {
3373-
height = parseFloat(propToValueMap.get('height'));
3374-
}
3375-
return new self.DOMRect(rect.x, rect.y, width, height);
3376-
},
3377-
get(target, prop) {
3378-
if ( prop === 'toString' ) {
3379-
return target.toString.bind(target);
3380-
}
3381-
return Reflect.get(target, prop);
3382-
},
3383-
});
3384-
}
3385-
33863274
/******************************************************************************/
33873275

33883276
builtinScriptlets.push({

assets/resources/spoof-css.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a comprehensive, efficient content blocker
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
The scriptlets below are meant to be injected only into a
22+
web page context.
23+
*/
24+
25+
import { registerScriptlet } from './base.js';
26+
import { safeSelf } from './safe-self.js';
27+
28+
/**
29+
* @scriptlet spoof-css.js
30+
*
31+
* @description
32+
* Spoof the value of CSS properties.
33+
*
34+
* @param selector
35+
* A CSS selector for the element(s) to target.
36+
*
37+
* @param [property, value, ...]
38+
* A list of property-value pairs of the style properties to spoof to the
39+
* specified values.
40+
*
41+
* */
42+
43+
export function spoofCSS(
44+
selector,
45+
...args
46+
) {
47+
if ( typeof selector !== 'string' ) { return; }
48+
if ( selector === '' ) { return; }
49+
const toCamelCase = s => s.replace(/-[a-z]/g, s => s.charAt(1).toUpperCase());
50+
const propToValueMap = new Map();
51+
const privatePropToValueMap = new Map();
52+
for ( let i = 0; i < args.length; i += 2 ) {
53+
const prop = toCamelCase(args[i+0]);
54+
if ( typeof prop !== 'string' ) { break; }
55+
if ( prop === '' ) { break; }
56+
const value = args[i+1];
57+
if ( typeof value !== 'string' ) { break; }
58+
if ( prop.charCodeAt(0) === 0x5F /* _ */ ) {
59+
privatePropToValueMap.set(prop, value);
60+
} else {
61+
propToValueMap.set(toCamelCase(prop), value);
62+
}
63+
}
64+
const safe = safeSelf();
65+
const logPrefix = safe.makeLogPrefix('spoof-css', selector, ...args);
66+
const instanceProperties = [ 'cssText', 'length', 'parentRule' ];
67+
const spoofStyle = (prop, real) => {
68+
const normalProp = toCamelCase(prop);
69+
const shouldSpoof = propToValueMap.has(normalProp);
70+
const value = shouldSpoof ? propToValueMap.get(normalProp) : real;
71+
if ( shouldSpoof ) {
72+
safe.uboLog(logPrefix, `Spoofing ${prop} to ${value}`);
73+
}
74+
return value;
75+
};
76+
const cloackFunc = (fn, thisArg, name) => {
77+
const trap = fn.bind(thisArg);
78+
Object.defineProperty(trap, 'name', { value: name });
79+
Object.defineProperty(trap, 'toString', {
80+
value: ( ) => `function ${name}() { [native code] }`
81+
});
82+
return trap;
83+
};
84+
self.getComputedStyle = new Proxy(self.getComputedStyle, {
85+
apply: function(target, thisArg, args) {
86+
// eslint-disable-next-line no-debugger
87+
if ( privatePropToValueMap.has('_debug') ) { debugger; }
88+
const style = Reflect.apply(target, thisArg, args);
89+
const targetElements = new WeakSet(document.querySelectorAll(selector));
90+
if ( targetElements.has(args[0]) === false ) { return style; }
91+
const proxiedStyle = new Proxy(style, {
92+
get(target, prop) {
93+
if ( typeof target[prop] === 'function' ) {
94+
if ( prop === 'getPropertyValue' ) {
95+
return cloackFunc(function getPropertyValue(prop) {
96+
return spoofStyle(prop, target[prop]);
97+
}, target, 'getPropertyValue');
98+
}
99+
return cloackFunc(target[prop], target, prop);
100+
}
101+
if ( instanceProperties.includes(prop) ) {
102+
return Reflect.get(target, prop);
103+
}
104+
return spoofStyle(prop, Reflect.get(target, prop));
105+
},
106+
getOwnPropertyDescriptor(target, prop) {
107+
if ( propToValueMap.has(prop) ) {
108+
return {
109+
configurable: true,
110+
enumerable: true,
111+
value: propToValueMap.get(prop),
112+
writable: true,
113+
};
114+
}
115+
return Reflect.getOwnPropertyDescriptor(target, prop);
116+
},
117+
});
118+
return proxiedStyle;
119+
},
120+
get(target, prop) {
121+
if ( prop === 'toString' ) {
122+
return target.toString.bind(target);
123+
}
124+
return Reflect.get(target, prop);
125+
},
126+
});
127+
Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, {
128+
apply: function(target, thisArg, args) {
129+
// eslint-disable-next-line no-debugger
130+
if ( privatePropToValueMap.has('_debug') ) { debugger; }
131+
const rect = Reflect.apply(target, thisArg, args);
132+
const targetElements = new WeakSet(document.querySelectorAll(selector));
133+
if ( targetElements.has(thisArg) === false ) { return rect; }
134+
let { x, y, height, width } = rect;
135+
if ( privatePropToValueMap.has('_rectx') ) {
136+
x = parseFloat(privatePropToValueMap.get('_rectx'));
137+
}
138+
if ( privatePropToValueMap.has('_recty') ) {
139+
y = parseFloat(privatePropToValueMap.get('_recty'));
140+
}
141+
if ( privatePropToValueMap.has('_rectw') ) {
142+
width = parseFloat(privatePropToValueMap.get('_rectw'));
143+
} else if ( propToValueMap.has('width') ) {
144+
width = parseFloat(propToValueMap.get('width'));
145+
}
146+
if ( privatePropToValueMap.has('_recth') ) {
147+
height = parseFloat(privatePropToValueMap.get('_recth'));
148+
} else if ( propToValueMap.has('height') ) {
149+
height = parseFloat(propToValueMap.get('height'));
150+
}
151+
return new self.DOMRect(x, y, width, height);
152+
},
153+
get(target, prop) {
154+
if ( prop === 'toString' ) {
155+
return target.toString.bind(target);
156+
}
157+
return Reflect.get(target, prop);
158+
},
159+
});
160+
}
161+
registerScriptlet(spoofCSS, {
162+
name: 'spoof-css.js',
163+
dependencies: [
164+
safeSelf,
165+
],
166+
});

0 commit comments

Comments
 (0)