@@ -297,283 +297,145 @@ let sync_metrics = {
297297 } ) ( ) ,
298298
299299 fingerprinting : ( ( ) => {
300- // These are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs
301- const fingerprintingAPIs = [
300+ // The APIs are determined by looking at the tests in https://github.com/fingerprintjs/fingerprintjs and https://amiunique.org/fingerprint
301+ // Grouped by unique API to improve diversity metric reliability
302+ const fingerprintingAPIs = {
302303 // Payment APIs
303- 'ApplePaySession\.canMakePayments' ,
304+ 'payment' : ' ApplePaySession\.canMakePayments',
304305
305306 // User Agent and Platform fingerprinting
306- 'navigator\.userAgent' ,
307- 'navigator\.platform' ,
308- 'navigator\.oscpu' ,
309- 'navigator\.vendor' ,
310- 'navigator\.vendorSub' ,
311- 'navigator\.product' ,
312- 'navigator\.productSub' ,
313- 'navigator\.buildID' ,
307+ 'navigator_userAgent' : 'navigator\.userAgent' ,
308+ 'navigator_platform' : 'navigator\.platform' ,
309+ 'navigator_oscpu' : 'navigator\.oscpu' ,
310+ 'navigator_vendor' : 'navigator\.(vendor|vendorSub)' ,
311+ 'navigator_product' : 'navigator\.(product|productSub)' ,
312+ 'navigator_buildID' : 'navigator\.buildID' ,
314313
315314 // Audio fingerprinting
316- 'createAnalyser' ,
317- 'createOscillator' ,
318- 'createScriptProcessor' ,
319- 'getChannelData' ,
320- 'getFloatFrequencyData' ,
321- 'getByteFrequencyData' ,
322- 'OscillatorNode' ,
323- 'AudioContext' ,
324- 'webkitAudioContext' ,
325- 'AnalyserNode' ,
326- 'createDynamicsCompressor' ,
327- 'channelCount' ,
328- 'channelCountMode' ,
329- 'channelInterpretation' ,
330- 'fftSize' ,
331- 'frequencyBinCount' ,
332- 'maxDecibels' ,
333- 'minDecibels' ,
334- 'smoothingTimeConstant' ,
335- 'sampleRate' ,
315+ 'audio_context' : '(AudioContext|webkitAudioContext)' ,
316+ 'audio_analysis' : '(createAnalyser|AnalyserNode|getFloatFrequencyData|getByteFrequencyData|fftSize|frequencyBinCount|maxDecibels|minDecibels|smoothingTimeConstant)' ,
317+ 'audio_processing' : '(createOscillator|OscillatorNode|createScriptProcessor|createDynamicsCompressor)' ,
318+ 'audio_data' : '(getChannelData|channelCount|channelCountMode|channelInterpretation|sampleRate)' ,
336319
337320 // Canvas fingerprinting
338- 'canvas\.getContext' ,
339- 'canvas\.toDataURL' ,
340- 'canvasRenderingContext2D\.fillText' ,
341- 'canvasRenderingContext2D\.strokeText' ,
342- 'canvasRenderingContext2D\.getImageData' ,
343- 'HTMLCanvasElement\.toBlob' ,
344- 'getContext\(.*2d.*\)' ,
345- 'getContext\(.*webgl.*\)' ,
321+ 'canvas_context' : 'canvas\.getContext|getContext\(.*(2d|webgl).*\)' ,
322+ 'canvas_rendering' : '(canvasRenderingContext2D\.(fillText|strokeText|getImageData)|canvas\.toDataURL|HTMLCanvasElement\.toBlob)' ,
346323
347324 // CSS media queries for fingerprinting
348- '@media.*color-gamut' ,
349- '@media.*prefers-contrast' ,
350- '@media.*forced-colors' ,
351- '@media.*dynamic-range' ,
352- '@media.*inverted-colors' ,
353- '@media.*min-monochrome' ,
354- '@media.*max-monochrome' ,
355- '@media.*prefers-reduced-motion' ,
356- '@media.*prefers-reduced-transparency' ,
325+ 'css_media_queries' : '@media.*(color-gamut|prefers-contrast|forced-colors|dynamic-range|inverted-colors|min-monochrome|max-monochrome|prefers-reduced-motion|prefers-reduced-transparency)' ,
357326
358327 // Hardware fingerprinting
359- 'cpuClass' ,
360- 'deviceMemory' ,
361- 'hardwareConcurrency' ,
362- 'maxTouchPoints' ,
363- 'ontouchstart' ,
328+ 'hardware_info' : '(cpuClass|deviceMemory|hardwareConcurrency|maxTouchPoints)' ,
329+
330+ // Touch capabilities
331+ 'touch_capabilities' : '(ontouchstart|TouchEvent|createTouch|createTouchList)' ,
364332
365333 // Storage APIs (potential fingerprinting)
366- 'indexedDB' ,
367- 'localStorage' ,
368- 'sessionStorage' ,
369- 'openDatabase' ,
334+ 'storage_apis' : '(indexedDB|localStorage|sessionStorage|openDatabase)' ,
370335
371336 // PDF and plugins
372- 'pdfViewerEnabled' ,
373- 'navigator\.plugins' ,
374- 'navigator\.mimeTypes' ,
375- 'Plugin\s' ,
376- 'MimeType' ,
337+ 'plugins' : '(pdfViewerEnabled|navigator\.(plugins|mimeTypes)|Plugin\s|MimeType)' ,
377338
378339 // Attribution and tracking
379- 'attributionSourceId' ,
340+ 'attribution' : ' attributionSourceId',
380341
381342 // Time zone and language fingerprinting
382- 'resolvedOptions\(\)\.timeZone' ,
383- 'getTimezoneOffset' ,
384- 'navigator\.language' ,
385- 'navigator\.languages' ,
386- 'Intl\.DateTimeFormat' ,
387- 'Intl\.Collator' ,
343+ 'timezone' : '(resolvedOptions\(\)\.timeZone|getTimezoneOffset)' ,
344+ 'language' : '(navigator\.(language|languages)|Intl\.(DateTimeFormat|Collator))' ,
388345
389346 // WebGL fingerprinting
390- 'vendorUnmasked' ,
391- 'rendererUnmasked' ,
392- 'shadingLanguageVersion' ,
393- 'WEBGL_debug_renderer_info' ,
394- 'getShaderPrecisionFormat' ,
395- 'WebGLRenderingContext' ,
396- 'getParameter' ,
397- 'getSupportedExtensions' ,
398- 'getExtension' ,
399- 'VENDOR' ,
400- 'RENDERER' ,
401- 'VERSION' ,
402- 'SHADING_LANGUAGE_VERSION' ,
347+ 'webgl_info' : '(vendorUnmasked|rendererUnmasked|shadingLanguageVersion|WEBGL_debug_renderer_info|WebGLRenderingContext)' ,
348+ 'webgl_params' : '(getShaderPrecisionFormat|getParameter|getSupportedExtensions|getExtension|VENDOR|RENDERER|VERSION|SHADING_LANGUAGE_VERSION)' ,
403349
404350 // Screen properties
405- 'availWidth' ,
406- 'availHeight' ,
407- 'screen\.width' ,
408- 'screen\.height' ,
409- 'screen\.colorDepth' ,
410- 'screen\.pixelDepth' ,
411- 'screen\.availTop' ,
412- 'screen\.availLeft' ,
413- 'outerWidth' ,
414- 'outerHeight' ,
415- 'innerWidth' ,
416- 'innerHeight' ,
417- 'devicePixelRatio' ,
351+ 'screen_properties' : '(availWidth|availHeight)|screen\.(width|height|colorDepth|pixelDepth|availTop|availLeft)|(outerWidth|outerHeight|innerWidth|innerHeight)|devicePixelRatio' ,
418352
419353 // Window and browser chrome fingerprinting
420- 'locationbar' ,
421- 'menubar' ,
422- 'personalbar' ,
423- 'scrollbars' ,
424- 'statusbar' ,
425- 'toolbar' ,
426- 'history\.length' ,
427-
428- // Geolocation API: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API
429- 'getCurrentPosition' ,
430- 'watchPosition' ,
431- 'navigator\.geolocation' ,
354+ 'browser_chrome' : '(locationbar|menubar|personalbar|scrollbars|statusbar|toolbar|history\.length)' ,
355+
356+ // Geolocation API
357+ 'geolocation' : '(getCurrentPosition|watchPosition|navigator\.geolocation)' ,
432358
433359 // Media devices and capabilities
434- 'enumerateDevices' ,
435- 'getUserMedia' ,
436- 'getDisplayMedia' ,
437- 'navigator\.mediaDevices' ,
438- 'canPlayType' ,
439- 'HTMLVideoElement\.canPlayType' ,
440- 'HTMLAudioElement\.canPlayType' ,
360+ 'media_devices' : '(enumerateDevices|getUserMedia|getDisplayMedia|navigator\.mediaDevices)' ,
361+ 'media_capabilities' : '(canPlayType|HTMLVideoElement\.canPlayType|HTMLAudioElement\.canPlayType)' ,
441362
442363 // Permissions API
443- 'navigator\.permissions' ,
444- 'permissions\.query' ,
364+ 'permissions' : '(navigator\.permissions|permissions\.query)' ,
445365
446366 // Battery API
447- 'navigator\.battery' ,
448- 'navigator\.getBattery' ,
449- 'charging' ,
450- 'chargingTime' ,
451- 'dischargingTime' ,
452- // 'level',
367+ 'battery' : '(navigator\.(battery|getBattery)|charging|chargingTime|dischargingTime)' ,
453368
454369 // Connection API
455- 'navigator\.connection' ,
456- 'navigator\.mozConnection' ,
457- 'navigator\.webkitConnection' ,
458- 'downlink' ,
459- 'effectiveType' ,
460- // 'rtt',
461- // 'saveData',
370+ 'connection' : '(navigator\.(connection|mozConnection|webkitConnection)|downlink|effectiveType)' ,
462371
463372 // Sensors APIs
464- 'Accelerometer' ,
465- 'Gyroscope' ,
466- 'LinearAccelerationSensor' ,
467- 'AbsoluteOrientationSensor' ,
468- 'RelativeOrientationSensor' ,
469- 'AmbientLightSensor' ,
470- 'ProximitySensor' ,
471-
472- // Touch and input
473- 'TouchEvent' ,
474- 'createTouch' ,
475- 'createTouchList' ,
373+ 'sensors' : '(Accelerometer|Gyroscope|LinearAccelerationSensor|AbsoluteOrientationSensor|RelativeOrientationSensor|AmbientLightSensor|ProximitySensor)' ,
476374
477375 // Font detection
478- 'document\.fonts' ,
479- 'FontFace' ,
376+ 'fonts' : '(document\.fonts|FontFace)' ,
480377
481378 // Do Not Track
482- 'navigator\.doNotTrack' ,
483- 'window\.doNotTrack' ,
379+ 'do_not_track' : '(navigator\.doNotTrack|window\.doNotTrack)' ,
484380
485381 // Cookie detection
486- 'navigator\.cookieEnabled' ,
382+ 'cookies' : ' navigator\.cookieEnabled',
487383
488384 // Java detection
489- 'navigator\.javaEnabled' ,
385+ 'java' : 'navigator\.javaEnabled' ,
386+
387+ // WebRTC
388+ 'webrtc_peer' : '(RTCPeerConnection|webkitRTCPeerConnection|mozRTCPeerConnection)' ,
389+ 'webrtc_data' : '(RTCDataChannel|createDataChannel)' ,
490390
491- // Additional modern fingerprinting vectors
492- 'RTCPeerConnection' ,
493- 'webkitRTCPeerConnection' ,
494- 'mozRTCPeerConnection' ,
495- 'performance\.memory' ,
496- 'performance\.timing' ,
497- 'Notification\.permission' ,
391+ // Performance APIs
392+ 'performance' : '(performance\.(memory|timing))' ,
393+
394+ // Notifications
395+ 'notifications' : 'Notification\.permission' ,
498396
499397 // Keyboard layout detection
500- 'KeyboardLayoutMap' ,
501- 'navigator\.keyboard' ,
502- 'getLayoutMap' ,
398+ 'keyboard' : '(KeyboardLayoutMap|navigator\.keyboard|getLayoutMap)' ,
503399
504400 // Gamepad API
505- 'navigator\.getGamepads' ,
506- 'GamepadEvent' ,
401+ 'gamepad' : '(navigator\.getGamepads|GamepadEvent)' ,
507402
508403 // Storage quota
509- 'navigator\.storage' ,
510- 'navigator\.webkitTemporaryStorage' ,
511- 'navigator\.webkitPersistentStorage' ,
512- 'estimate' ,
404+ 'storage_quota' : '(navigator\.(storage|webkitTemporaryStorage|webkitPersistentStorage)|estimate)' ,
513405
514406 // Speech APIs
515- 'SpeechSynthesis' ,
516- 'SpeechRecognition' ,
517-
518- // WebRTC Data Channel
519- 'RTCDataChannel' ,
520- 'createDataChannel' ,
407+ 'speech' : '(SpeechSynthesis|SpeechRecognition)' ,
521408
522409 // Crypto subtle fingerprinting
523- 'crypto\.subtle' ,
524- 'SubtleCrypto' ,
410+ 'crypto' : '(crypto\.subtle|SubtleCrypto)' ,
525411
526412 // Worker capabilities
527- 'Worker' ,
528- 'SharedWorker' ,
529- 'ServiceWorker'
530- ] ;
413+ 'workers' : '(Worker|SharedWorker|ServiceWorker)'
414+ } ;
531415
532416 // Pre-compile regexes - handle already escaped patterns
533- const compiledRegexes = fingerprintingAPIs . map ( api => ( {
534- api,
535- regex : new RegExp ( api , 'gi' )
417+ const compiledRegexes = Object . entries ( fingerprintingAPIs ) . map ( ( [ apiName , pattern ] ) => ( {
418+ api : apiName ,
419+ regex : new RegExp ( pattern , 'gi' )
536420 } ) ) ;
537421 let likelyFingerprintingScripts = [ ] ;
538422
539423 response_bodies . forEach ( req => {
540424 try {
541425 let detectedApis = [ ] ;
542- let totalOccurrences = 0 ;
543- let body = req . response_body ;
544-
545- // Validate response body
546- if ( ! body || typeof body !== 'string' ) {
547- return ; // Skip invalid response bodies
548- }
549426
550427 compiledRegexes . forEach ( ( { api, regex } ) => {
551428 try {
552- // Reset regex index for global regex
553- regex . lastIndex = 0 ;
554-
555- // Use a more memory-efficient counting approach
556- let match ;
557- let matches = 0 ;
558- while ( ( match = regex . exec ( body ) ) !== null ) {
559- matches ++ ;
560- // Prevent infinite loops on zero-length matches
561- if ( match . index === regex . lastIndex ) {
562- regex . lastIndex ++ ;
563- }
564- }
565-
566- if ( matches > 0 ) {
429+ if ( regex . test ( req . response_body ) ) {
567430 detectedApis . push ( api ) ;
568- totalOccurrences += matches ;
569431 }
570432 } catch ( regexError ) {
571433 // Skip this API on regex error - avoid console.warn in WebPageTest
572434 }
573435 } ) ;
574436
575437 // Track scripts with significant fingerprinting API usage
576- if ( detectedApis . length >= 5 ) {
438+ if ( detectedApis . length >= 5 ) {
577439 likelyFingerprintingScripts . push ( {
578440 url : req . url ,
579441 detectedApis
0 commit comments