Skip to content

Commit 9278ddc

Browse files
Screen size FP protection
1 parent 50fd679 commit 9278ddc

File tree

11 files changed

+1377
-552
lines changed

11 files changed

+1377
-552
lines changed

Sources/ContentScopeScripts/dist/contentScope.js

Lines changed: 156 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3892,112 +3892,145 @@
38923892

38933893
const featureName = 'fingerprinting-screen-size';
38943894

3895-
/**
3896-
* normalize window dimensions, if more than one monitor is in play.
3897-
* X/Y values are set in the browser based on distance to the main monitor top or left, which
3898-
* can mean second or more monitors have very large or negative values. This function maps a given
3899-
* given coordinate value to the proper place on the main screen.
3900-
*/
3901-
function normalizeWindowDimension (value, targetDimension) {
3902-
if (value > targetDimension) {
3903-
return value % targetDimension
3904-
}
3905-
if (value < 0) {
3906-
return targetDimension + value
3907-
}
3908-
return value
3909-
}
3910-
3911-
function setWindowPropertyValue (property, value) {
3895+
function setWindowScreenPropertyValue (property, value) {
39123896
// Here we don't update the prototype getter because the values are updated dynamically
39133897
try {
3914-
defineProperty(globalThis, property, {
3898+
defineProperty(window.screen, property, {
39153899
get: () => value,
39163900
set: () => {},
39173901
configurable: true
39183902
});
39193903
} catch (e) {}
39203904
}
39213905

3922-
const origPropertyValues = {};
3923-
3924-
/**
3925-
* Fix window dimensions. The extension runs in a different JS context than the
3926-
* page, so we can inject the correct screen values as the window is resized,
3927-
* ensuring that no information is leaked as the dimensions change, but also that the
3928-
* values change correctly for valid use cases.
3929-
*/
39303906
function setWindowDimensions () {
39313907
try {
3932-
const window = globalThis;
3933-
const top = globalThis.top;
3934-
3935-
const normalizedY = normalizeWindowDimension(window.screenY, window.screen.height);
3936-
const normalizedX = normalizeWindowDimension(window.screenX, window.screen.width);
3937-
if (normalizedY <= origPropertyValues.availTop) {
3938-
setWindowPropertyValue('screenY', 0);
3939-
setWindowPropertyValue('screenTop', 0);
3940-
} else {
3941-
setWindowPropertyValue('screenY', normalizedY);
3942-
setWindowPropertyValue('screenTop', normalizedY);
3943-
}
3944-
3945-
if (top.window.outerHeight >= origPropertyValues.availHeight - 1) {
3946-
setWindowPropertyValue('outerHeight', top.window.screen.height);
3947-
} else {
3948-
try {
3949-
setWindowPropertyValue('outerHeight', top.window.outerHeight);
3950-
} catch (e) {
3951-
// top not accessible to certain iFrames, so ignore.
3952-
}
3953-
}
3954-
3955-
if (normalizedX <= origPropertyValues.availLeft) {
3956-
setWindowPropertyValue('screenX', 0);
3957-
setWindowPropertyValue('screenLeft', 0);
3958-
} else {
3959-
setWindowPropertyValue('screenX', normalizedX);
3960-
setWindowPropertyValue('screenLeft', normalizedX);
3961-
}
3908+
setWindowScreenPropertyValue('availWidth', window.innerWidth);
3909+
setWindowScreenPropertyValue('availHeight', window.innerHeight);
3910+
setWindowScreenPropertyValue('width', window.innerWidth);
3911+
setWindowScreenPropertyValue('height', window.innerHeight);
3912+
} catch (e) {}
3913+
}
39623914

3963-
if (top.window.outerWidth >= origPropertyValues.availWidth - 1) {
3964-
setWindowPropertyValue('outerWidth', top.window.screen.width);
3965-
} else {
3966-
try {
3967-
setWindowPropertyValue('outerWidth', top.window.outerWidth);
3968-
} catch (e) {
3969-
// top not accessible to certain iFrames, so ignore.
3970-
}
3971-
}
3972-
} catch (e) {
3973-
// in a cross domain iFrame, top.window is not accessible.
3974-
}
3915+
/**
3916+
* Since we're spoofing that the browser window is in the upper left corner
3917+
* of the screen (i.e., 0,0), we should map any offsets provided to window.open() back
3918+
* into the *real* screen coordinates. From the websites point of view, the browser's inner window
3919+
* is the user's screen. Without this correction, any pop-ups set using relative dimensions will
3920+
* be in the wrong position and possibly on the wrong monitor (if the user has multiple monitors).
3921+
*/
3922+
function correctWindowOpenOffset (windowFeatures, offsetName, realScreenOffset) {
3923+
const dimensionRegex = new RegExp(`\\b${offsetName}\\s*=\\s*(?<offset>-?\\d+(\\.\\d+)?)\\b`, 'i');
3924+
const matches = windowFeatures.match(dimensionRegex);
3925+
3926+
if (matches && matches.groups && matches.groups.offset) {
3927+
const newOffset = Number(matches.groups.offset) + realScreenOffset;
3928+
windowFeatures = windowFeatures.replace(
3929+
dimensionRegex,
3930+
`${offsetName}=${newOffset}`
3931+
);
3932+
}
3933+
return windowFeatures
39753934
}
39763935

39773936
function init$7 (args) {
3978-
const Screen = globalThis.Screen;
3979-
const screen = globalThis.screen;
3937+
// Storing the original getters for screenX and screenY allow us to
3938+
// query the real screen offsets after overwriting. This is needed to
3939+
// accurately position window.open pop-ups based on the real window
3940+
// location (which may change during a page visit).
3941+
const origPropDesc = {};
3942+
try {
3943+
origPropDesc.screenX = Object.getOwnPropertyDescriptor(window, 'screenX').get;
3944+
origPropDesc.screenY = Object.getOwnPropertyDescriptor(window, 'screenY').get;
3945+
} catch (e) {}
39803946

3981-
origPropertyValues.availTop = overrideProperty('availTop', {
3947+
// Always return that the window is in the upper left of the display
3948+
overrideProperty('screenX', {
3949+
object: window,
3950+
origValue: window.screenX,
3951+
targetValue: 0
3952+
});
3953+
overrideProperty('screenY', {
3954+
object: window,
3955+
origValue: window.screenY,
3956+
targetValue: 0
3957+
});
3958+
overrideProperty('screenLeft', {
3959+
object: window,
3960+
origValue: window.screenLeft,
3961+
targetValue: 0
3962+
});
3963+
overrideProperty('screenTop', {
3964+
object: window,
3965+
origValue: window.screenTop,
3966+
targetValue: 0
3967+
});
3968+
overrideProperty('availTop', {
39823969
object: Screen.prototype,
39833970
origValue: screen.availTop,
3984-
targetValue: getFeatureAttr(featureName, args, 'availTop', 0)
3971+
targetValue: 0
39853972
});
3986-
origPropertyValues.availLeft = overrideProperty('availLeft', {
3973+
overrideProperty('availLeft', {
39873974
object: Screen.prototype,
39883975
origValue: screen.availLeft,
3989-
targetValue: getFeatureAttr(featureName, args, 'availLeft', 0)
3976+
targetValue: 0
39903977
});
3991-
origPropertyValues.availWidth = overrideProperty('availWidth', {
3978+
if (Object.prototype.hasOwnProperty.call(Screen.prototype, 'left')) { // Firefox only
3979+
overrideProperty('left', {
3980+
object: Screen.prototype,
3981+
origValue: screen.left,
3982+
targetValue: 0
3983+
});
3984+
}
3985+
if (Object.prototype.hasOwnProperty.call(Screen.prototype, 'top')) { // Firefox only
3986+
overrideProperty('top', {
3987+
object: Screen.prototype,
3988+
origValue: screen.top,
3989+
targetValue: 0
3990+
});
3991+
}
3992+
if (Object.prototype.hasOwnProperty.call(window, 'mozInnerScreenX')) { // Firefox only
3993+
overrideProperty('mozInnerScreenX', {
3994+
object: window,
3995+
origValue: window.mozInnerScreenX,
3996+
targetValue: 0
3997+
});
3998+
}
3999+
if (Object.prototype.hasOwnProperty.call(window, 'mozInnerScreenY')) { // Firefox only
4000+
overrideProperty('mozInnerScreenY', {
4001+
object: window,
4002+
origValue: window.mozInnerScreenY,
4003+
targetValue: 0
4004+
});
4005+
}
4006+
4007+
// Reveal only the size of the current window to content
4008+
// Since innerHeight and innerWidth are dynamic based on window size we need to
4009+
// update these values dynamically. However, it's still important to override
4010+
// the prototypes to prevent a malicious website from being able to discover
4011+
// the true size via `delete window.screen.width`, etc.
4012+
// See: https://palant.info/2020/12/10/how-anti-fingerprinting-extensions-tend-to-make-fingerprinting-easier/
4013+
overrideProperty('availWidth', {
39924014
object: Screen.prototype,
39934015
origValue: screen.availWidth,
3994-
targetValue: screen.width
4016+
targetValue: window.innerWidth
39954017
});
3996-
origPropertyValues.availHeight = overrideProperty('availHeight', {
4018+
overrideProperty('availHeight', {
39974019
object: Screen.prototype,
39984020
origValue: screen.availHeight,
3999-
targetValue: screen.height
4021+
targetValue: window.innerHeight
4022+
});
4023+
overrideProperty('width', {
4024+
object: Screen.prototype,
4025+
origValue: screen.width,
4026+
targetValue: window.innerWidth
40004027
});
4028+
overrideProperty('height', {
4029+
object: Screen.prototype,
4030+
origValue: screen.height,
4031+
targetValue: window.innerHeight
4032+
});
4033+
40014034
overrideProperty('colorDepth', {
40024035
object: Screen.prototype,
40034036
origValue: screen.colorDepth,
@@ -4009,14 +4042,58 @@
40094042
targetValue: getFeatureAttr(featureName, args, 'pixelDepth', 24)
40104043
});
40114044

4045+
// Override window.open to allow us to re-position popups based on the
4046+
// real window location.
4047+
const windowOpenProxy = new DDGProxy(window, 'open', {
4048+
apply (target, thisArg, args) {
4049+
if (args.length < 3) {
4050+
return DDGReflect.apply(target, thisArg, args)
4051+
}
4052+
try {
4053+
let windowFeatures = args[2];
4054+
const screenY = origPropDesc.screenY ? origPropDesc.screenY.call(window) : 0;
4055+
if (screenY !== 0) {
4056+
windowFeatures = correctWindowOpenOffset(
4057+
windowFeatures,
4058+
'top',
4059+
screenY
4060+
);
4061+
windowFeatures = correctWindowOpenOffset(
4062+
windowFeatures,
4063+
'screeny',
4064+
screenY
4065+
);
4066+
}
4067+
const screenX = origPropDesc.screenX ? origPropDesc.screenX.call(window) : 0;
4068+
if (screenX !== 0) {
4069+
windowFeatures = correctWindowOpenOffset(
4070+
windowFeatures,
4071+
'left',
4072+
screenX
4073+
);
4074+
windowFeatures = correctWindowOpenOffset(
4075+
windowFeatures,
4076+
'screenx',
4077+
screenX
4078+
);
4079+
}
4080+
args[2] = windowFeatures;
4081+
} catch (e) {
4082+
// Ignore all errors
4083+
}
4084+
return DDGReflect.apply(target, thisArg, args)
4085+
}
4086+
});
4087+
windowOpenProxy.overload();
4088+
40124089
window.addEventListener('resize', function () {
40134090
setWindowDimensions();
40144091
});
4015-
setWindowDimensions();
40164092
}
40174093

40184094
var fingerprintingScreenSize = /*#__PURE__*/Object.freeze({
40194095
__proto__: null,
4096+
correctWindowOpenOffset: correctWindowOpenOffset,
40204097
init: init$7
40214098
});
40224099

0 commit comments

Comments
 (0)