爬虫自动化之定制浏览器随机指纹

admin 2025年1月11日14:52:12评论25 views字数 148356阅读494分31秒阅读模式

爬虫自动化之定制浏览器随机指纹

前言

大家好,我是本源

由于现在不够CB,改不了名字。emmm,大家觉得我写的好的记得给个评分支持下,让我尽快凑够买改名卡的CB......

最近这段时间在小肩膀那边学习了浏览器定制的相关课程,感受颇深,老师经常和我们讲学习逆向要努力勤奋。时刻保持利他之心。切勿骄傲自满,技术是要多分享交流才能获得提升,深受启发。

特地来分享一篇关于爬虫定制的浏览器相关的文章。如有不足,请指点~

1.为什么自动化爬取数据?自动化爬取有什么困境?

💡 Tips:为什么要用自动化来爬取数据?直接走协议不香吗?

网络爬虫要获取数据,最好最快的方法,自然是使用协议来进行模拟登录,获取数据等行为。但是现如今的网页端反爬虫措施越来越完善,爬虫和反爬虫的对抗已经进入了白热化,甚至可以说一天不学习就会落后很多。

相比于协议的反爬虫力度来说,自动化的压力要小很多,自动化来爬取数据速度只是比人手动快一些,比协议要慢的多。这也说明自动化爬虫对网站的伤害比较小。如果网络爬虫的要求仅仅是完成一些重复性工作,或者说只是爬取到数据就好,爬慢一点也无妨,使用自动化确实是一个好的方案(虽然爬虫界各大佬对自动化的鄙视是表露在外的)。

Python爬虫在进行自动化的时候,最常用的是selenium,因为不少网站会检测selenium的一些特征。更进一步,如果不使用selenium,而使用其他的自动化工具,也可以检测webdriver之类的自动化特征,或者干脆就看看你的JS操作的isTrust,如果是手动出发的就是True,JS触发的就是False。

此外,还有浏览器的指纹检测,将一台机器上相对稳定不变的参数作为指纹搜集,比如canvas,webgl,webAudio,SSL/TLS指纹等等,举一个浏览器指纹检测的例子https://gongjux.com/fingerprint/。

面对自动化反爬,许多人的处理方案就是使用油猴脚本,通过Hook来对一些参数获取进行劫持替换,我们接下来采取一种更底层的方式,直接取修改chromium浏览器的C++层代码。

当编译好一个自己修改过的chromium浏览器,并随机了指纹,可以采用两种方案进行数据爬取:

  1. 使用playwright进行浏览器驱动和自动化。

  2. 使用Windows的API进行浏览器的自动化,借助Fiddler之类的工具转发数据到本地服务。

但今天讲的文章里,我们主要探究如何自己定制修改chromium浏览器。  

特别鸣谢:感谢如意老师给予的指导

2.chromium浏览器的源码该如何编译?

💡 Tips:由于chromium的源码在不停地更新换代,所以如果按照网上的经验贴通常是无法编译成功的,所以最好的方式就是通过谷歌的官方文档进行编译。

官方文档地址:chromium编译官方文档  。

记住,你现在可以编译,不代表你以后也可以用这套环境编译最新的源码。别人可以编译,不代表你也能够编译。一切以官方文档为标准 。

编译chromium源码的系统要求:

  • 64位的电脑,而且至少有8G的内存,16G以上更好,多多益善。

  • 只编译chrome,需要至少100G的NTFS格式的磁盘存储空间,如果要整体编译,需要至少200G以上的存储空间。

  • 比较新的版本的visual studio,至少vs2017以及之后的版本。

  • Windows 10或者更新的操作系统。

满足以上条件就可以愉快地开始编译了。

(1)visual studio的配置

Chromium 需要Visual Studio 2017 (>=15.7.2) 才能构建,但最好使用Visual Studio 2019 (>=16.0.0)。本次编译示范使用的是vs2019的社区版。

爬虫自动化之定制浏览器随机指纹

这里必须要勾选<span class="ne-text">使用C++的桌面开发</span>其中的<span class="ne-text">MFC/ATL</span>组件。

此外,需要额外安装SDK10.0.20348.0。下载链接在这里:SDK下载地址。

安装SDK的时候,Debugging Tools是必须的,保险起见,还是全部勾选吧:

爬虫自动化之定制浏览器随机指纹

(2)安装depot_tools

下载depot_tools压缩包并解压,并将其配置到环境变量的开头,由于其中会涉及到Python环境变量,所以务必将它移动到最开始,以防Python环境变量配置错误。

配置环境变量的方式:控制面板→系统和安全→系统→高级系统设置:

爬虫自动化之定制浏览器随机指纹

配置成功后,到对应的depot_tools的目录之下,使用管理员方式打开CMD,运行命令<span class="ne-text">gclient</span>,这一步可能需要科学上网,务必保证你可以科学上网。

(3)环境变量

接下来,需要在环境变量中新增以下键值对,其中的GYP_MSVS_OVERRIDE_PATH和vs2019_install填写你自己的vs路径,GYP_MSVS_VERSION是对应的vs版本,WINDOWSSDKDIR是你的SDK安装的路径。

http_proxy和https_proxy是你的科学上网相关的代理。

 复制代码 隐藏代码
DEPOT_TOOLS_WIN_TOOLCHAIN=0
GYP_GENERATORS=msvs-ninja,ninja
GYP_MSVS_OVERRIDE_PATH=E:vs2019
GYP_MSVS_VERSION=2019               
vs2019_install=E:vs2019                                       
WINDOWSSDKDIR=C:Program Files (x86)Windows Kits10

http_proxy=127.0.0.1:7890                        
https_proxy=127.0.0.1:7890

(4)chromium源码拉取和编译

配置了这么多,终于到了源码拉取的部分,但是这也是最耗费时间的部分。

 复制代码 隐藏代码
mkdir chromium && cd chromium  # 任意创建一个目录接收源码fetch chromium --no-history    # 拉取最新源码,不带--no-history则拉取完整源码gclient sync --no-history      # 因为网络缘故断开,使用这一句重新同步源码

源码拉取完毕之后,当前目录下会多出一个src目录,源码就位于其中。

 复制代码 隐藏代码
cd src                        #进入目录gn gen --ide=vs outDefault          #在outDefault目录下生成工程文件autoninja -C outbuild mini_installer#编译源码,编译后生成chrome.exe和一个chrome.7z压缩文件#如果不带mini_installer将会进行整体编译,将近10w个文件,非常耗时

