PageSourceSearch

https://tivu.co/themes/vidplay/js/Fingerprintjs2/fingerprint2.js

js tivu.co collected 2026-10-03 02:29:09 UTC 59,282 bytes, 1,411 lines download raw bytes

vendor: 6,532 bytes, lines 1-167
1/*
2* Fingerprintjs2 2.0.0 - 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
19(function (name, context, definition) {
20  'use strict'
21  if (typeof window !== 'undefined' && typeof window.define === 'function' && window.define.amd) { window.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,360 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: 16,243 bytes, lines 572-1006
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))
vendor: 15,017 bytes, lines 1026-1411
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.