PageSourceSearch

https://www.wabcmusicradio.com/wp-content/plugins/wpb-super-gallery/public/inc/js/fingerprint2.js?ver=2.0.5

js wabcmusicradio.com collected 2026-09-27 04:39:31 UTC 60,652 bytes, 1,411 lines download raw bytes

vendor: 6,659 bytes, lines 1-167
1/*
2* Fingerprintjs2 2.0.5 - Modern & flexible browser fingerprint library v2
3* https://github.com/Valve/fingerprintjs2
4* Copyright (c) 2015 Valentin Vasilyev ([email protected])
5* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6*
7* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
8* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
9* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
10* ARE DISCLAIMED. IN NO EVENT SHALL VALENTIN VASILYEV BE LIABLE FOR ANY
11* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
12* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
13* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
14* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
15* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
16* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17*/
18/* global define */
19(function (name, context, definition) {
20    'use strict'
21    if (typeof window !== 'undefined' && typeof define === 'function' && define.amd) { define(definition) } else if (typeof module !== 'undefined' && module.exports) { module.exports = definition() } else if (context.exports) { context.exports = definition() } else { context[name] = definition() }
22  })('Fingerprint2', this, function () {
23    'use strict'
24  
25  /// MurmurHash3 related functions
26  
27  //
28  // Given two 64bit ints (as an array of two 32bit ints) returns the two
29  // added together as a 64bit int (as an array of two 32bit ints).
30  //
31    var x64Add = function (m, n) {
32      m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff]
33      n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff]
34      var o = [0, 0, 0, 0]
35      o[3] += m[3] + n[3]
36      o[2] += o[3] >>> 16
37      o[3] &= 0xffff
38      o[2] += m[2] + n[2]
39      o[1] += o[2] >>> 16
40      o[2] &= 0xffff
41      o[1] += m[1] + n[1]
42      o[0] += o[1] >>> 16
43      o[1] &= 0xffff
44      o[0] += m[0] + n[0]
45      o[0] &= 0xffff
46      return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]]
47    }
48  
49  //
50  // Given two 64bit ints (as an array of two 32bit ints) returns the two
51  // multiplied together as a 64bit int (as an array of two 32bit ints).
52  //
53    var x64Multiply = function (m, n) {
54      m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff]
55      n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff]
56      var o = [0, 0, 0, 0]
57      o[3] += m[3] * n[3]
58      o[2] += o[3] >>> 16
59      o[3] &= 0xffff
60      o[2] += m[2] * n[3]
61      o[1] += o[2] >>> 16
62      o[2] &= 0xffff
63      o[2] += m[3] * n[2]
64      o[1] += o[2] >>> 16
65      o[2] &= 0xffff
66      o[1] += m[1] * n[3]
67      o[0] += o[1] >>> 16
68      o[1] &= 0xffff
69      o[1] += m[2] * n[2]
70      o[0] += o[1] >>> 16
71      o[1] &= 0xffff
72      o[1] += m[3] * n[1]
73      o[0] += o[1] >>> 16
74      o[1] &= 0xffff
75      o[0] += (m[0] * n[3]) + (m[1] * n[2]) + (m[2] * n[1]) + (m[3] * n[0])
76      o[0] &= 0xffff
77      return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]]
78    }
79  //
80  // Given a 64bit int (as an array of two 32bit ints) and an int
81  // representing a number of bit positions, returns the 64bit int (as an
82  // array of two 32bit ints) rotated left by that number of positions.
83  //
84    var x64Rotl = function (m, n) {
85      n %= 64
86      if (n === 32) {
87        return [m[1], m[0]]
88      } else if (n < 32) {
89        return [(m[0] << n) | (m[1] >>> (32 - n)), (m[1] << n) | (m[0] >>> (32 - n))]
90      } else {
91        n -= 32
92        return [(m[1] << n) | (m[0] >>> (32 - n)), (m[0] << n) | (m[1] >>> (32 - n))]
93      }
94    }
95  //
96  // Given a 64bit int (as an array of two 32bit ints) and an int
97  // representing a number of bit positions, returns the 64bit int (as an
98  // array of two 32bit ints) shifted left by that number of positions.
99  //
100    var x64LeftShift = function (m, n) {
101      n %= 64
102      if (n === 0) {
103        return m
104      } else if (n < 32) {
105        return [(m[0] << n) | (m[1] >>> (32 - n)), m[1] << n]
106      } else {
107        return [m[1] << (n - 32), 0]
108      }
109    }
110  //
111  // Given two 64bit ints (as an array of two 32bit ints) returns the two
112  // xored together as a 64bit int (as an array of two 32bit ints).
113  //
114    var x64Xor = function (m, n) {
115      return [m[0] ^ n[0], m[1] ^ n[1]]
116    }
117  //
118  // Given a block, returns murmurHash3's final x64 mix of that block.
119  // (`[0, h[0] >>> 1]` is a 33 bit unsigned right shift. This is the
120  // only place where we need to right shift 64bit ints.)
121  //
122    var x64Fmix = function (h) {
123      h = x64Xor(h, [0, h[0] >>> 1])
124      h = x64Multiply(h, [0xff51afd7, 0xed558ccd])
125      h = x64Xor(h, [0, h[0] >>> 1])
126      h = x64Multiply(h, [0xc4ceb9fe, 0x1a85ec53])
127      h = x64Xor(h, [0, h[0] >>> 1])
128      return h
129    }
130  
131  //
132  // Given a string and an optional seed as an int, returns a 128 bit
133  // hash using the x64 flavor of MurmurHash3, as an unsigned hex.
134  //
135    var x64hash128 = function (key, seed) {
136      key = key || ''
137      seed = seed || 0
138      var remainder = key.length % 16
139      var bytes = key.length - remainder
140      var h1 = [0, seed]
141      var h2 = [0, seed]
142      var k1 = [0, 0]
143      var k2 = [0, 0]
144      var c1 = [0x87c37b91, 0x114253d5]
145      var c2 = [0x4cf5ad43, 0x2745937f]
146      for (var i = 0; i < bytes; i = i + 16) {
147        k1 = [((key.charCodeAt(i + 4) & 0xff)) | ((key.charCodeAt(i + 5) & 0xff) << 8) | ((key.charCodeAt(i + 6) & 0xff) << 16) | ((key.charCodeAt(i + 7) & 0xff) << 24), ((key.charCodeAt(i) & 0xff)) | ((key.charCodeAt(i + 1) & 0xff) << 8) | ((key.charCodeAt(i + 2) & 0xff) << 16) | ((key.charCodeAt(i + 3) & 0xff) << 24)]
148        k2 = [((key.charCodeAt(i + 12) & 0xff)) | ((key.charCodeAt(i + 13) & 0xff) << 8) | ((key.charCodeAt(i + 14) & 0xff) << 16) | ((key.charCodeAt(i + 15) & 0xff) << 24), ((key.charCodeAt(i + 8) & 0xff)) | ((key.charCodeAt(i + 9) & 0xff) << 8) | ((key.charCodeAt(i + 10) & 0xff) << 16) | ((key.charCodeAt(i + 11) & 0xff) << 24)]
149        k1 = x64Multiply(k1, c1)
150        k1 = x64Rotl(k1, 31)
151        k1 = x64Multiply(k1, c2)
152        h1 = x64Xor(h1, k1)
153        h1 = x64Rotl(h1, 27)
154        h1 = x64Add(h1, h2)
155        h1 = x64Add(x64Multiply(h1, [0, 5]), [0, 0x52dce729])
156        k2 = x64Multiply(k2, c2)
157        k2 = x64Rotl(k2, 33)
158        k2 = x64Multiply(k2, c1)
159        h2 = x64Xor(h2, k2)
160        h2 = x64Rotl(h2, 31)
161        h2 = x64Add(h2, h1)
162        h2 = x64Add(x64Multiply(h2, [0, 5]), [0, 0x38495ab5])
163      }
164      k1 = [0, 0]
165      k2 = [0, 0]
166      switch (remainder) {
167        case 15:
168          k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 14)], 48))
169        // fallthrough
170        case 14:
171          k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 13)], 40))
172        // fallthrough
173        case 13:
174          k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 12)], 32))
175        // fallthrough
176        case 12:
177          k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 11)], 24))
178        // fallthrough
179        case 11:
180          k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 10)], 16))
181        // fallthrough
182        case 10:
183          k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 9)], 8))
184        // fallthrough
185        case 9:
186          k2 = x64Xor(k2, [0, key.charCodeAt(i + 8)])
187          k2 = x64Multiply(k2, c2)
188          k2 = x64Rotl(k2, 33)
189          k2 = x64Multiply(k2, c1)
190          h2 = x64Xor(h2, k2)
191        // fallthrough
192        case 8:
193          k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 7)], 56))
194        // fallthrough
195        case 7:
196          k1 = x64Xor(k1, x64LeftShift([0, key.charCo
vendor: 15,720 bytes, lines 196-557
196deAt(i + 6)], 48))
197        // fallthrough
198        case 6:
199          k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 5)], 40))
200        // fallthrough
201        case 5:
202          k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 4)], 32))
203        // fallthrough
204        case 4:
205          k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 3)], 24))
206        // fallthrough
207        case 3:
208          k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 2)], 16))
209        // fallthrough
210        case 2:
211          k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 1)], 8))
212        // fallthrough
213        case 1:
214          k1 = x64Xor(k1, [0, key.charCodeAt(i)])
215          k1 = x64Multiply(k1, c1)
216          k1 = x64Rotl(k1, 31)
217          k1 = x64Multiply(k1, c2)
218          h1 = x64Xor(h1, k1)
219        // fallthrough
220      }
221      h1 = x64Xor(h1, [0, key.length])
222      h2 = x64Xor(h2, [0, key.length])
223      h1 = x64Add(h1, h2)
224      h2 = x64Add(h2, h1)
225      h1 = x64Fmix(h1)
226      h2 = x64Fmix(h2)
227      h1 = x64Add(h1, h2)
228      h2 = x64Add(h2, h1)
229      return ('00000000' + (h1[0] >>> 0).toString(16)).slice(-8) + ('00000000' + (h1[1] >>> 0).toString(16)).slice(-8) + ('00000000' + (h2[0] >>> 0).toString(16)).slice(-8) + ('00000000' + (h2[1] >>> 0).toString(16)).slice(-8)
230    }
231  
232    var defaultOptions = {
233      preprocessor: null,
234      audio: {
235        timeout: 1000,
236          // On iOS 11, audio context can only be used in response to user interaction.
237          // We require users to explicitly enable audio fingerprinting on iOS 11.
238          // See https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088
239        excludeIOS11: true
240      },
241      fonts: {
242        swfContainerId: 'fingerprintjs2',
243        swfPath: 'flash/compiled/FontList.swf',
244        userDefinedFonts: [],
245        extendedJsFonts: false
246      },
247      screen: {
248         // To ensure consistent fingerprints when users rotate their mobile devices
249        detectScreenOrientation: true
250      },
251      plugins: {
252        sortPluginsFor: [/palemoon/i],
253        excludeIE: false
254      },
255      extraComponents: [],
256      excludes: {
257        // Unreliable on Windows, see https://github.com/Valve/fingerprintjs2/issues/375
258        'enumerateDevices': true,
259        // devicePixelRatio depends on browser zoom, and it's impossible to detect browser zoom
260        'pixelRatio': true,
261        // DNT depends on incognito mode for some browsers (Chrome) and it's impossible to detect incognito mode
262        'doNotTrack': true,
263        // uses js fonts already
264        'fontsFlash': true
265      },
266      NOT_AVAILABLE: 'not available',
267      ERROR: 'error',
268      EXCLUDED: 'excluded'
269    }
270  
271    var each = function (obj, iterator) {
272      if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
273        obj.forEach(iterator)
274      } else if (obj.length === +obj.length) {
275        for (var i = 0, l = obj.length; i < l; i++) {
276          iterator(obj[i], i, obj)
277        }
278      } else {
279        for (var key in obj) {
280          if (obj.hasOwnProperty(key)) {
281            iterator(obj[key], key, obj)
282          }
283        }
284      }
285    }
286  
287    var map = function (obj, iterator) {
288      var results = []
289      // Not using strict equality so that this acts as a
290      // shortcut to checking for `null` and `undefined`.
291      if (obj == null) {
292        return results
293      }
294      if (Array.prototype.map && obj.map === Array.prototype.map) { return obj.map(iterator) }
295      each(obj, function (value, index, list) {
296        results.push(iterator(value, index, list))
297      })
298      return results
299    }
300  
301    var extendSoft = function (target, source) {
302      if (source == null) { return target }
303      var value
304      var key
305      for (key in source) {
306        value = source[key]
307        if (value != null && !(Object.prototype.hasOwnProperty.call(target, key))) {
308          target[key] = value
309        }
310      }
311      return target
312    }
313  
314  // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
315    var enumerateDevicesKey = function (done, options) {
316      if (!isEnumerateDevicesSupported()) {
317        return done(options.NOT_AVAILABLE)
318      }
319      navigator.mediaDevices.enumerateDevices().then(function (devices) {
320        done(devices.map(function (device) {
321          return 'id=' + device.deviceId + ';gid=' + device.groupId + ';' + device.kind + ';' + device.label
322        }))
323      })
324        .catch(function (error) {
325          done(error)
326        })
327    }
328  
329    var isEnumerateDevicesSupported = function () {
330      return (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices)
331    }
332  // Inspired by and based on https://github.com/cozylife/audio-fingerprint
333    var audioKey = function (done, options) {
334      var audioOptions = options.audio
335      if (audioOptions.excludeIOS11 && navigator.userAgent.match(/OS 11.+Version\/11.+Safari/)) {
336          // See comment for excludeUserAgent and https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088
337        return done(options.EXCLUDED)
338      }
339  
340      var AudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext
341  
342      if (AudioContext == null) {
343        return done(options.NOT_AVAILABLE)
344      }
345  
346      var context = new AudioContext(1, 44100, 44100)
347  
348      var oscillator = context.createOscillator()
349      oscillator.type = 'triangle'
350      oscillator.frequency.setValueAtTime(10000, context.currentTime)
351  
352      var compressor = context.createDynamicsCompressor()
353      each([
354          ['threshold', -50],
355          ['knee', 40],
356          ['ratio', 12],
357          ['reduction', -20],
358          ['attack', 0],
359          ['release', 0.25]
360      ], function (item) {
361        if (compressor[item[0]] !== undefined && typeof compressor[item[0]].setValueAtTime === 'function') {
362          compressor[item[0]].setValueAtTime(item[1], context.currentTime)
363        }
364      })
365  
366      oscillator.connect(compressor)
367      compressor.connect(context.destination)
368      oscillator.start(0)
369      context.startRendering()
370  
371      var audioTimeoutId = setTimeout(function () {
372        console.warn('Audio fingerprint timed out. Please report bug at https://github.com/Valve/fingerprintjs2 with your user agent: "' + navigator.userAgent + '".')
373        context.oncomplete = function () {}
374        context = null
375        return done('audioTimeout')
376      }, audioOptions.timeout)
377  
378      context.oncomplete = function (event) {
379        var fingerprint
380        try {
381          clearTimeout(audioTimeoutId)
382          fingerprint = event.renderedBuffer.getChannelData(0)
383              .slice(4500, 5000)
384              .reduce(function (acc, val) { return acc + Math.abs(val) }, 0)
385              .toString()
386          oscillator.disconnect()
387          compressor.disconnect()
388        } catch (error) {
389          done(error)
390          return
391        }
392        done(fingerprint)
393      }
394    }
395    var UserAgent = function (done) {
396      done(navigator.userAgent)
397    }
398    var languageKey = function (done, options) {
399      done(navigator.language || navigator.userLanguage || navigator.browserLanguage || navigator.systemLanguage || options.NOT_AVAILABLE)
400    }
401    var colorDepthKey = function (done, options) {
402      done(window.screen.colorDepth || options.NOT_AVAILABLE)
403    }
404    var deviceMemoryKey = function (done, options) {
405      done(navigator.deviceMemory || options.NOT_AVAILABLE)
406    }
407    var pixelRatioKey = function (done, options) {
408      done(window.devicePixelRatio || options.NOT_AVAILABLE)
409    }
410    var screenResolutionKey = function (done, options) {
411      done(getScreenResolution(options))
412    }
413    var getScreenResolution = function (options) {
414      var resolution = [window.screen.width, window.screen.height]
415      if (options.screen.detectScreenOrientation) {
416        resolution.sort().reverse()
417      }
418      return resolution
419    }
420    var availableScreenResolutionKey = function (done, options) {
421      done(getAvailableScreenResolution(options))
422    }
423    var getAvailableScreenResolution = function (options) {
424      if (window.screen.availWidth && window.screen.availHeight) {
425        var available = [window.screen.availHeight, window.screen.availWidth]
426        if (options.screen.detectScreenOrientation) {
427          available.sort().reverse()
428        }
429        return available
430      }
431      // headless browsers
432      return options.NOT_AVAILABLE
433    }
434    var timezoneOffset = function (done) {
435      done(new Date().getTimezoneOffset())
436    }
437    var timezone = function (done, options) {
438      if (window.Intl && window.Intl.DateTimeFormat) {
439        done(new window.Intl.DateTimeFormat().resolvedOptions().timeZone)
440        return
441      }
442      done(options.NOT_AVAILABLE)
443    }
444    var sessionStorageKey = function (done, options) {
445      done(hasSessionStorage(options))
446    }
447    var localStorageKey = function (done, options) {
448      done(hasLocalStorage(options))
449    }
450    var indexedDbKey = function (done, options) {
451      done(hasIndexedDB(options))
452    }
453    var addBehaviorKey = function (done) {
454        // body might not be defined at this point or removed programmatically
455      done(!!(document.body && document.body.addBehavior))
456    }
457    var openDatabaseKey = function (done) {
458      done(!!window.openDatabase)
459    }
460    var cpuClassKey = function (done, options) {
461      done(getNavigatorCpuClass(options))
462    }
463    var platformKey = function (done, options) {
464      done(getNavigatorPlatform(options))
465    }
466    var doNotTrackKey = function (done, options) {
467      done(getDoNotTrack(options))
468    }
469    var canvasKey = function (done, options) {
470      if (isCanvasSupported()) {
471        done(getCanvasFp(options))
472        return
473      }
474      done(options.NOT_AVAILABLE)
475    }
476    var webglKey = function (done, options) {
477      if (isWebGlSupported()) {
478        done(getWebglFp())
479        return
480      }
481      done(options.NOT_AVAILABLE)
482    }
483    var webglVendorAndRendererKey = function (done) {
484      if (isWebGlSupported()) {
485        done(getWebglVendorAndRenderer())
486        return
487      }
488      done()
489    }
490    var adBlockKey = function (done) {
491      done(getAdBlock())
492    }
493    var hasLiedLanguagesKey = function (done) {
494      done(getHasLiedLanguages())
495    }
496    var hasLiedResolutionKey = function (done) {
497      done(getHasLiedResolution())
498    }
499    var hasLiedOsKey = function (done) {
500      done(getHasLiedOs())
501    }
502    var hasLiedBrowserKey = function (done) {
503      done(getHasLiedBrowser())
504    }
505  // flash fonts (will increase fingerprinting time 20X to ~ 130-150ms)
506    var flashFontsKey = function (done, options) {
507      // we do flash if swfobject is loaded
508      if (!hasSwfObjectLoaded()) {
509        return done('swf object not loaded')
510      }
511      if (!hasMinFlashInstalled()) {
512        return done('flash not installed')
513      }
514      if (!options.fonts.swfPath) {
515        return done('missing options.fonts.swfPath')
516      }
517      loadSwfAndDetectFonts(function (fonts) {
518        done(fonts)
519      }, options)
520    }
521  // kudos to http://www.lalit.org/lab/javascript-css-font-detect/
522    var jsFontsKey = function (done, options) {
523        // a font will be compared against all the three default fonts.
524        // and if it doesn't match all 3 then that font is not available.
525      var baseFonts = ['monospace', 'sans-serif', 'serif']
526  
527      var fontList = [
528        'Andale Mono', 'Arial', 'Arial Black', 'Arial Hebrew', 'Arial MT', 'Arial Narrow', 'Arial Rounded MT Bold', 'Arial Unicode MS',
529        'Bitstream Vera Sans Mono', 'Book Antiqua', 'Bookman Old Style',
530        'Calibri', 'Cambria', 'Cambria Math', 'Century', 'Century Gothic', 'Century Schoolbook', 'Comic Sans', 'Comic Sans MS', 'Consolas', 'Courier', 'Courier New',
531        'Geneva', 'Georgia',
532        'Helvetica', 'Helvetica Neue',
533        'Impact',
534        'Lucida Bright', 'Lucida Calligraphy', 'Lucida Console', 'Lucida Fax', 'LUCIDA GRANDE', 'Lucida Handwriting', 'Lucida Sans', 'Lucida Sans Typewriter', 'Lucida Sans Unicode',
535        'Microsoft Sans Serif', 'Monaco', 'Monotype Corsiva', 'MS Gothic', 'MS Outlook', 'MS PGothic', 'MS Reference Sans Serif', 'MS Sans Serif', 'MS Serif', 'MYRIAD', 'MYRIAD PRO',
536        'Palatino', 'Palatino Linotype',
537        'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Light', 'Segoe UI Semibold', 'Segoe UI Symbol',
538        'Tahoma', 'Times', 'Times New Roman', 'Times New Roman PS', 'Trebuchet MS',
539        'Verdana', 'Wingdings', 'Wingdings 2', 'Wingdings 3'
540      ]
541  
542      if (options.fonts.extendedJsFonts) {
543        var extendedFontList = [
544          'Abadi MT Condensed Light', 'Academy Engraved LET', 'ADOBE CASLON PRO', 'Adobe Garamond', 'ADOBE GARAMOND PRO', 'Agency FB', 'Aharoni', 'Albertus Extra Bold', 'Albertus Medium', 'Algerian', 'Amazone BT', 'American Typewriter',
545          'American Typewriter Condensed', 'AmerType Md BT', 'Andalus', 'Angsana New', 'AngsanaUPC', 'Antique Olive', 'Aparajita', 'Apple Chancery', 'Apple Color Emoji', 'Apple SD Gothic Neo', 'Arabic Typesetting', 'ARCHER',
546          'ARNO PRO', 'Arrus BT', 'Aurora Cn BT', 'AvantGarde Bk BT', 'AvantGarde Md BT', 'AVENIR', 'Ayuthaya', 'Bandy', 'Bangla Sangam MN', 'Bank Gothic', 'BankGothic Md BT', 'Baskerville',
547          'Baskerville Old Face', 'Batang', 'BatangChe', 'Bauer Bodoni', 'Bauhaus 93', 'Bazooka', 'Bell MT', 'Bembo', 'Benguiat Bk BT', 'Berlin Sans FB', 'Berlin Sans FB Demi', 'Bernard MT Condensed', 'BernhardFashion BT', 'BernhardMod BT', 'Big Caslon', 'BinnerD',
548          'Blackadder ITC', 'BlairMdITC TT', 'Bodoni 72', 'Bodoni 72 Oldstyle', 'Bodoni 72 Smallcaps', 'Bodoni MT', 'Bodoni MT Black', 'Bodoni MT Condensed', 'Bodoni MT Poster Compressed',
549          'Bookshelf Symbol 7', 'Boulder', 'Bradley Hand', 'Bradley Hand ITC', 'Bremen Bd BT', 'Britannic Bold', 'Broadway', 'Browallia New', 'BrowalliaUPC', 'Brush Script MT', 'Californian FB', 'Calisto MT', 'Calligrapher', 'Candara',
550          'CaslonOpnface BT', 'Castellar', 'Centaur', 'Cezanne', 'CG Omega', 'CG Times', 'Chalkboard', 'Chalkboard SE', 'Chalkduster', 'Charlesworth', 'Charter Bd BT', 'Charter BT', 'Chaucer',
551          'ChelthmITC Bk BT', 'Chiller', 'Clarendon', 'Clarendon Condensed', 'CloisterBlack BT', 'Cochin', 'Colonna MT', 'Constantia', 'Cooper Black', 'Copperplate', 'Copperplate Gothic', 'Copperplate Gothic Bold',
552          'Copperplate Gothic Light', 'CopperplGoth Bd BT', 'Corbel', 'Cordia New', 'CordiaUPC', 'Cornerstone', 'Coronet', 'Cuckoo', 'Curlz MT', 'DaunPenh', 'Dauphin', 'David', 'DB LCD Temp', 'DELICIOUS', 'Denmark',
553          'DFKai-SB', 'Didot', 'DilleniaUPC', 'DIN', 'DokChampa', 'Dotum', 'DotumChe', 'Ebrima', 'Edwardian Script ITC', 'Elephant', 'English 111 Vivace BT', 'Engravers MT', 'EngraversGothic BT', 'Eras Bold ITC', 'Eras Demi ITC', 'Eras Light ITC', 'Eras Medium ITC',
554          'EucrosiaUPC', 'Euphemia', 'Euphemia UCAS', 'EUROSTILE', 'Exotc350 Bd BT', 'FangSong', 'Felix Titling', 'Fixedsys', 'FONTIN', 'Footlight MT Light', 'Forte',
555          'FrankRuehl', 'Fransiscan', 'Freefrm721 Blk BT', 'FreesiaUPC', 'Freestyle Script', 'French Script MT', 'FrnkGothITC Bk BT', 'Fruitger', 'FRUTIGER',
556          'Futura', 'Futura Bk BT', 'Futura Lt BT', 'Futura Md BT', 'Futura ZBlk BT', 'FuturaBlack BT', 'Gabriola', 'Galliard BT', 'Gautami', 'Geeza Pro', 'Geometr231 BT', 'Geometr231 Hv BT', 'Geometr231 Lt BT', 'GeoSlab 703 Lt BT',
557          'GeoSlab 703 XBd BT', 'Gigi', 'Gill Sans', 'Gill Sans MT', 'Gill Sans MT Condensed', 'Gill Sans MT Ext Condensed Bold', 'Gill Sans Ultra Bold', 'Gill Sans Ultra Bold Condensed', 'Gisha', 'Gloucester MT Extra Condensed', 'GOTHAM', 'GOTHAM BOLD',
558          'Goudy Old Style', 'Goudy Stout', 'GoudyHandtooled BT', 'GoudyOLSt BT', 'Gujarati Sangam MN', 'Gulim', 'GulimChe', 'Gungsuh', 'GungsuhChe', 'Gurmukhi MN', 'Haettenschweiler', 'Harlow Solid Italic', 'Harrington', 'Heather', 'Heiti SC', 'Heiti TC', 'HELV',
559          'Herald', 'High Tower Text', 'Hiragino Kaku Gothic ProN', 'Hiragino Mincho ProN', 'Hoefler Text', 'Humanst 521 Cn BT', 'Humanst521 BT', 'Humanst521 Lt BT', 'Imprint MT Shadow', 'Incised901 Bd BT', 'Incised901 BT',
560          'Incised901 Lt BT', 'INCONSOLATA', 'Informal Roman', 'Informal011 BT', 'INTERSTATE', 'IrisUPC', 'Iskoola Pota', 'JasmineUPC', 'Jazz LET', 'Jenson', 'Jester', 'Jokerman', 'Juice ITC', 'Kabel Bk BT', 'Kabel Ult BT', 'Kailasa', 'KaiTi', 'Kalinga', 'Kannada Sangam MN',
561          'Kartika', 'Kaufmann Bd BT', 'Kaufmann BT', 'Khmer UI', 'KodchiangUPC', 'Kokila', 'Korinna BT', 'Kristen ITC', 'Krungthep', 'Kunstler Script', 'Lao UI', 'Latha', 'Leelawadee', 'Letter Gothic', 'Levenim MT', 'LilyUPC', 'Lithograph', 'Lithograph Light', 'Long Island',
562          'Lydian BT', 'Magneto', 'Maiandra GD', 'Malayalam Sangam MN', 'Malgun Gothic',
563          'Mangal', 'Marigold', 'Marion', 'Marker Felt', 'Market', 'Marlett', 'Matisse ITC', 'Matura MT Script Capitals', 'Meiryo', 'Meiryo UI', 'Microsoft Himalaya', 'Microsoft JhengHei', 'Microsoft New Tai Lue', 'Microsoft PhagsPa', 'Microsoft Tai Le',
564          'Microsoft Uighur', 'Microsoft YaHei', 'Microsoft Yi Baiti', 'MingLiU', 'MingLiU_HKSCS', 'MingLiU_HKSCS-ExtB', 'MingLiU-ExtB', 'Minion', 'Minion Pro', 'Miriam', 'Miriam Fixed', 'Mistral', 'Modern', 'Modern No. 20', 'Mona Lisa Solid ITC TT', 'Mongolian Baiti',
565          'MONO', 'MoolBoran', 'Mrs Eaves', 'MS LineDraw', 'MS Mincho', 'MS PMincho', 'MS Reference Specialty', 'MS UI Gothic', 'MT Extra', 'MUSEO', 'MV Boli',
566          'Nadeem', 'Narkisim', 'NEVIS', 'News Gothic', 'News GothicMT', 'NewsGoth BT', 'Niagara Engraved', 'Niagara Solid', 'Noteworthy', 'NSimSun', 'Nyala', 'OCR A Extended', 'Old Century', 'Old English Text MT', 'Onyx', 'Onyx BT', 'OPTIMA', 'Oriya Sangam MN',
567          'OSAKA', 'OzHandicraft BT', 'Palace Script MT', 'Papyrus', 'Parchment', 'Party LET', 'Pegasus', 'Perpetua', 'Perpetua Titling MT', 'PetitaBold', 'Pickwick', 'Plantagenet Cherokee', 'Playbill', 'PMingLiU', 'PMingLiU-ExtB',
568          'Poor Richard', 'Poster', 'PosterBodoni BT', 'PRINCETOWN LET', 'Pristina', 'PTBarnum BT', 'Pythagoras', 'Raavi', 'Rage Italic', 'Ravie', 'Ribbon131 Bd BT', 'Rockwell', 'Rockwell Condensed', 'Rockwell Extra Bold', 'Rod', 'Roman', 'Sakkal Majalla',
569          'Santa Fe LET', 'Savoye LET', 'Sceptre', 'Script', 'Script MT Bold', 'SCRIPTINA', 'Serifa', 'Serifa BT', 'Serifa Th BT', 'ShelleyVolante BT', 'Sherwood',
570          'Shonar Bangla', 'Showcard Gothic', 'Shruti', 'Signboard', 'SILKSCREEN', 'SimHei', 'Simplified Arabic', 'Simplified Arabic Fixed', 'SimSun', 'SimSun-ExtB', 'Sinhala Sangam MN', 'Sketch Rockwell', 'Skia', 'Small Fonts', 'Snap ITC', 'Snell Roundhand', 'Socket',
571          'Souvenir Lt BT', 'Staccato222 BT', 'Steamer', 'Stencil', 'Storybook', 'Styllo', 'Subway', 'Swis721 BlkEx BT', 'Swiss911 XCm BT', 'Sylfaen', 'Synchro LET', 'System', 'Tamil Sangam MN', 'Technical', 'Teletype', 'Telugu Sangam MN', 'Tempus Sans ITC',
572          'Terminal', 'Thonburi', 'Traditional Arabic', 'Trajan', 'TRAJAN PRO', 'Tristan', 'Tubular', 'Tunga', 'Tw Cen MT', 'Tw Cen MT C
vendor: 33,813 bytes, lines 572-1411
572ondensed', 'Tw Cen MT Condensed Extra Bold',
573          'TypoUpright BT', 'Unicorn', 'Univers', 'Univers CE 55 Medium', 'Univers Condensed', 'Utsaah', 'Vagabond', 'Vani', 'Vijaya', 'Viner Hand ITC', 'VisualUI', 'Vivaldi', 'Vladimir Script', 'Vrinda', 'Westminster', 'WHITNEY', 'Wide Latin',
574          'ZapfEllipt BT', 'ZapfHumnst BT', 'ZapfHumnst Dm BT', 'Zapfino', 'Zurich BlkEx BT', 'Zurich Ex BT', 'ZWAdobeF']
575        fontList = fontList.concat(extendedFontList)
576      }
577  
578      fontList = fontList.concat(options.fonts.userDefinedFonts)
579  
580        // remove duplicate fonts
581      fontList = fontList.filter(function (font, position) {
582        return fontList.indexOf(font) === position
583      })
584  
585        // we use m or w because these two characters take up the maximum width.
586        // And we use a LLi so that the same matching fonts can get separated
587      var testString = 'mmmmmmmmmmlli'
588  
589        // we test using 72px font size, we may use any size. I guess larger the better.
590      var testSize = '72px'
591  
592      var h = document.getElementsByTagName('body')[0]
593  
594        // div to load spans for the base fonts
595      var baseFontsDiv = document.createElement('div')
596  
597        // div to load spans for the fonts to detect
598      var fontsDiv = document.createElement('div')
599  
600      var defaultWidth = {}
601      var defaultHeight = {}
602  
603        // creates a span where the fonts will be loaded
604      var createSpan = function () {
605        var s = document.createElement('span')
606          /*
607           * We need this css as in some weird browser this
608           * span elements shows up for a microSec which creates a
609           * bad user experience
610           */
611        s.style.position = 'absolute'
612        s.style.left = '-9999px'
613        s.style.fontSize = testSize
614  
615          // css font reset to reset external styles
616        s.style.fontStyle = 'normal'
617        s.style.fontWeight = 'normal'
618        s.style.letterSpacing = 'normal'
619        s.style.lineBreak = 'auto'
620        s.style.lineHeight = 'normal'
621        s.style.textTransform = 'none'
622        s.style.textAlign = 'left'
623        s.style.textDecoration = 'none'
624        s.style.textShadow = 'none'
625        s.style.whiteSpace = 'normal'
626        s.style.wordBreak = 'normal'
627        s.style.wordSpacing = 'normal'
628  
629        s.innerHTML = testString
630        return s
631      }
632  
633        // creates a span and load the font to detect and a base font for fallback
634      var createSpanWithFonts = function (fontToDetect, baseFont) {
635        var s = createSpan()
636        s.style.fontFamily = "'" + fontToDetect + "'," + baseFont
637        return s
638      }
639  
640        // creates spans for the base fonts and adds them to baseFontsDiv
641      var initializeBaseFontsSpans = function () {
642        var spans = []
643        for (var index = 0, length = baseFonts.length; index < length; index++) {
644          var s = createSpan()
645          s.style.fontFamily = baseFonts[index]
646          baseFontsDiv.appendChild(s)
647          spans.push(s)
648        }
649        return spans
650      }
651  
652        // creates spans for the fonts to detect and adds them to fontsDiv
653      var initializeFontsSpans = function () {
654        var spans = {}
655        for (var i = 0, l = fontList.length; i < l; i++) {
656          var fontSpans = []
657          for (var j = 0, numDefaultFonts = baseFonts.length; j < numDefaultFonts; j++) {
658            var s = createSpanWithFonts(fontList[i], baseFonts[j])
659            fontsDiv.appendChild(s)
660            fontSpans.push(s)
661          }
662          spans[fontList[i]] = fontSpans // Stores {fontName : [spans for that font]}
663        }
664        return spans
665      }
666  
667        // checks if a font is available
668      var isFontAvailable = function (fontSpans) {
669        var detected = false
670        for (var i = 0; i < baseFonts.length; i++) {
671          detected = (fontSpans[i].offsetWidth !== defaultWidth[baseFonts[i]] || fontSpans[i].offsetHeight !== defaultHeight[baseFonts[i]])
672          if (detected) {
673            return detected
674          }
675        }
676        return detected
677      }
678  
679        // create spans for base fonts
680      var baseFontsSpans = initializeBaseFontsSpans()
681  
682        // add the spans to the DOM
683      h.appendChild(baseFontsDiv)
684  
685        // get the default width for the three base fonts
686      for (var index = 0, length = baseFonts.length; index < length; index++) {
687        defaultWidth[baseFonts[index]] = baseFontsSpans[index].offsetWidth // width for the default font
688        defaultHeight[baseFonts[index]] = baseFontsSpans[index].offsetHeight // height for the default font
689      }
690  
691        // create spans for fonts to detect
692      var fontsSpans = initializeFontsSpans()
693  
694        // add all the spans to the DOM
695      h.appendChild(fontsDiv)
696  
697        // check available fonts
698      var available = []
699      for (var i = 0, l = fontList.length; i < l; i++) {
700        if (isFontAvailable(fontsSpans[fontList[i]])) {
701          available.push(fontList[i])
702        }
703      }
704  
705        // remove spans from DOM
706      h.removeChild(fontsDiv)
707      h.removeChild(baseFontsDiv)
708      done(available)
709    }
710    var pluginsComponent = function (done, options) {
711      if (isIE()) {
712        if (!options.plugins.excludeIE) {
713          done(getIEPlugins(options))
714        } else {
715          done(options.EXCLUDED)
716        }
717      } else {
718        done(getRegularPlugins(options))
719      }
720    }
721    var getRegularPlugins = function (options) {
722      if (navigator.plugins == null) {
723        return options.NOT_AVAILABLE
724      }
725  
726      var plugins = []
727        // plugins isn't defined in Node envs.
728      for (var i = 0, l = navigator.plugins.length; i < l; i++) {
729        if (navigator.plugins[i]) { plugins.push(navigator.plugins[i]) }
730      }
731  
732        // sorting plugins only for those user agents, that we know randomize the plugins
733        // every time we try to enumerate them
734      if (pluginsShouldBeSorted(options)) {
735        plugins = plugins.sort(function (a, b) {
736          if (a.name > b.name) { return 1 }
737          if (a.name < b.name) { return -1 }
738          return 0
739        })
740      }
741      return map(plugins, function (p) {
742        var mimeTypes = map(p, function (mt) {
743          return [mt.type, mt.suffixes]
744        })
745        return [p.name, p.description, mimeTypes]
746      })
747    }
748    var getIEPlugins = function (options) {
749      var result = []
750      if ((Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(window, 'ActiveXObject')) || ('ActiveXObject' in window)) {
751        var names = [
752          'AcroPDF.PDF', // Adobe PDF reader 7+
753          'Adodb.Stream',
754          'AgControl.AgControl', // Silverlight
755          'DevalVRXCtrl.DevalVRXCtrl.1',
756          'MacromediaFlashPaper.MacromediaFlashPaper',
757          'Msxml2.DOMDocument',
758          'Msxml2.XMLHTTP',
759          'PDF.PdfCtrl', // Adobe PDF reader 6 and earlier, brrr
760          'QuickTime.QuickTime', // QuickTime
761          'QuickTimeCheckObject.QuickTimeCheck.1',
762          'RealPlayer',
763          'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)',
764          'RealVideo.RealVideo(tm) ActiveX Control (32-bit)',
765          'Scripting.Dictionary',
766          'SWCtl.SWCtl', // ShockWave player
767          'Shell.UIHelper',
768          'ShockwaveFlash.ShockwaveFlash', // flash plugin
769          'Skype.Detection',
770          'TDCCtl.TDCCtl',
771          'WMPlayer.OCX', // Windows media player
772          'rmocx.RealPlayer G2 Control',
773          'rmocx.RealPlayer G2 Control.1'
774        ]
775          // starting to detect plugins in IE
776        result = map(names, function (name) {
777          try {
778              // eslint-disable-next-line no-new
779            new window.ActiveXObject(name)
780            return name
781          } catch (e) {
782            return options.ERROR
783          }
784        })
785      } else {
786        result.push(options.NOT_AVAILABLE)
787      }
788      if (navigator.plugins) {
789        result = result.concat(getRegularPlugins(options))
790      }
791      return result
792    }
793    var pluginsShouldBeSorted = function (options) {
794      var should = false
795      for (var i = 0, l = options.plugins.sortPluginsFor.length; i < l; i++) {
796        var re = options.plugins.sortPluginsFor[i]
797        if (navigator.userAgent.match(re)) {
798          should = true
799          break
800        }
801      }
802      return should
803    }
804    var touchSupportKey = function (done) {
805      done(getTouchSupport())
806    }
807    var hardwareConcurrencyKey = function (done, options) {
808      done(getHardwareConcurrency(options))
809    }
810    var hasSessionStorage = function (options) {
811      try {
812        return !!window.sessionStorage
813      } catch (e) {
814        return options.ERROR // SecurityError when referencing it means it exists
815      }
816    }
817  
818  // https://bugzilla.mozilla.org/show_bug.cgi?id=781447
819    var hasLocalStorage = function (options) {
820      try {
821        return !!window.localStorage
822      } catch (e) {
823        return options.ERROR // SecurityError when referencing it means it exists
824      }
825    }
826    var hasIndexedDB = function (options) {
827      try {
828        return !!window.indexedDB
829      } catch (e) {
830        return options.ERROR // SecurityError when referencing it means it exists
831      }
832    }
833    var getHardwareConcurrency = function (options) {
834      if (navigator.hardwareConcurrency) {
835        return navigator.hardwareConcurrency
836      }
837      return options.NOT_AVAILABLE
838    }
839    var getNavigatorCpuClass = function (options) {
840      return navigator.cpuClass || options.NOT_AVAILABLE
841    }
842    var getNavigatorPlatform = function (options) {
843      if (navigator.platform) {
844        return navigator.platform
845      } else {
846        return options.NOT_AVAILABLE
847      }
848    }
849    var getDoNotTrack = function (options) {
850      if (navigator.doNotTrack) {
851        return navigator.doNotTrack
852      } else if (navigator.msDoNotTrack) {
853        return navigator.msDoNotTrack
854      } else if (window.doNotTrack) {
855        return window.doNotTrack
856      } else {
857        return options.NOT_AVAILABLE
858      }
859    }
860  // This is a crude and primitive touch screen detection.
861  // It's not possible to currently reliably detect the  availability of a touch screen
862  // with a JS, without actually subscribing to a touch event.
863  // http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
864  // https://github.com/Modernizr/Modernizr/issues/548
865  // method returns an array of 3 values:
866  // maxTouchPoints, the success or failure of creating a TouchEvent,
867  // and the availability of the 'ontouchstart' property
868  
869    var getTouchSupport = function () {
870      var maxTouchPoints = 0
871      var touchEvent
872      if (typeof navigator.maxTouchPoints !== 'undefined') {
873        maxTouchPoints = navigator.maxTouchPoints
874      } else if (typeof navigator.msMaxTouchPoints !== 'undefined') {
875        maxTouchPoints = navigator.msMaxTouchPoints
876      }
877      try {
878        document.createEvent('TouchEvent')
879        touchEvent = true
880      } catch (_) {
881        touchEvent = false
882      }
883      var touchStart = 'ontouchstart' in window
884      return [maxTouchPoints, touchEvent, touchStart]
885    }
886  // https://www.browserleaks.com/canvas#how-does-it-work
887  
888    var getCanvasFp = function (options) {
889      var result = []
890        // Very simple now, need to make it more complex (geo shapes etc)
891      var canvas = document.createElement('canvas')
892      canvas.width = 2000
893      canvas.height = 200
894      canvas.style.display = 'inline'
895      var ctx = canvas.getContext('2d')
896        // detect browser support of canvas winding
897        // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
898        // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/canvas/winding.js
899      ctx.rect(0, 0, 10, 10)
900      ctx.rect(2, 2, 6, 6)
901      result.push('canvas winding:' + ((ctx.isPointInPath(5, 5, 'evenodd') === false) ? 'yes' : 'no'))
902  
903      ctx.textBaseline = 'alphabetic'
904      ctx.fillStyle = '#f60'
905      ctx.fillRect(125, 1, 62, 20)
906      ctx.fillStyle = '#069'
907        // https://github.com/Valve/fingerprintjs2/issues/66
908      if (options.dontUseFakeFontInCanvas) {
909        ctx.font = '11pt Arial'
910      } else {
911        ctx.font = '11pt no-real-font-123'
912      }
913      ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 2, 15)
914      ctx.fillStyle = 'rgba(102, 204, 0, 0.2)'
915      ctx.font = '18pt Arial'
916      ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 4, 45)
917  
918        // canvas blending
919        // http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
920        // http://jsfiddle.net/NDYV8/16/
921      ctx.globalCompositeOperation = 'multiply'
922      ctx.fillStyle = 'rgb(255,0,255)'
923      ctx.beginPath()
924      ctx.arc(50, 50, 50, 0, Math.PI * 2, true)
925      ctx.closePath()
926      ctx.fill()
927      ctx.fillStyle = 'rgb(0,255,255)'
928      ctx.beginPath()
929      ctx.arc(100, 50, 50, 0, Math.PI * 2, true)
930      ctx.closePath()
931      ctx.fill()
932      ctx.fillStyle = 'rgb(255,255,0)'
933      ctx.beginPath()
934      ctx.arc(75, 100, 50, 0, Math.PI * 2, true)
935      ctx.closePath()
936      ctx.fill()
937      ctx.fillStyle = 'rgb(255,0,255)'
938        // canvas winding
939        // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
940        // http://jsfiddle.net/NDYV8/19/
941      ctx.arc(75, 75, 75, 0, Math.PI * 2, true)
942      ctx.arc(75, 75, 25, 0, Math.PI * 2, true)
943      ctx.fill('evenodd')
944  
945      if (canvas.toDataURL) { result.push('canvas fp:' + canvas.toDataURL()) }
946      return result
947    }
948    var getWebglFp = function () {
949      var gl
950      var fa2s = function (fa) {
951        gl.clearColor(0.0, 0.0, 0.0, 1.0)
952        gl.enable(gl.DEPTH_TEST)
953        gl.depthFunc(gl.LEQUAL)
954        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
955        return '[' + fa[0] + ', ' + fa[1] + ']'
956      }
957      var maxAnisotropy = function (gl) {
958        var ext = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic')
959        if (ext) {
960          var anisotropy = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT)
961          if (anisotropy === 0) {
962            anisotropy = 2
963          }
964          return anisotropy
965        } else {
966          return null
967        }
968      }
969  
970      gl = getWebglCanvas()
971      if (!gl) { return null }
972        // WebGL fingerprinting is a combination of techniques, found in MaxMind antifraud script & Augur fingerprinting.
973        // First it draws a gradient object with shaders and convers the image to the Base64 string.
974        // Then it enumerates all WebGL extensions & capabilities and appends them to the Base64 string, resulting in a huge WebGL string, potentially very unique on each device
975        // Since iOS supports webgl starting from version 8.1 and 8.1 runs on several graphics chips, the results may be different across ios devices, but we need to verify it.
976      var result = []
977      var vShaderTemplate = 'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}'
978      var fShaderTemplate = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}'
979      var vertexPosBuffer = gl.createBuffer()
980      gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer)
981      var vertices = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.732134444, 0])
982      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
983      vertexPosBuffer.itemSize = 3
984      vertexPosBuffer.numItems = 3
985      var program = gl.createProgram()
986      var vshader = gl.createShader(gl.VERTEX_SHADER)
987      gl.shaderSource(vshader, vShaderTemplate)
988      gl.compileShader(vshader)
989      var fshader = gl.createShader(gl.FRAGMENT_SHADER)
990      gl.shaderSource(fshader, fShaderTemplate)
991      gl.compileShader(fshader)
992      gl.attachShader(program, vshader)
993      gl.attachShader(program, fshader)
994      gl.linkProgram(program)
995      gl.useProgram(program)
996      program.vertexPosAttrib = gl.getAttribLocation(program, 'attrVertex')
997      program.offsetUniform = gl.getUniformLocation(program, 'uniformOffset')
998      gl.enableVertexAttribArray(program.vertexPosArray)
999      gl.vertexAttribPointer(program.vertexPosAttrib, vertexPosBuffer.itemSize, gl.FLOAT, !1, 0, 0)
1000      gl.uniform2f(program.offsetUniform, 1, 1)
1001      gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems)
1002      try {
1003        result.push(gl.canvas.toDataURL())
1004      } catch (e) {
1005          /* .toDataURL may be absent or broken (blocked by extension) */
1006      }
1007      result.push('extensions:' + (gl.getSupportedExtensions() || []).join(';'))
1008      result.push('webgl aliased line width range:' + fa2s(gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE)))
1009      result.push('webgl aliased point size range:' + fa2s(gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)))
1010      result.push('webgl alpha bits:' + gl.getParameter(gl.ALPHA_BITS))
1011      result.push('webgl antialiasing:' + (gl.getContextAttributes().antialias ? 'yes' : 'no'))
1012      result.push('webgl blue bits:' + gl.getParameter(gl.BLUE_BITS))
1013      result.push('webgl depth bits:' + gl.getParameter(gl.DEPTH_BITS))
1014      result.push('webgl green bits:' + gl.getParameter(gl.GREEN_BITS))
1015      result.push('webgl max anisotropy:' + maxAnisotropy(gl))
1016      result.push('webgl max combined texture image units:' + gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS))
1017      result.push('webgl max cube map texture size:' + gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE))
1018      result.push('webgl max fragment uniform vectors:' + gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS))
1019      result.push('webgl max render buffer size:' + gl.getParameter(gl.MAX_RENDERBUFFER_SIZE))
1020      result.push('webgl max texture image units:' + gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS))
1021      result.push('webgl max texture size:' + gl.getParameter(gl.MAX_TEXTURE_SIZE))
1022      result.push('webgl max varying vectors:' + gl.getParameter(gl.MAX_VARYING_VECTORS))
1023      result.push('webgl max vertex attribs:' + gl.getParameter(gl.MAX_VERTEX_ATTRIBS))
1024      result.push('webgl max vertex texture image units:' + gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS))
1025      result.push('webgl max vertex uniform vectors:' + gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS))
1026      result.push('webgl max viewport dims:' + fa2s(gl.getParameter(gl.MAX_VIEWPORT_DIMS)))
1027      result.push('webgl red bits:' + gl.getParameter(gl.RED_BITS))
1028      result.push('webgl renderer:' + gl.getParameter(gl.RENDERER))
1029      result.push('webgl shading language version:' + gl.getParameter(gl.SHADING_LANGUAGE_VERSION))
1030      result.push('webgl stencil bits:' + gl.getParameter(gl.STENCIL_BITS))
1031      result.push('webgl vendor:' + gl.getParameter(gl.VENDOR))
1032      result.push('webgl version:' + gl.getParameter(gl.VERSION))
1033  
1034      try {
1035          // Add the unmasked vendor and unmasked renderer if the debug_renderer_info extension is available
1036        var extensionDebugRendererInfo = gl.getExtension('WEBGL_debug_renderer_info')
1037        if (extensionDebugRendererInfo) {
1038          result.push('webgl unmasked vendor:' + gl.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL))
1039          result.push('webgl unmasked renderer:' + gl.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL))
1040        }
1041      } catch (e) { /* squelch */ }
1042  
1043      if (!gl.getShaderPrecisionFormat) {
1044        return result
1045      }
1046  
1047      each(['FLOAT', 'INT'], function (numType) {
1048        each(['VERTEX', 'FRAGMENT'], function (shader) {
1049          each(['HIGH', 'MEDIUM', 'LOW'], function (numSize) {
1050            each(['precision', 'rangeMin', 'rangeMax'], function (key) {
1051              var format = gl.getShaderPrecisionFormat(gl[shader + '_SHADER'], gl[numSize + '_' + numType])[key]
1052              if (key !== 'precision') {
1053                key = 'precision ' + key
1054              }
1055              var line = ['webgl ', shader.toLowerCase(), ' shader ', numSize.toLowerCase(), ' ', numType.toLowerCase(), ' ', key, ':', format].join('')
1056              result.push(line)
1057            })
1058          })
1059        })
1060      })
1061      return result
1062    }
1063    var getWebglVendorAndRenderer = function () {
1064        /* This a subset of the WebGL fingerprint with a lot of entropy, while being reasonably browser-independent */
1065      try {
1066        var glContext = getWebglCanvas()
1067        var extensionDebugRendererInfo = glContext.getExtension('WEBGL_debug_renderer_info')
1068        return glContext.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL) + '~' + glContext.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL)
1069      } catch (e) {
1070        return null
1071      }
1072    }
1073    var getAdBlock = function () {
1074      var ads = document.createElement('div')
1075      ads.innerHTML = '&nbsp;'
1076      ads.className = 'adsbox'
1077      var result = false
1078      try {
1079          // body may not exist, that's why we need try/catch
1080        document.body.appendChild(ads)
1081        result = document.getElementsByClassName('adsbox')[0].offsetHeight === 0
1082        document.body.removeChild(ads)
1083      } catch (e) {
1084        result = false
1085      }
1086      return result
1087    }
1088    var getHasLiedLanguages = function () {
1089        // We check if navigator.language is equal to the first language of navigator.languages
1090      if (typeof navigator.languages !== 'undefined') {
1091        try {
1092          var firstLanguages = navigator.languages[0].substr(0, 2)
1093          if (firstLanguages !== navigator.language.substr(0, 2)) {
1094            return true
1095          }
1096        } catch (err) {
1097          return true
1098        }
1099      }
1100      return false
1101    }
1102    var getHasLiedResolution = function () {
1103      return window.screen.width < window.screen.availWidth || window.screen.height < window.screen.availHeight
1104    }
1105    var getHasLiedOs = function () {
1106      var userAgent = navigator.userAgent.toLowerCase()
1107      var oscpu = navigator.oscpu
1108      var platform = navigator.platform.toLowerCase()
1109      var os
1110        // We extract the OS from the user agent (respect the order of the if else if statement)
1111      if (userAgent.indexOf('windows phone') >= 0) {
1112        os = 'Windows Phone'
1113      } else if (userAgent.indexOf('win') >= 0) {
1114        os = 'Windows'
1115      } else if (userAgent.indexOf('android') >= 0) {
1116        os = 'Android'
1117      } else if (userAgent.indexOf('linux') >= 0) {
1118        os = 'Linux'
1119      } else if (userAgent.indexOf('iphone') >= 0 || userAgent.indexOf('ipad') >= 0) {
1120        os = 'iOS'
1121      } else if (userAgent.indexOf('mac') >= 0) {
1122        os = 'Mac'
1123      } else {
1124        os = 'Other'
1125      }
1126        // We detect if the person uses a mobile device
1127      var mobileDevice = (('ontouchstart' in window) ||
1128          (navigator.maxTouchPoints > 0) ||
1129          (navigator.msMaxTouchPoints > 0))
1130  
1131      if (mobileDevice && os !== 'Windows Phone' && os !== 'Android' && os !== 'iOS' && os !== 'Other') {
1132        return true
1133      }
1134  
1135        // We compare oscpu with the OS extracted from the UA
1136      if (typeof oscpu !== 'undefined') {
1137        oscpu = oscpu.toLowerCase()
1138        if (oscpu.indexOf('win') >= 0 && os !== 'Windows' && os !== 'Windows Phone') {
1139          return true
1140        } else if (oscpu.indexOf('linux') >= 0 && os !== 'Linux' && os !== 'Android') {
1141          return true
1142        } else if (oscpu.indexOf('mac') >= 0 && os !== 'Mac' && os !== 'iOS') {
1143          return true
1144        } else if ((oscpu.indexOf('win') === -1 && oscpu.indexOf('linux') === -1 && oscpu.indexOf('mac') === -1) !== (os === 'Other')) {
1145          return true
1146        }
1147      }
1148  
1149        // We compare platform with the OS extracted from the UA
1150      if (platform.indexOf('win') >= 0 && os !== 'Windows' && os !== 'Windows Phone') {
1151        return true
1152      } else if ((platform.indexOf('linux') >= 0 || platform.indexOf('android') >= 0 || platform.indexOf('pike') >= 0) && os !== 'Linux' && os !== 'Android') {
1153        return true
1154      } else if ((platform.indexOf('mac') >= 0 || platform.indexOf('ipad') >= 0 || platform.indexOf('ipod') >= 0 || platform.indexOf('iphone') >= 0) && os !== 'Mac' && os !== 'iOS') {
1155        return true
1156      } else if ((platform.indexOf('win') === -1 && platform.indexOf('linux') === -1 && platform.indexOf('mac') === -1) !== (os === 'Other')) {
1157        return true
1158      }
1159  
1160      return typeof navigator.plugins === 'undefined' && os !== 'Windows' && os !== 'Windows Phone'
1161    }
1162    var getHasLiedBrowser = function () {
1163      var userAgent = navigator.userAgent.toLowerCase()
1164      var productSub = navigator.productSub
1165  
1166        // we extract the browser from the user agent (respect the order of the tests)
1167      var browser
1168      if (userAgent.indexOf('firefox') >= 0) {
1169        browser = 'Firefox'
1170      } else if (userAgent.indexOf('opera') >= 0 || userAgent.indexOf('opr') >= 0) {
1171        browser = 'Opera'
1172      } else if (userAgent.indexOf('chrome') >= 0) {
1173        browser = 'Chrome'
1174      } else if (userAgent.indexOf('safari') >= 0) {
1175        browser = 'Safari'
1176      } else if (userAgent.indexOf('trident') >= 0) {
1177        browser = 'Internet Explorer'
1178      } else {
1179        browser = 'Other'
1180      }
1181  
1182      if ((browser === 'Chrome' || browser === 'Safari' || browser === 'Opera') && productSub !== '20030107') {
1183        return true
1184      }
1185  
1186        // eslint-disable-next-line no-eval
1187      var tempRes = eval.toString().length
1188      if (tempRes === 37 && browser !== 'Safari' && browser !== 'Firefox' && browser !== 'Other') {
1189        return true
1190      } else if (tempRes === 39 && browser !== 'Internet Explorer' && browser !== 'Other') {
1191        return true
1192      } else if (tempRes === 33 && browser !== 'Chrome' && browser !== 'Opera' && browser !== 'Other') {
1193        return true
1194      }
1195  
1196        // We create an error to see how it is handled
1197      var errFirefox
1198      try {
1199          // eslint-disable-next-line no-throw-literal
1200        throw 'a'
1201      } catch (err) {
1202        try {
1203          err.toSource()
1204          errFirefox = true
1205        } catch (errOfErr) {
1206          errFirefox = false
1207        }
1208      }
1209      return errFirefox && browser !== 'Firefox' && browser !== 'Other'
1210    }
1211    var isCanvasSupported = function () {
1212      var elem = document.createElement('canvas')
1213      return !!(elem.getContext && elem.getContext('2d'))
1214    }
1215    var isWebGlSupported = function () {
1216        // code taken from Modernizr
1217      if (!isCanvasSupported()) {
1218        return false
1219      }
1220  
1221      var glContext = getWebglCanvas()
1222      return !!window.WebGLRenderingContext && !!glContext
1223    }
1224    var isIE = function () {
1225      if (navigator.appName === 'Microsoft Internet Explorer') {
1226        return true
1227      } else if (navigator.appName === 'Netscape' && /Trident/.test(navigator.userAgent)) { // IE 11
1228        return true
1229      }
1230      return false
1231    }
1232    var hasSwfObjectLoaded = function () {
1233      return typeof window.swfobject !== 'undefined'
1234    }
1235    var hasMinFlashInstalled = function () {
1236      return window.swfobject.hasFlashPlayerVersion('9.0.0')
1237    }
1238    var addFlashDivNode = function (options) {
1239      var node = document.createElement('div')
1240      node.setAttribute('id', options.fonts.swfContainerId)
1241      document.body.appendChild(node)
1242    }
1243    var loadSwfAndDetectFonts = function (done, options) {
1244      var hiddenCallback = '___fp_swf_loaded'
1245      window[hiddenCallback] = function (fonts) {
1246        done(fonts)
1247      }
1248      var id = options.fonts.swfContainerId
1249      addFlashDivNode()
1250      var flashvars = { onReady: hiddenCallback }
1251      var flashparams = { allowScriptAccess: 'always', menu: 'false' }
1252      window.swfobject.embedSWF(options.fonts.swfPath, id, '1', '1', '9.0.0', false, flashvars, flashparams, {})
1253    }
1254    var getWebglCanvas = function () {
1255      var canvas = document.createElement('canvas')
1256      var gl = null
1257      try {
1258        gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
1259      } catch (e) { /* squelch */ }
1260      if (!gl) { gl = null }
1261      return gl
1262    }
1263  
1264    var components = [
1265      {key: 'userAgent', getData: UserAgent},
1266      {key: 'language', getData: languageKey},
1267      {key: 'colorDepth', getData: colorDepthKey},
1268      {key: 'deviceMemory', getData: deviceMemoryKey},
1269      {key: 'pixelRatio', getData: pixelRatioKey},
1270      {key: 'hardwareConcurrency', getData: hardwareConcurrencyKey},
1271      {key: 'screenResolution', getData: screenResolutionKey},
1272      {key: 'availableScreenResolution', getData: availableScreenResolutionKey},
1273      {key: 'timezoneOffset', getData: timezoneOffset},
1274      {key: 'timezone', getData: timezone},
1275      {key: 'sessionStorage', getData: sessionStorageKey},
1276      {key: 'localStorage', getData: localStorageKey},
1277      {key: 'indexedDb', getData: indexedDbKey},
1278      {key: 'addBehavior', getData: addBehaviorKey},
1279      {key: 'openDatabase', getData: openDatabaseKey},
1280      {key: 'cpuClass', getData: cpuClassKey},
1281      {key: 'platform', getData: platformKey},
1282      {key: 'doNotTrack', getData: doNotTrackKey},
1283      {key: 'plugins', getData: pluginsComponent},
1284      {key: 'canvas', getData: canvasKey},
1285      {key: 'webgl', getData: webglKey},
1286      {key: 'webglVendorAndRenderer', getData: webglVendorAndRendererKey},
1287      {key: 'adBlock', getData: adBlockKey},
1288      {key: 'hasLiedLanguages', getData: hasLiedLanguagesKey},
1289      {key: 'hasLiedResolution', getData: hasLiedResolutionKey},
1290      {key: 'hasLiedOs', getData: hasLiedOsKey},
1291      {key: 'hasLiedBrowser', getData: hasLiedBrowserKey},
1292      {key: 'touchSupport', getData: touchSupportKey},
1293      {key: 'fonts', getData: jsFontsKey, pauseBefore: true},
1294      {key: 'fontsFlash', getData: flashFontsKey, pauseBefore: true},
1295      {key: 'audio', getData: audioKey},
1296      {key: 'enumerateDevices', getData: enumerateDevicesKey}
1297    ]
1298  
1299    var Fingerprint2 = function (options) {
1300      throw new Error("'new Fingerprint()' is deprecated, see https://github.com/Valve/fingerprintjs2#upgrade-guide-from-182-to-200")
1301    }
1302  
1303    Fingerprint2.get = function (options, callback) {
1304      if (!callback) {
1305        callback = options
1306        options = {}
1307      } else if (!options) {
1308        options = {}
1309      }
1310      extendSoft(options, defaultOptions)
1311      options.components = options.extraComponents.concat(components)
1312  
1313      var keys = {
1314        data: [],
1315        addPreprocessedComponent: function (key, value) {
1316          if (typeof options.preprocessor === 'function') {
1317            value = options.preprocessor(key, value)
1318          }
1319          keys.data.push({key: key, value: value})
1320        }
1321      }
1322  
1323      var i = -1
1324      var chainComponents = function (alreadyWaited) {
1325        i += 1
1326        if (i >= options.components.length) { // on finish
1327          callback(keys.data)
1328          return
1329        }
1330        var component = options.components[i]
1331  
1332        if (options.excludes[component.key]) {
1333          chainComponents(false) // skip
1334          return
1335        }
1336  
1337        if (!alreadyWaited && component.pauseBefore) {
1338          i -= 1
1339          setTimeout(function () {
1340            chainComponents(true)
1341          }, 1)
1342          return
1343        }
1344  
1345        try {
1346          component.getData(function (value) {
1347            keys.addPreprocessedComponent(component.key, value)
1348            chainComponents(false)
1349          }, options)
1350        } catch (error) {
1351          // main body error
1352          keys.addPreprocessedComponent(component.key, String(error))
1353          chainComponents(false)
1354        }
1355      }
1356  
1357      chainComponents(false)
1358    }
1359  
1360    Fingerprint2.getPromise = function (options) {
1361      return new Promise(function (resolve, reject) {
1362        Fingerprint2.get(options, resolve)
1363      })
1364    }
1365  
1366    Fingerprint2.getV18 = function (options, callback) {
1367      if (callback == null) {
1368        callback = options
1369        options = {}
1370      }
1371      return Fingerprint2.get(options, function (components) {
1372        var newComponents = []
1373        for (var i = 0; i < components.length; i++) {
1374          var component = components[i]
1375          if (component.value === (options.NOT_AVAILABLE || 'not available')) {
1376            newComponents.push({key: component.key, value: 'unknown'})
1377          } else if (component.key === 'plugins') {
1378            newComponents.push({key: 'plugins',
1379              value: map(component.value, function (p) {
1380                var mimeTypes = map(p[2], function (mt) {
1381                  if (mt.join) { return mt.join('~') }
1382                  return mt
1383                }).join(',')
1384                return [p[0], p[1], mimeTypes].join('::')
1385              })})
1386          } else if (['canvas', 'webgl'].indexOf(component.key) !== -1) {
1387            newComponents.push({key: component.key, value: component.value.join('~')})
1388          } else if (['sessionStorage', 'localStorage', 'indexedDb', 'addBehavior', 'openDatabase'].indexOf(component.key) !== -1) {
1389            if (component.value) {
1390              newComponents.push({key: component.key, value: 1})
1391            } else {
1392              // skip
1393              continue
1394            }
1395          } else {
1396            if (component.value) {
1397              newComponents.push(component.value.join ? {key: component.key, value: component.value.join(';')} : component)
1398            } else {
1399              newComponents.push({key: component.key, value: component.value})
1400            }
1401          }
1402        }
1403        var murmur = x64hash128(map(newComponents, function (component) { return component.value }).join('~~~'), 31)
1404        callback(murmur, newComponents)
1405      })
1406    }
1407  
1408    Fingerprint2.x64hash128 = x64hash128
1409    Fingerprint2.VERSION = '2.0.0'
1410    return Fingerprint2
1411  })

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.