@@ -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 ,
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