编译完成后,进入outDefault目录下:

爬虫自动化之定制浏览器随机指纹

到此为止,chromium最新版源码编译成功。

3.如何抹除自动化特征

💡 Tips:对于小白来说,修改chromium底层源码最简单的方式就是善用搜索。

总体来说,要定位修改的源码,一共分三步走。

  1. 知晓自己要修改的东西是什么。

  2. 定位源码中需要修改的地方。

  3. 修改源代码并回编译(回编译速度会很快,因为是增量的,不是重头开始编译)。

拿AiBot这个网站来说,网站地址:指纹检测网站

爬虫自动化之定制浏览器随机指纹

如果要绕过一般的自动化检测网站,最迫切的就是要抹除webdriver这一特征检测,因为检测一般都是检测 window.navigator.webdriver 属性,明白了要抹除的是webdriver,第一步就已经完成了。

不过,一定会有问在杠,比如他知道可以使用其他方法,比如selenium调用execute_cdp_cmd  的方式抹除自动化特征云云,我知道,我确实知道,我也可以把要执行的抹除特征的JS代码放在这里:

 复制代码 隐藏代码
/*!
* Note: Auto-generated, do not update manually.
* Generated by: https://github.com/berstend/puppeteer-extra/tree/master/packages/extract-stealth-evasions
* Generated on: Sun, 13 Feb 2022 12:56:05 GMT
* License: MIT
*/
(({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: 'utils => {n      if (!window.chrome) {n        // Use the exact property descriptor found in headful Chromen        // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n        Object.defineProperty(window, 'chrome', {n          writable: true,n          enumerable: true,n          configurable: false, // note!n          value: {} // We'll extend that latern        })n      }nn      // That means we're running headful and don't need to mock anythingn      if ('app' in window.chrome) {n        return // Nothing to do heren      }nn      const makeError = {n        ErrorInInvocation: fn => {n          const err = new TypeError(`Error in invocation of app.${fn}()`)n          return utils.stripErrorWithAnchor(n            err,n            `at ${fn} (eval at <anonymous>`n          )n        }n      }nn      // There's a some static data in that property which doesn't seem to change,n      // we should periodically check for updates: `JSON.stringify(window.app, null, 2)`n      const STATIC_DATA = JSON.parse(n        `n{n  "isInstalled": false,n  "InstallState": {n    "DISABLED": "disabled",n    "INSTALLED": "installed",n    "NOT_INSTALLED": "not_installed"n  },n  "RunningState": {n    "CANNOT_RUN": "cannot_run",n    "READY_TO_RUN": "ready_to_run",n    "RUNNING": "running"n  }n}n        `.trim()n      )nn      window.chrome.app = {n        ...STATIC_DATA,nn        get isInstalled() {n          return falsen        },nn        getDetails: function getDetails() {n          if (arguments.length) {n            throw makeError.ErrorInInvocation(`getDetails`)n          }n          return nulln        },n        getIsInstalled: function getDetails() {n          if (arguments.length) {n            throw makeError.ErrorInInvocation(`getIsInstalled`)n          }n          return falsen        },n        runningState: function getDetails() {n          if (arguments.length) {n            throw makeError.ErrorInInvocation(`runningState`)n          }n          return 'cannot_run'n        }n      }n      utils.patchToStringNested(window.chrome.app)n    }',
  _args: []
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "utils => {n      if (!window.chrome) {n        // Use the exact property descriptor found in headful Chromen        // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n        Object.defineProperty(window, 'chrome', {n          writable: true,n          enumerable: true,n          configurable: false, // note!n          value: {} // We'll extend that latern        })n      }nn      // That means we're running headful and don't need to mock anythingn      if ('csi' in window.chrome) {n        return // Nothing to do heren      }nn      // Check that the Navigation Timing API v1 is available, we need thatn      if (!window.performance || !window.performance.timing) {n        returnn      }nn      const { timing } = window.performancenn      window.chrome.csi = function() {n        return {n          onloadT: timing.domContentLoadedEventEnd,n          startE: timing.navigationStart,n          pageT: Date.now() - timing.navigationStart,n          tran: 15 // Transition type or somethingn        }n      }n      utils.patchToString(window.chrome.csi)n    }",
  _args: []
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, { opts }) => {n        if (!window.chrome) {n          // Use the exact property descriptor found in headful Chromen          // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n          Object.defineProperty(window, 'chrome', {n            writable: true,n            enumerable: true,n            configurable: false, // note!n            value: {} // We'll extend that latern          })n        }nn        // That means we're running headful and don't need to mock anythingn        if ('loadTimes' in window.chrome) {n          return // Nothing to do heren        }nn        // Check that the Navigation Timing API v1 + v2 is available, we need thatn        if (n          !window.performance ||n          !window.performance.timing ||n          !window.PerformancePaintTimingn        ) {n          returnn        }nn        const { performance } = windownn        // Some stuff is not available on about:blank as it requires a navigation to occur,n        // let's harden the code to not fail then:n        const ntEntryFallback = {n          nextHopProtocol: 'h2',n          type: 'other'n        }nn        // The API exposes some funky info regarding the connectionn        const protocolInfo = {n          get connectionInfo() {n            const ntEntry =n              performance.getEntriesByType('navigation')[0] || ntEntryFallbackn            return ntEntry.nextHopProtocoln          },n          get npnNegotiatedProtocol() {n            // NPN is deprecated in favor of ALPN, but this implementation returns then            // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.n            const ntEntry =n              performance.getEntriesByType('navigation')[0] || ntEntryFallbackn            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)n              ? ntEntry.nextHopProtocoln              : 'unknown'n          },n          get navigationType() {n            const ntEntry =n              performance.getEntriesByType('navigation')[0] || ntEntryFallbackn            return ntEntry.typen          },n          get wasAlternateProtocolAvailable() {n            // The Alternate-Protocol header is deprecated in favor of Alt-Svcn            // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically thisn            // should always return false.n            return falsen          },n          get wasFetchedViaSpdy() {n            // SPDY is deprecated in favor of HTTP/2, but this implementation returnsn            // true for HTTP/2 or HTTP2+QUIC/39 as well.n            const ntEntry =n              performance.getEntriesByType('navigation')[0] || ntEntryFallbackn            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)n          },n          get wasNpnNegotiated() {n            // NPN is deprecated in favor of ALPN, but this implementation returns truen            // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.n            const ntEntry =n              performance.getEntriesByType('navigation')[0] || ntEntryFallbackn            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)n          }n        }nn        const { timing } = window.performancenn        // Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3n        function toFixed(num, fixed) {n          var re = new RegExp('^-?\\d+(?:.\\d{0,' + (fixed || -1) + '})?')n          return num.toString().match(re)[0]n        }nn        const timingInfo = {n          get firstPaintAfterLoadTime() {n            // This was never actually implemented and always returns 0.n            return 0n          },n          get requestTime() {n            return timing.navigationStart / 1000n          },n          get startLoadTime() {n            return timing.navigationStart / 1000n          },n          get commitLoadTime() {n            return timing.responseStart / 1000n          },n          get finishDocumentLoadTime() {n            return timing.domContentLoadedEventEnd / 1000n          },n          get finishLoadTime() {n            return timing.loadEventEnd / 1000n          },n          get firstPaintTime() {n            const fpEntry = performance.getEntriesByType('paint')[0] || {n              startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`)n            }n            return toFixed(n              (fpEntry.startTime + performance.timeOrigin) / 1000,n              3n            )n          }n        }nn        window.chrome.loadTimes = function() {n          return {n            ...protocolInfo,n            ...timingInfon          }n        }n        utils.patchToString(window.chrome.loadTimes)n      }",
  _args: [{opts: {}}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, { opts, STATIC_DATA }) => {n        if (!window.chrome) {n          // Use the exact property descriptor found in headful Chromen          // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`n          Object.defineProperty(window, 'chrome', {n            writable: true,n            enumerable: true,n            configurable: false, // note!n            value: {} // We'll extend that latern          })n        }nn        // That means we're running headful and don't need to mock anythingn        const existsAlready = 'runtime' in window.chromen        // `chrome.runtime` is only exposed on secure originsn        const isNotSecure = !window.location.protocol.startsWith('https')n        if (existsAlready || (isNotSecure && !opts.runOnInsecureOrigins)) {n          return // Nothing to do heren        }nn        window.chrome.runtime = {n          // There's a bunch of static data in that property which doesn't seem to change,n          // we should periodically check for updates: `JSON.stringify(window.chrome.runtime, null, 2)`n          ...STATIC_DATA,n          // `chrome.runtime.id` is extension related and returns undefined in Chromen          get id() {n            return undefinedn          },n          // These two require more sophisticated mocksn          connect: null,n          sendMessage: nulln        }nn        const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({n          NoMatchingSignature: new TypeError(n            preamble + `No matching signature.`n          ),n          MustSpecifyExtensionID: new TypeError(n            preamble +n              `${method} called from a webpage must specify an Extension ID (string) for its first argument.`n          ),n          InvalidExtensionID: new TypeError(n            preamble + `Invalid extension id: '${extensionId}'`n          )n        })nn        // Valid Extension IDs are 32 characters in length and use the letter `a` to `p`:n        // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90n        const isValidExtensionID = str =>n          str.length === 32 && str.toLowerCase().match(/^[a-p]+$/)nn        /** Mock `chrome.runtime.sendMessage` */n        const sendMessageHandler = {n          apply: function(target, ctx, args) {n            const [extensionId, options, responseCallback] = args || []nn            // Define custom errorsn            const errorPreamble = `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): `n            const Errors = makeCustomRuntimeErrors(n              errorPreamble,n              `chrome.runtime.sendMessage()`,n              extensionIdn            )nn            // Check if the call signature looks okn            const noArguments = args.length === 0n            const tooManyArguments = args.length > 4n            const incorrectOptions = options && typeof options !== 'object'n            const incorrectResponseCallback =n              responseCallback && typeof responseCallback !== 'function'n            if (n              noArguments ||n              tooManyArguments ||n              incorrectOptions ||n              incorrectResponseCallbackn            ) {n              throw Errors.NoMatchingSignaturen            }nn            // At least 2 arguments are required before we even validate the extension IDn            if (args.length < 2) {n              throw Errors.MustSpecifyExtensionIDn            }nn            // Now let's make sure we got a string as extension IDn            if (typeof extensionId !== 'string') {n              throw Errors.NoMatchingSignaturen            }nn            if (!isValidExtensionID(extensionId)) {n              throw Errors.InvalidExtensionIDn            }nn            return undefined // Normal behaviorn          }n        }n        utils.mockWithProxy(n          window.chrome.runtime,n          'sendMessage',n          function sendMessage() {},n          sendMessageHandlern        )nn        /**n         * Mock `chrome.runtime.connect`n         *n         * home.php?mod=space&uid=2660 https://developer.chrome.com/apps/runtime#method-connectn         */n        const connectHandler = {n          apply: function(target, ctx, args) {n            const [extensionId, connectInfo] = args || []nn            // Define custom errorsn            const errorPreamble = `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): `n            const Errors = makeCustomRuntimeErrors(n              errorPreamble,n              `chrome.runtime.connect()`,n              extensionIdn            )nn            // Behavior differs a bit from sendMessage:n            const noArguments = args.length === 0n            const emptyStringArgument = args.length === 1 && extensionId === ''n            if (noArguments || emptyStringArgument) {n              throw Errors.MustSpecifyExtensionIDn            }nn            const tooManyArguments = args.length > 2n            const incorrectConnectInfoType =n              connectInfo && typeof connectInfo !== 'object'nn            if (tooManyArguments || incorrectConnectInfoType) {n              throw Errors.NoMatchingSignaturen            }nn            const extensionIdIsString = typeof extensionId === 'string'n            if (extensionIdIsString && extensionId === '') {n              throw Errors.MustSpecifyExtensionIDn            }n            if (extensionIdIsString && !isValidExtensionID(extensionId)) {n              throw Errors.InvalidExtensionIDn            }nn            // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validaten            const validateConnectInfo = ci => {n              // More than a first param connectInfo as been providedn              if (args.length > 1) {n                throw Errors.NoMatchingSignaturen              }n              // An empty connectInfo has been providedn              if (Object.keys(ci).length === 0) {n                throw Errors.MustSpecifyExtensionIDn              }n              // Loop over all connectInfo props an check themn              Object.entries(ci).forEach(([k, v]) => {n                const isExpected = ['name', 'includeTlsChannelId'].includes(k)n                if (!isExpected) {n                  throw new TypeError(n                    errorPreamble + `Unexpected property: '${k}'.`n                  )n                }n                const MismatchError = (propName, expected, found) =>n                  TypeError(n                    errorPreamble +n                      `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.`n                  )n                if (k === 'name' && typeof v !== 'string') {n                  throw MismatchError(k, 'string', typeof v)n                }n                if (k === 'includeTlsChannelId' && typeof v !== 'boolean') {n                  throw MismatchError(k, 'boolean', typeof v)n                }n              })n            }n            if (typeof extensionId === 'object') {n              validateConnectInfo(extensionId)n              throw Errors.MustSpecifyExtensionIDn            }nn            // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as welln            return utils.patchToStringNested(makeConnectResponse())n          }n        }n        utils.mockWithProxy(n          window.chrome.runtime,n          'connect',n          function connect() {},n          connectHandlern        )nn        function makeConnectResponse() {n          const onSomething = () => ({n            addListener: function addListener() {},n            dispatch: function dispatch() {},n            hasListener: function hasListener() {},n            hasListeners: function hasListeners() {n              return falsen            },n            removeListener: function removeListener() {}n          })nn          const response = {n            name: '',n            sender: undefined,n            disconnect: function disconnect() {},n            onDisconnect: onSomething(),n            onMessage: onSomething(),n            postMessage: function postMessage() {n              if (!arguments.length) {n                throw new TypeError(`Insufficient number of arguments.`)n              }n              throw new Error(`Attempting to use a disconnected port object`)n            }n          }n          return responsen        }n      }",
  _args: [{
    opts: {runOnInsecureOrigins: !1},
    STATIC_DATA: {
      OnInstalledReason: {
        CHROME_UPDATE: "chrome_update",
        INSTALL: "install",
        SHARED_MODULE_UPDATE: "shared_module_update",
        UPDATE: "update"      },
      OnRestartRequiredReason: {APP_UPDATE: "app_update", OS_UPDATE: "os_update", PERIODIC: "periodic"},
      PlatformArch: {
        ARM: "arm",
        ARM64: "arm64",
        MIPS: "mips",
        MIPS64: "mips64",
        X86_32: "x86-32",
        X86_64: "x86-64"      },
      PlatformNaclArch: {ARM: "arm", MIPS: "mips", MIPS64: "mips64", X86_32: "x86-32", X86_64: "x86-64"},
      PlatformOs: {ANDROID: "android", CROS: "cros", LINUX: "linux", MAC: "mac", OPENBSD: "openbsd", WIN: "win"},
      RequestUpdateCheckStatus: {
        NO_UPDATE: "no_update",
        THROTTLED: "throttled",
        UPDATE_AVAILABLE: "update_available"      }
    }
  }]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "utils => {n      /**n       * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.n       *n       * @examplen       * video/webm; codecs="vp8, vorbis"n       * video/mp4; codecs="avc1.42E01E"n       * audio/x-m4a;n       * audio/ogg; codecs="vorbis"n       * home.php?mod=space&uid=952169 {String} argn       */n      const parseInput = arg => {n        const [mime, codecStr] = arg.trim().split(';')n        let codecs = []n        if (codecStr && codecStr.includes('codecs="')) {n          codecs = codecStrn            .trim()n            .replace(`codecs="`, '')n            .replace(`"`, '')n            .trim()n            .split(',')n            .filter(x => !!x)n            .map(x => x.trim())n        }n        return {n          mime,n          codecStr,n          codecsn        }n      }nn      const canPlayType = {n        // Intercept certain requestsn        apply: function(target, ctx, args) {n          if (!args || !args.length) {n            return target.apply(ctx, args)n          }n          const { mime, codecs } = parseInput(args[0])n          // This specific mp4 codec is missing in Chromiumn          if (mime === 'video/mp4') {n            if (codecs.includes('avc1.42E01E')) {n              return 'probably'n            }n          }n          // This mimetype is only supported if no codecs are specifiedn          if (mime === 'audio/x-m4a' && !codecs.length) {n            return 'maybe'n          }nn          // This mimetype is only supported if no codecs are specifiedn          if (mime === 'audio/aac' && !codecs.length) {n            return 'probably'n          }n          // Everything else as usualn          return target.apply(ctx, args)n        }n      }nn      /* global HTMLMediaElement */n      utils.replaceWithProxy(n        HTMLMediaElement.prototype,n        'canPlayType',n        canPlayTypen      )n    }",
  _args: []
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, { opts }) => {n        utils.replaceGetterWithProxy(n          Object.getPrototypeOf(navigator),n          'hardwareConcurrency',n          utils.makeHandler().getterValue(opts.hardwareConcurrency)n        )n      }",
  _args: [{opts: {hardwareConcurrency: 4}}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, { opts }) => {n        const languages = opts.languages.lengthn          ? opts.languagesn          : ['en-US', 'en']n        utils.replaceGetterWithProxy(n          Object.getPrototypeOf(navigator),n          'languages',n          utils.makeHandler().getterValue(Object.freeze([...languages]))n        )n      }",
  _args: [{opts: {languages: []}}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, opts) => {n      const isSecure = document.location.protocol.startsWith('https')nn      // In headful on secure origins the permission should be "default", not "denied"n      if (isSecure) {n        utils.replaceGetterWithProxy(Notification, 'permission', {n          apply() {n            return 'default'n          }n        })n      }nn      // Another weird behavior:n      // On insecure origins in headful the state is "denied",n      // whereas in headless it's "prompt"n      if (!isSecure) {n        const handler = {n          apply(target, ctx, args) {n            const param = (args || [])[0]nn            const isNotifications =n              param && param.name && param.name === 'notifications'n            if (!isNotifications) {n              return utils.cache.Reflect.apply(...arguments)n            }nn            return Promise.resolve(n              Object.setPrototypeOf(n                {n                  state: 'denied',n                  onchange: nulln                },n                PermissionStatus.prototypen              )n            )n          }n        }n        // Note: Don't use `Object.getPrototypeOf` heren        utils.replaceWithProxy(Permissions.prototype, 'query', handler)n      }n    }",
  _args: [{}]
}), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, { fns, data }) => {n        fns = utils.materializeFns(fns)nn        // That means we're running headfuln        const hasPlugins = 'plugins' in navigator && navigator.plugins.lengthn        if (hasPlugins) {n          return // nothing to do heren        }nn        const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes)n        const plugins = fns.generatePluginArray(utils, fns)(data.plugins)nn        // Plugin and MimeType cross-reference each other, let's do that nown        // Note: We're looping through `data.plugins` here, not the generated `plugins`n        for (const pluginData of data.plugins) {n          pluginData.__mimeTypes.forEach((type, index) => {n            plugins[pluginData.name][index] = mimeTypes[type]nn            Object.defineProperty(plugins[pluginData.name], type, {n              value: mimeTypes[type],n              writable: false,n              enumerable: false, // Not enumerablen              configurable: truen            })n            Object.defineProperty(mimeTypes[type], 'enabledPlugin', {n              value:n                type === 'application/x-pnacl'n                  ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaksn                  : new Proxy(plugins[pluginData.name], {}), // Prevent circular referencesn              writable: false,n              enumerable: false, // Important: `JSON.stringify(navigator.plugins)`n              configurable: truen            })n          })n        }nn        const patchNavigator = (name, value) =>n          utils.replaceProperty(Object.getPrototypeOf(navigator), name, {n            get() {n              return valuen            }n          })nn        patchNavigator('mimeTypes', mimeTypes)n        patchNavigator('plugins', plugins)nn        // All donen      }",
  _args: [{
    fns: {
      generateMimeTypeArray: "(utils, fns) => mimeTypesData => {n  return fns.generateMagicArray(utils, fns)(n    mimeTypesData,n    MimeTypeArray.prototype,n    MimeType.prototype,n    'type'n  )n}",
      generatePluginArray: "(utils, fns) => pluginsData => {n  return fns.generateMagicArray(utils, fns)(n    pluginsData,n    PluginArray.prototype,n    Plugin.prototype,n    'name'n  )n}",
      generateMagicArray: "(utils, fns) =>n  function(n    dataArray = [],n    proto = MimeTypeArray.prototype,n    itemProto = MimeType.prototype,n    itemMainProp = 'type'n  ) {n    // Quick helper to set props with the same descriptors vanilla is usingn    const defineProp = (obj, prop, value) =>n      Object.defineProperty(obj, prop, {n        value,n        writable: false,n        enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)`n        configurable: truen      })nn    // Loop over our fake data and construct itemsn    const makeItem = data => {n      const item = {}n      for (const prop of Object.keys(data)) {n        if (prop.startsWith('__')) {n          continuen        }n        defineProp(item, prop, data[prop])n      }n      return patchItem(item, data)n    }nn    const patchItem = (item, data) => {n      let descriptor = Object.getOwnPropertyDescriptors(item)nn      // Special case: Plugins have a magic length property which is not enumerablen      // e.g. `navigator.plugins[i].length` should always be the length of the assigned mimeTypesn      if (itemProto === Plugin.prototype) {n        descriptor = {n          ...descriptor,n          length: {n            value: data.__mimeTypes.length,n            writable: false,n            enumerable: false,n            configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`n          }n        }n      }nn      // We need to spoof a specific `MimeType` or `Plugin` objectn      const obj = Object.create(itemProto, descriptor)nn      // Virtually all property keys are not enumerable in vanillan      const blacklist = [...Object.keys(data), 'length', 'enabledPlugin']n      return new Proxy(obj, {n        ownKeys(target) {n          return Reflect.ownKeys(target).filter(k => !blacklist.includes(k))n        },n        getOwnPropertyDescriptor(target, prop) {n          if (blacklist.includes(prop)) {n            return undefinedn          }n          return Reflect.getOwnPropertyDescriptor(target, prop)n        }n      })n    }nn    const magicArray = []nn    // Loop through our fake data and use that to create convincing entitiesn    dataArray.forEach(data => {n      magicArray.push(makeItem(data))n    })nn    // Add direct property access  based on types (e.g. `obj['application/pdf']`) afterwardsn    magicArray.forEach(entry => {n      defineProp(magicArray, entry[itemMainProp], entry)n    })nn    // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)`n    const magicArrayObj = Object.create(proto, {n      ...Object.getOwnPropertyDescriptors(magicArray),nn      // There's one ugly quirk we unfortunately need to take care of:n      // The `MimeTypeArray` prototype has an enumerable `length` property,n      // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`.n      // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap.n      length: {n        value: magicArray.length,n        writable: false,n        enumerable: false,n        configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`n      }n    })nn    // Generate our functional function mocks :-)n    const functionMocks = fns.generateFunctionMocks(utils)(n      proto,n      itemMainProp,n      magicArrayn    )nn    // We need to overlay our custom object with a JS Proxyn    const magicArrayObjProxy = new Proxy(magicArrayObj, {n      get(target, key = '') {n        // Redirect function calls to our custom proxied versions mocking the vanilla behaviorn        if (key === 'item') {n          return functionMocks.itemn        }n        if (key === 'namedItem') {n          return functionMocks.namedItemn        }n        if (proto === PluginArray.prototype && key === 'refresh') {n          return functionMocks.refreshn        }n        // Everything else can pass through as normaln        return utils.cache.Reflect.get(...arguments)n      },n      ownKeys(target) {n        // There are a couple of quirks where the original property demonstrates "magical" behavior that makes no sensen        // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length`n        // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdlyn        // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doingn        // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missingn        const keys = []n        const typeProps = magicArray.map(mt => mt[itemMainProp])n        typeProps.forEach((_, i) => keys.push(`${i}`))n        typeProps.forEach(propName => keys.push(propName))n        return keysn      },n      getOwnPropertyDescriptor(target, prop) {n        if (prop === 'length') {n          return undefinedn        }n        return Reflect.getOwnPropertyDescriptor(target, prop)n      }n    })nn    return magicArrayObjProxyn  }",
      generateFunctionMocks: "utils => (n  proto,n  itemMainProp,n  dataArrayn) => ({n  /** Returns the MimeType object with the specified index. */n  item: utils.createProxy(proto.item, {n    apply(target, ctx, args) {n      if (!args.length) {n        throw new TypeError(n          `Failed to execute 'item' on '${n            proto[Symbol.toStringTag]n          }': 1 argument required, but only 0 present.`n        )n      }n      // Special behavior alert:n      // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookupn      // - If anything else than an integer (including as string) is provided it will return the first entryn      const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integern      // Note: Vanilla never returns `undefined`n      return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || nulln    }n  }),n  /** Returns the MimeType object with the specified name. */n  namedItem: utils.createProxy(proto.namedItem, {n    apply(target, ctx, args) {n      if (!args.length) {n        throw new TypeError(n          `Failed to execute 'namedItem' on '${n            proto[Symbol.toStringTag]n          }': 1 argument required, but only 0 present.`n        )n      }n      return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not `undefined`!n    }n  }),n  /** Does nothing and shall return nothing */n  refresh: proto.refreshn    ? utils.createProxy(proto.refresh, {n        apply(target, ctx, args) {n          return undefinedn        }n      })n    : undefinedn})"    },
    data: {
      mimeTypes: [{
        type: "application/pdf",
        suffixes: "pdf",
        description: "",
        __pluginName: "Chrome PDF Viewer"      }, {
        type: "application/x-google-chrome-pdf",
        suffixes: "pdf",
        description: "Portable Document Format",
        __pluginName: "Chrome PDF Plugin"      }, {
        type: "application/x-nacl",
        suffixes: "",
        description: "Native Client Executable",
        __pluginName: "Native Client"      }, {
        type: "application/x-pnacl",
        suffixes: "",
        description: "Portable Native Client Executable",
        __pluginName: "Native Client"      }],
      plugins: [{
        name: "Chrome PDF Plugin",
        filename: "internal-pdf-viewer",
        description: "Portable Document Format",
        __mimeTypes: ["application/x-google-chrome-pdf"]
      }, {
        name: "Chrome PDF Viewer",
        filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai",
        description: "",
        __mimeTypes: ["application/pdf"]
      }, {
        name: "Native Client",
        filename: "internal-nacl-plugin",
        description: "",
        __mimeTypes: ["application/x-nacl", "application/x-pnacl"]
      }]
    }
  }]
}), !1 === navigator.webdriver || void 0 === navigator.webdriver || delete Object.getPrototypeOf(navigator).webdriver, (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, opts) => {n      const getParameterProxyHandler = {n        apply: function(target, ctx, args) {n          const param = (args || [])[0]n          const result = utils.cache.Reflect.apply(target, ctx, args)n          // UNMASKED_VENDOR_WEBGLn          if (param === 37445) {n            return opts.vendor || 'Intel Inc.' // default in headless: Google Inc.n          }n          // UNMASKED_RENDERER_WEBGLn          if (param === 37446) {n            return opts.renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShadern          }n          return resultn        }n      }nn      // There's more than one WebGL rendering contextn      // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibilityn      // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter)n      const addProxy = (obj, propName) => {n        utils.replaceWithProxy(obj, propName, getParameterProxyHandler)n      }n      // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing:n      addProxy(WebGLRenderingContext.prototype, 'getParameter')n      addProxy(WebGL2RenderingContext.prototype, 'getParameter')n    }",
  _args: [{}]
}), (() => {
  try {
    if (window.outerWidth && window.outerHeight) return;
    const n = 85;
    window.outerWidth = window.innerWidth, window.outerHeight = window.innerHeight + n
  } catch (n) {
  }
})(), (({_utilsFns: _utilsFns, _mainFunction: _mainFunction, _args: _args}) => {
  const utils = Object.fromEntries(Object.entries(_utilsFns).map((([key, value]) => [key, eval(value)])));
  utils.init(), eval(_mainFunction)(utils, ..._args)
})({
  _utilsFns: {
    init: "() => {n  utils.preloadCache()n}",
    stripProxyFromErrors: "(handler = {}) => {n  const newHandler = {n    setPrototypeOf: function (target, proto) {n      if (proto === null)n        throw new TypeError('Cannot convert object to primitive value')n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {n        throw new TypeError('Cyclic __proto__ value')n      }n      return Reflect.setPrototypeOf(target, proto)n    }n  }n  // We wrap each trap in the handler in a try/catch and modify the error stack if they thrown  const traps = Object.getOwnPropertyNames(handler)n  traps.forEach(trap => {n    newHandler[trap] = function () {n      try {n        // Forward the call to the defined proxy handlern        return handler[trap].apply(this, arguments || [])n      } catch (err) {n        // Stack traces differ per browser, we only support chromium based ones currentlyn        if (!err || !err.stack || !err.stack.includes(`at `)) {n          throw errn        }nn        // When something throws within one of our traps the Proxy will show up in error stacksn        // An earlier implementation of this code would simply strip lines with a blacklist,n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.n        // We try to use a known "anchor" line for that and strip it with everything above it.n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.nn        const stripWithBlacklist = (stack, stripFirstLine = true) => {n          const blacklist = [n            `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.applyn            `at Object.${trap} `, // e.g. Object.get or Object.applyn            `at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)n          ]n          return (n            err.stackn              .split('\n')n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)n              .filter((line, index) => !(index === 1 && stripFirstLine))n              // Check if the line starts with one of our blacklisted stringsn              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))n              .join('\n')n          )n        }nn        const stripWithAnchor = (stack, anchor) => {n          const stackArr = stack.split('\n')n          anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromiumn          const anchorIndex = stackArr.findIndex(line =>n            line.trim().startsWith(anchor)n          )n          if (anchorIndex === -1) {n            return false // 404, anchor not foundn          }n          // Strip everything from the top until we reach the anchor linen          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n          stackArr.splice(1, anchorIndex)n          return stackArr.join('\n')n        }nn        // Special cases due to our nested toString proxiesn        err.stack = err.stack.replace(n          'at Object.toString (',n          'at Function.toString ('n        )n        if ((err.stack || '').includes('at Function.toString (')) {n          err.stack = stripWithBlacklist(err.stack, false)n          throw errn        }nn        // Try using the anchor method, fallback to blacklist if necessaryn        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)nn        throw err // Re-throw our now sanitized errorn      }n    }n  })n  return newHandlern}",
    stripErrorWithAnchor: "(err, anchor) => {n  const stackArr = err.stack.split('\n')n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))n  if (anchorIndex === -1) {n    return err // 404, anchor not foundn  }n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)n  stackArr.splice(1, anchorIndex)n  err.stack = stackArr.join('\n')n  return errn}",
    replaceProperty: "(obj, propName, descriptorOverrides = {}) => {n  return Object.defineProperty(obj, propName, {n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),n    // Add our overrides (e.g. value, get())n    ...descriptorOverridesn  })n}",
    preloadCache: "() => {n  if (utils.cache) {n    returnn  }n  utils.cache = {n    // Used in our proxiesn    Reflect: {n      get: Reflect.get.bind(Reflect),n      apply: Reflect.apply.bind(Reflect)n    },n    // Used in `makeNativeString`n    nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`n  }n}",
    makeNativeString: "(name = '') => {n  return utils.cache.nativeToStringStr.replace('toString', name || '')n}",
    patchToString: "(obj, str = '') => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }n      // `toString` targeted at our proxied Object detectedn      if (ctx === obj) {n        // We either return the optional string verbatim or derive the most desired result automaticallyn        return str || utils.makeNativeString(obj.name)n      }n      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }n      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    patchToStringNested: "(obj = {}) => {n  return utils.execRecursively(obj, ['function'], utils.patchToString)n}",
    redirectToString: "(proxyObj, originalObj) => {n  const handler = {n    apply: function (target, ctx) {n      // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`n      if (ctx === Function.prototype.toString) {n        return utils.makeNativeString('toString')n      }nn      // `toString` targeted at our proxied Object detectedn      if (ctx === proxyObj) {n        const fallback = () =>n          originalObj && originalObj.namen            ? utils.makeNativeString(originalObj.name)n            : utils.makeNativeString(proxyObj.name)nn        // Return the toString representation of our original object if possiblen        return originalObj + '' || fallback()n      }nn      if (typeof ctx === 'undefined' || ctx === null) {n        return target.call(ctx)n      }nn      // Check if the toString protype of the context is the same as the global prototype,n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test casen      const hasSameProto = Object.getPrototypeOf(n        Function.prototype.toStringn      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtinsn      if (!hasSameProto) {n        // Pass the call on to the local Function.prototype.toString insteadn        return ctx.toString()n      }nn      return target.call(ctx)n    }n  }nn  const toStringProxy = new Proxy(n    Function.prototype.toString,n    utils.stripProxyFromErrors(handler)n  )n  utils.replaceProperty(Function.prototype, 'toString', {n    value: toStringProxyn  })n}",
    replaceWithProxy: "(obj, propName, handler) => {n  const originalObj = obj[propName]n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.redirectToString(proxyObj, originalObj)nn  return truen}",
    replaceGetterWithProxy: "(obj, propName, handler) => {n  const fn = Object.getOwnPropertyDescriptor(obj, propName).getn  const fnStr = fn.toString() // special getter function stringn  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { get: proxyObj })n  utils.patchToString(proxyObj, fnStr)nn  return truen}",
    mockWithProxy: "(obj, propName, pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))nn  utils.replaceProperty(obj, propName, { value: proxyObj })n  utils.patchToString(proxyObj)nn  return truen}",
    createProxy: "(pseudoTarget, handler) => {n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))n  utils.patchToString(proxyObj)nn  return proxyObjn}",
    splitObjPath: "objPath => ({n  // Remove last dot entry (property) ==> `HTMLMediaElement.prototype`n  objName: objPath.split('.').slice(0, -1).join('.'),n  // Extract last dot entry ==> `canPlayType`n  propName: objPath.split('.').slice(-1)[0]n})",
    replaceObjPathWithProxy: "(objPath, handler) => {n  const { objName, propName } = utils.splitObjPath(objPath)n  const obj = eval(objName) // eslint-disable-line no-evaln  return utils.replaceWithProxy(obj, propName, handler)n}",
    execRecursively: "(obj = {}, typeFilter = [], fn) => {n  function recurse(obj) {n    for (const key in obj) {n      if (obj[key] === undefined) {n        continuen      }n      if (obj[key] && typeof obj[key] === 'object') {n        recurse(obj[key])n      } else {n        if (obj[key] && typeFilter.includes(typeof obj[key])) {n          fn.call(this, obj[key])n        }n      }n    }n  }n  recurse(obj)n  return objn}",
    stringifyFns: "(fnObj = { hello: () => 'world' }) => {n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are finen  // https://github.com/feross/fromentriesn  function fromEntries(iterable) {n    return [...iterable].reduce((obj, [key, val]) => {n      obj[key] = valn      return objn    }, {})n  }n  return (Object.fromEntries || fromEntries)(n    Object.entries(fnObj)n      .filter(([key, value]) => typeof value === 'function')n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-evaln  )n}",
    materializeFns: "(fnStrObj = { hello: "() => 'world'" }) => {n  return Object.fromEntries(n    Object.entries(fnStrObj).map(([key, value]) => {n      if (value.startsWith('function')) {n        // some trickery is needed to make oldschool functions work :-)n        return [key, eval(`() => ${value}`)()] // eslint-disable-line no-evaln      } else {n        // arrow functions just workn        return [key, eval(value)] // eslint-disable-line no-evaln      }n    })n  )n}",
    makeHandler: "() => ({n  // Used by simple `navigator` getter evasionsn  getterValue: value => ({n    apply(target, ctx, args) {n      // Let's fetch the value first, to trigger and escalate potential errorsn      // Illegal invocations like `navigator.__proto__.vendor` will throw heren      utils.cache.Reflect.apply(...arguments)n      return valuen    }n  })n})"  },
  _mainFunction: "(utils, opts) => {n      try {n        // Adds a contentWindow proxy to the provided iframe elementn        const addContentWindowProxy = iframe => {n          const contentWindowProxy = {n            get(target, key) {n              // Now to the interesting part:n              // We actually make this thing behave like a regular iframe window,n              // by intercepting calls to e.g. `.self` and redirect it to the correct thing. :)n              // That makes it possible for these assertions to be correct:n              // iframe.contentWindow.self === window.top // must be falsen              if (key === 'self') {n                return thisn              }n              // iframe.contentWindow.frameElement === iframe // must be truen              if (key === 'frameElement') {n                return iframen              }n              // Intercept iframe.contentWindow[0] to hide the property 0 added by the proxy.n              if (key === '0') {n                  return undefinedn              }n              return Reflect.get(target, key)n            }n          }nn          if (!iframe.contentWindow) {n            const proxy = new Proxy(window, contentWindowProxy)n            Object.defineProperty(iframe, 'contentWindow', {n              get() {n                return proxyn              },n              set(newValue) {n                return newValue // contentWindow is immutablen              },n              enumerable: true,n              configurable: falsen            })n          }n        }nn        // Handles iframe element creation, augments `srcdoc` property so we can intercept furthern        const handleIframeCreation = (target, thisArg, args) => {n          const iframe = target.apply(thisArg, args)nn          // We need to keep the originals aroundn          const _iframe = iframen          const _srcdoc = _iframe.srcdocnn          // Add hook for the srcdoc propertyn          // We need to be very surgical here to not break other iframes by accidentn          Object.defineProperty(iframe, 'srcdoc', {n            configurable: true, // Important, so we can reset this latern            get: function() {n              return _srcdocn            },n            set: function(newValue) {n              addContentWindowProxy(this)n              // Reset property, the hook is only needed oncen              Object.defineProperty(iframe, 'srcdoc', {n                configurable: false,n                writable: false,n                value: _srcdocn              })n              _iframe.srcdoc = newValuen            }n          })n          return iframen        }nn        // Adds a hook to intercept iframe creation eventsn        const addIframeCreationSniffer = () => {n          /* global document */n          const createElementHandler = {n            // Make toString() nativen            get(target, key) {n              return Reflect.get(target, key)n            },n            apply: function(target, thisArg, args) {n              const isIframe =n                args && args.length && `${args[0]}`.toLowerCase() === 'iframe'n              if (!isIframe) {n                // Everything as usualn                return target.apply(thisArg, args)n              } else {n                return handleIframeCreation(target, thisArg, args)n              }n            }n          }n          // All this just due to iframes with srcdoc bugn          utils.replaceWithProxy(n            document,n            'createElement',n            createElementHandlern          )n        }nn        // Let's gon        addIframeCreationSniffer()n      } catch (err) {n        // console.warn(err)n      }n    }",
  _args: []
});

但是你要明白,我们这里要教的是chromium浏览器源码层面,以一种更底层的方式来修改。

第二步,定位要修改的源码。如果是使用vs2019打开chromium项目,将会被数以万计的解决方案所淹没。面对这一庞大的工程,小白肯定是目瞪口呆的。因为我们采取在线浏览定位的方式。

chromium在线源码浏览:源码浏览。在这个网站上可以轻松快捷地查看最新版的chromium的源代码,而且有强大的搜索跳转功能,已经能够满足我们修改chromium源码的需求了,因为我们仅仅是要抹除一些特征用于自动化,而非进行浏览器的二次开发。

爬虫自动化之定制浏览器随机指纹

接着,进入chromium/src当中,切换到third_party/blink目录之下,搜索<span class="ne-text">Navigator::webdriver</span>,之所以在blink目录之下,是因为chromium使用的是blink内核,在blink之下可以缩小范围,减少搜索量。

爬虫自动化之定制浏览器随机指纹

许多人可能想问,我为什么知道是搜<span class="ne-text">Navigator::webdriver</span>?事实上,我也不知道要搜的是这个,我是先搜索了<span class="ne-text">webdriver</span>,之后又把navigator相关的文件查看完毕,才知道是这个,这里只是为了让各位更快定位。

chromium中的原始代码如下所示:

 复制代码 隐藏代码
bool Navigator::webdriver() const {
  if (RuntimeEnabledFeatures::AutomationControlledEnabled())
    return true;

  bool automation_enabled = false;
  probe::ApplyAutomationOverride(GetExecutionContext(), automation_enabled);
  return automation_enabled;
}

        我们稍作修改,可以让它永远返回false,或者按照你的需求让它返回其他东西。

 复制代码 隐藏代码
bool Navigator::webdriver() const {
    return false;
}

修改完毕之后,我们返回src目录之下,在cmd中输入<span class="ne-text">autoninja -C outbuild mini_installer</span>这次的更新会非常迅速,回编译成功之后,我们就可以看到webdriver已经被修改了。

如果要修改<span class="ne-text">isTrust</span>也是同理的,直接搜索修改就好了:

 复制代码 隐藏代码
bool isTrusted() const { return is_trusted_; }

4.如何随机浏览器指纹

💡 Tips:随机指纹使用的是一样的方法。

随机指纹使用的方法和上边一般无二。

但是有一个很重要的点需要各位看官明白,浏览器指纹并非是有一个固定的参数或者方法,修改之后就可以过一切网站的指纹检测,不是这样的。

永远要根据你要攻克的网站来动态地确定,不排除大多数网站采取一样的指纹检测策略,比如知名的FP指纹检测:fingerprint。

说白了,和JS逆向一样,你要随机哪个网站的指纹,先要查看一下该网站的JS代码,看看它计算指纹的时候使用的是哪个方法。举个非常简单的例子,拿canvas指纹来说,创建canvas画布会有非常多的API接口可用于指纹计算:

 复制代码 隐藏代码
function makeTextImage(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) {
  canvas.width = 240  canvas.height = 60  context.textBaseline = 'alphabetic'  context.fillStyle = '#f60'  context.fillRect(100, 1, 62, 20)
  context.fillStyle = '#069'  const printedText = `Cwm fjordbank gly ${String.fromCharCode(55357, 56835) /* 😃 */}`  context.fillText(printedText, 2, 15)
  context.fillStyle = 'rgba(102, 204, 0, 0.2)'  context.font = '18pt Arial'  context.fillText(printedText, 4, 45)

  return save(canvas)
}function save(canvas: HTMLCanvasElement) {
  return canvas.toDataURL()
}

        canvas画布的创建有<span class="ne-text">width</span><span class="ne-text">height</span>,可能该网站在计算指纹的时候,会指定宽高,那我们可以直接把底层的宽高随机掉,或者更改为你需要的数值。

爬虫自动化之定制浏览器随机指纹

或者说这个网站进行指纹检测会使用颜色,那么干脆颜色也随机好了:

爬虫自动化之定制浏览器随机指纹

那如果我使用的是<span class="ne-text">toDataURL</span>呢?一样的,随便改改类型或者result就可以:

爬虫自动化之定制浏览器随机指纹

推及到其他的浏览器指纹上,webgl,webaudio,webrtc,font,css font,navigator,SSL/TLS指纹都是同理的,只要有耐心,一个一个去找对应的代码实现,可以把所有的指纹都随机掉。

5.浏览器固定指纹

💡 Tips:上边讲的方法是定位,或者说可以做到随机,但是遇到了新的需求.....

在随机浏览器指纹的过程中,慢慢遇到了一种新的需求,那就是固定指纹。也就是我事先本地写好一套指纹,然后浏览器开启后就用我这一套指纹,什么时候我想更改了,再替换本地文件就可以了。

这种需求常用于跨境电商,指纹不能纯随机,而且如果每次都写死,每次再回编译,再运行,这样也太麻烦了,浪费了许多时间,那么这种需求怎么解决呢?

非常简单,通过命令行传参的形式来实现,命令行指定使用我们的文件,然后在chromium源码中进行读取即可。对于这种方式,因为利益相关,就不在这里展开了,毕竟我自己有时候也靠这个东西吃饭呢。

本次的分享就到此为止,谢谢诸位捧场。

该内容转载自网络,更多内容请点击“阅读原文”

爬虫自动化之定制浏览器随机指纹

原文始发于微信公众号(web安全工具库):爬虫自动化之定制浏览器随机指纹

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月11日14:52:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   爬虫自动化之定制浏览器随机指纹https://cn-sec.com/archives/1116250.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息