[
  {
    "path": "_locales/en/messages.json",
    "content": "{\n\t\"extName\": {\n\t\t\"message\": \"Eye Protector\"\n\t},\n\t\"extDescription\": {\n\t\t\"message\": \"May it be the best eye-protect extension on earth.\"\n\t},\n\t\"strProtocolMsg\": {\n\t\t\"message\": \"The extension only works under http/https url.\"\n\t},\n\t\"strPositive\": {\n\t\t\"message\": \"Positive Mode\"\n\t},\n\t\"strPassive\": {\n\t\t\"message\": \"Passive Mode\"\n\t},\n\t\"strEnableForCurrentDomain\": {\n\t\t\"message\": \"Enable for this domain\"\n\t},\n\t\"strDisableForCurrentDomain\": {\n\t\t\"message\": \"Disable for this domain\"\n\t},\n\t\"strCurrentMode\": {\n\t\t\"message\": \"Running in <a href='' target='_blank' id='mode'></a>\"\n\t},\n\t\"strAbout\": {\n\t\t\"message\": \"About\"\n\t},\n\t\"strRepository\": {\n\t\t\"message\": \"· Source code on <a href='https://github.com/chitosai/eye_protector' target='_blank'>Github</a>. If you like the extension, please give me a star, thanks :)\"\n\t},\n\t\"strMode\": {\n\t\t\"message\": \"Mode\"\n\t},\n\t\"strPositiveModeDesc\": {\n\t\t\"message\": \"Positive<p class='desc'><b>Replace</b> background color for all domains, you can disable eye-protect for particular domains.</p>\"\n\t},\n\t\"strPassiveModeDesc\": {\n\t\t\"message\": \"Passive<p class='desc'><b>Not replace</b> background color by default, you can enable eye-protect for particular domains mamually.</p>\"\n\t},\n\t\"strGlobalSetting\": {\n\t\t\"message\": \"Global Settings\"\n\t},\n\t\"strReplaceTextColor\": {\n\t\t\"message\": \"Replace text color to black(#000)\"\n\t},\n\t\"strReplaceInputColor\": {\n\t\t\"message\": \"Replace color of INPUTs\"\n\t},\n\t\"strCustomBgColor\": {\n\t\t\"message\": \"Custom background color\"\n\t},\n\t\"strSave\": {\n\t\t\"message\": \"Save\"\n\t},\n\t\"strRestore\": {\n\t\t\"message\": \"Restore to default color\"\n\t},\n\t\"strSaveSuccess\": {\n\t\t\"message\": \"New color saved\"\n\t},\n\t\"strDonate\": {\n\t\t\"message\": \"Much appreciate if you'd like to buy me a cup of cola :)\"\n\t},\n\t\"strBrightnessThreshold\": {\n\t\t\"message\": \"Brightness Thershold\"\n\t},\n\t\"strBrightnessThresholdTip1\": {\n\t\t\"message\": \"Pick a brightness threshold that fits you\"\n\t},\n\t\"strBrightnessThresholdTip2\": {\n\t\t\"message\": \"(For most people you don't need to modify it)\"\n\t}\n}"
  },
  {
    "path": "_locales/zh_CN/messages.json",
    "content": "{\n\t\"extName\": {\n\t\t\"message\": \"保护眼睛\"\n\t},\n\t\"extDescription\": {\n\t\t\"message\": \"阿姆斯特朗回旋喷气式阿姆斯特朗墨镜\"\n\t},\n\t\"strProtocolMsg\": {\n\t\t\"message\": \"此插件仅在 http/https 协议的域名下生效。\"\n\t},\n\t\"strPositive\": {\n\t\t\"message\": \"主动模式\"\n\t},\n\t\"strPassive\": {\n\t\t\"message\": \"被动模式\"\n\t},\n\t\"strEnableForCurrentDomain\": {\n\t\t\"message\": \"此域名启用护眼模式\"\n\t},\n\t\"strDisableForCurrentDomain\": {\n\t\t\"message\": \"此域名关闭护眼模式\"\n\t},\n\t\"strCurrentMode\": {\n\t\t\"message\": \"正在<a href='' target='_blank' id='mode'></a>下运行\"\n\t},\n\t\"strAbout\": {\n\t\t\"message\": \"关于\"\n\t},\n\t\"strRepository\": {\n\t\t\"message\": \"· 代码托管于<a href='https://github.com/chitosai/eye_protector' target='_blank'>Github</a>，如果喜欢这个扩展请给我一个Star，谢谢：）\"\n\t},\n\t\"strMode\": {\n\t\t\"message\": \"工作模式\"\n\t},\n\t\"strPositiveModeDesc\": {\n\t\t\"message\": \"主动模式<p class='desc'>默认<b>替换</b>所有网页的颜色，您可以在不需要替换颜色的域名下关闭护眼功能。</p>\"\n\t},\n\t\"strPassiveModeDesc\": {\n\t\t\"message\": \"被动模式<p class='desc'>默认<b>不替换</b>任何网页的颜色，您需要自行设置哪些域名需要开启护眼功能。</p>\"\n\t},\n\t\"strGlobalSetting\": {\n\t\t\"message\": \"全局设置\"\n\t},\n\t\"strReplaceTextColor\": {\n\t\t\"message\": \"文字替换为黑色\"\n\t},\n\t\"strReplaceInputColor\": {\n\t\t\"message\": \"替换输入框颜色\"\n\t},\n\t\"strCustomBgColor\": {\n\t\t\"message\": \"自定义背景色\"\n\t},\n\t\"strSave\": {\n\t\t\"message\": \"保存\"\n\t},\n\t\"strRestore\": {\n\t\t\"message\": \"还原为豆沙绿\"\n\t},\n\t\"strSaveSuccess\": {\n\t\t\"message\": \"保存成功\"\n\t},\n\t\"strDonate\": {\n\t\t\"message\": \"如果你愿意赞助我一杯可乐就太感谢了：）\"\n\t},\n\t\"strBrightnessThreshold\": {\n\t\t\"message\": \"亮度阈值\"\n\t},\n\t\"strBrightnessThresholdTip1\": {\n\t\t\"message\": \"设置一个适合你的亮度阈值\"\n\t},\n\t\"strBrightnessThresholdTip2\": {\n\t\t\"message\": \"（一般来说直接用默认值就可以了）\"\n\t}\n}"
  },
  {
    "path": "css/options.css",
    "content": "body {\n  font: 14px \"PingFangSC-Light\", \"Segoe UI\", \"Microsoft YaHei\";\n  margin: .6rem;\n  width: 150px;\n}\n#option-page {\n  width: 470px;\n  margin: 1rem auto;\n}\n#option-page .option-list {\n  margin-bottom: 1.6rem;\n}\n#col-1{\n  float: left;\n  width: 300px;\n  padding-right: 20px;\n}\n#col-2{\n  float: right;\n  width: 150px;\n}\n\n.title {\n  font-weight: bold;\n  font-size: 1.5rem;\n  margin: 1rem 0 .6rem;\n}\n.list{}\n.item {\n  border-radius: 3px;\n  cursor: pointer;\n  line-height: 2em;\n  padding: 0 10px 0 25px;\n}\n.item:hover {\n  background: #eee;\n}\n.item.checked {\n  background: url('../images/check.png') 0 8px no-repeat;\n  color: #41ad49;\n  cursor: default;\n}\n.desc {\n  color: #aaa;\n  line-height: 1.625em;\n  margin: 0;\n  padding-bottom: .3rem;\n}\n.desc b {\n  color: #777;\n}\n.about{\n  color: #aaa;\n}\n.donate-icon{\n  display: none;\n  width: 100%;\n}\n[lang^=\"zh\"] .zfb{\n  display: block;\n}\n[lang^=\"en\"] .paypal{\n  display: block;\n}\n\n#color-preview{\n  border-radius: 3px;\n  display: inline-block;\n  width: 20px;\n  height: 1em;\n  padding: 3px;\n  vertical-align: middle;\n}\n#color{\n  outline: 0;\n  width: 6em;\n}\n#color-save-success{\n  display: none;\n  margin: 5px 0 0;\n  color: #aaa;\n  font-size: 12px;\n}"
  },
  {
    "path": "js/main.js",
    "content": "var CACHED_STYLES = new Map();\n\n/**\n * 获取元素样式\n *\n */\nconst getStyle = (node, key) => {\n  return window.getComputedStyle(node)[key];\n};\n// 设置元素样式，同时缓存它原有的样式\nconst setStyle = (node, key, val) => {\n  // 获取原始样式\n  const originStyle = getStyle(node, key),\n    styleCache = CACHED_STYLES.get(node) || {};\n\n  // 把原始样式保存下来\n  if (!styleCache[key]) {\n    CACHED_STYLES.set(node, {\n      ...styleCache,\n      [key]: originStyle,\n    });\n  }\n\n  // 修改为新样式，以前这里可以直接同步改，但react流行之后似乎有些使用react的页面我们直接修改会造成react报错，例如知乎\n  // 试了一下解决方式就是我们把样式修改改为异步进行，让react先完成她的执行周期就不会有冲突了\n  setTimeout(() => {\n    node.style[key] = val;\n  }, 0);\n};\n\n/**\n * 获取rgba数组\n *\n */\nconst parseRGBA = (str) => {\n  const rgba = str.match(/[\\d\\.]+/g);\n  return [Number(rgba[0]), Number(rgba[1]), Number(rgba[2]), rgba.length == 4 ? Number(rgba[3]) : 1];\n};\n\n/**\n * 计算亮度\n *\n */\nconst calcBrightness = (colorString) => {\n  const rgba = parseRGBA(colorString);\n  // alpha通道为0是transparent\n  if (!rgba[3]) return false;\n\n  // 把RGB转换为亮度\n  return (0.2126 * rgba[0]) / 255 + (0.7152 * rgba[1]) / 255 + (0.072 * rgba[2]) / 255;\n};\nconst getNodeStyleBrightness = (node, key) => {\n  // 读取颜色数据\n  const colorString = getStyle(node, key);\n  return colorString ? calcBrightness(colorString) : false;\n};\n\n/**\n * 根据预设的class列表跳过特定div，直接用IndexOf是因为这样highlight/highlight/highlighter之类的不用重复了\n * @return true dom中包含需要跳过的class\n * @return false 不包含\n *\n */\nconst shouldBeIgnored = (node) => {\n  if (OPTIONS.skipNodeTypes.includes(node.nodeName)) {\n    return true;\n  }\n  const classnames = node.getAttribute(\"class\")?.toLowerCase() || \"\";\n  const _id = node.id?.toLowerCase() || \"\";\n  if (!classnames && !_id) {\n    return false;\n  }\n  for (const ic of OPTIONS.ignoreClass) {\n    if (classnames.includes(ic) || _id.includes(ic)) {\n      return true;\n    }\n  }\n  return false;\n};\n\n/*\n * 替换颜色\n * @return 3 设置了背景色，且背景色亮度超过阈值，替换了背景色\n * @return 2 设置了背景色，但背景色亮度没有超过阈值，没有替换背景色\n * @return 1 元素没有设置背景色\n * @return 0 此元素依照config中的设置跳过不处理\n */\nconst replaceBackgroundColor = (node) => {\n  // input[type=text]，用户选择「不替换输入框颜色」\n  if (node.nodeName == \"INPUT\" && !OPTIONS.basic.replaceTextInput) {\n    return 0;\n  }\n\n  // 根据亮度判断是否需要替换\n  const brightness = getNodeStyleBrightness(node, \"background-color\");\n  if (!brightness) return 1;\n\n  if (brightness > OPTIONS.basic.bgColorBrightnessThreshold) {\n    setStyle(node, \"background-color\", OPTIONS.basic.replaceBgWithColor);\n    return 3;\n  } else {\n    return 2;\n  }\n};\n\n/**\n * 修改Border颜色\n *\n */\nconst replaceBorderColor = (node) => {\n  // 四边各自计算\n  const sides = [\"top\", \"bottom\", \"left\", \"right\"];\n\n  const borderWidthAttrString = \"border-%s-width\",\n    borderColorAttrString = \"border-%s-color\";\n  let borderColorAttr, borderWidth, borderBrightness;\n\n  for (const side of sides) {\n    // 先判断下是否有边框\n    borderWidth = getStyle(node, borderWidthAttrString.replace(\"%s\", side));\n    if (!borderWidth) continue;\n\n    // 然后判断是否需要替换颜色\n    borderColorAttr = borderColorAttrString.replace(\"%s\", side);\n    borderBrightness = getNodeStyleBrightness(node, borderColorAttr);\n    if (!borderBrightness) continue;\n\n    if (borderBrightness > OPTIONS.basic.borderColorBrightnessThreshold) {\n      setStyle(node, borderColorAttr, OPTIONS.basic.replaceBorderWithColor);\n    }\n  }\n\n  // box-shadow，如果有位移和扩散都为0的box-shadow，我们就认为这个box-shadow是border\n  const shadow = getStyle(node, \"box-shadow\");\n  const m = /(rgb\\(\\d+, \\d+, \\d+\\)) 0px 0px 0px (\\d+)px/.exec(shadow);\n  if (m && calcBrightness(m[1])) {\n    setStyle(node, \"box-shadow\", `${OPTIONS.basic.replaceBorderWithColor} 0px 0px 0px ${m[2]}px`);\n  }\n};\n\n/**\n * 修改文字颜色\n *\n */\nconst replaceTextColor = (node) => {\n  if (!OPTIONS.basic.replaceTextColor) return false;\n\n  // 文字亮度\n  const brightness = getNodeStyleBrightness(node, \"color\");\n  if (!brightness) return false;\n\n  // 确认此元素亮度过高且没有背景图片\n  const bgImage = getStyle(node, \"background-image\");\n  if (brightness > OPTIONS.basic.borderColorBrightnessThreshold && (!bgImage || bgImage == \"none\")) {\n    // 替换文字颜色\n    setStyle(node, \"color\", \"#000\");\n  }\n\n  // TODO: 有时候虽然当前元素没有背景图片，但其实文字浮动在父元素或其他元素的背景图片上，造成文字看不清\n};\n\n/**\n * 替换颜色啦啦啦\n * @param bool processOther 是否处理边框、文字等其他颜色，此参数继承\n *\n */\nconst replaceColor = (node, processOther = false) => {\n  // nodeType != 1 表示这不是一个正常的element: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType\n  // 包含highlight/player等特征的节点应当直接跳过，其子节点也不必再遍历\n  if (node.nodeType !== 1 || shouldBeIgnored(node)) {\n    return;\n  }\n\n  // 替换背景色\n  const bgColorReplacReturn = replaceBackgroundColor(node);\n  // 根据是否替换了背景色决定是否要处理边框、文字颜色等\n  // 当返回值为2、3时说明当前节点是有背景色的，应当根据当前节点的情况修改processOther\n  // 其他情况继续沿用父节点传下来的值\n  if (bgColorReplacReturn == 3) {\n    processOther = true;\n  } else if (bgColorReplacReturn == 2) {\n    processOther = false;\n  }\n\n  if (processOther) {\n    // 替换边框色\n    replaceBorderColor(node);\n    // 替换文本颜色\n    replaceTextColor(node);\n  }\n\n  // 递归\n  node.childNodes.forEach((child) => replaceColor(child, processOther));\n\n  benchmark.tick();\n};\n\n/**\n * 回复页面原始样式\n *\n */\nconst restoreColor = () => {\n  const modifiedNodes = CACHED_STYLES.keys();\n  for (const node of modifiedNodes) {\n    // 感觉这里会存在内存泄漏，一个dom如果已经被页面逻辑移除了，但是却被eye-protector缓存下来了那是不是就永远不会被释放了？\n    const cahcedStyles = CACHED_STYLES.get(node);\n    for (const key in cahcedStyles) {\n      setStyle(node, key, cahcedStyles[key]);\n    }\n  }\n};\n\n// 用mutationObserver代替监听DOMSubtreeModified事件，后者有性能缺陷：\n// https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events\nvar observer = new MutationObserver(function (mutations) {\n  mutations.forEach(function (mutation) {\n    var len = mutation.addedNodes.length;\n    for (var i = 0; i < len; i++) {\n      var node = mutation.addedNodes[i];\n      // 先向上遍历一遍祖先，确认是否需要处理当前节点\n      var ancestor = node,\n        shouldIgnore = false;\n      while ((ancestor = ancestor.parentNode) && ancestor.nodeName != \"BODY\") {\n        if (shouldBeIgnored(ancestor)) {\n          shouldIgnore = true;\n          break;\n        }\n      }\n      // 文本节点内容改变也会触发mutation，而text并不是正经的node\n      if (!shouldIgnore && node.nodeType == 1) {\n        replaceColor(node);\n      }\n    }\n  });\n});\nvar observerConfig = {\n  childList: true,\n  subtree: true,\n};\n\nfunction wait(ms) {\n  return new Promise((resolve) => {\n    setTimeout(resolve, ms);\n  });\n}\n\nasync function init() {\n  await readOption();\n  if ((OPTIONS.basic.mode == \"positive\" && OPTIONS.positiveList.includes(host)) || (OPTIONS.basic.mode == \"passive\" && !OPTIONS.passiveList.includes(host))) {\n    restoreColor();\n    observer.disconnect();\n    return false;\n  }\n  // 1. 当系统不处在深色模式时，一切逻辑正常执行\n  if (!isNightMode) {\n    // always set background to <html> element\n    setStyle(document.documentElement, \"backgroundColor\", OPTIONS.basic.replaceBgWithColor);\n    // body需要特殊处理，当body的background-color为transparent时实际上页面是白色\n    // 此时也需要给body设置背景色\n    const body = document.body,\n      brightness = getNodeStyleBrightness(body, \"background-color\");\n    // 全黑时brightness = 0，一定要排除出去\n    if ((!brightness && brightness !== 0) || brightness > OPTIONS.basic.bgColorBrightnessThreshold) {\n      setStyle(body, \"background-color\", OPTIONS.basic.replaceBgWithColor);\n      setStyle(body, \"transition\", \"background-color .3s ease\");\n    }\n    // 遍历DOM\n    Array.from(body.children).forEach(replaceColor);\n    // watch dom changes\n    observer.observe(body, observerConfig);\n  } else {\n    // 2. 当系统处于深色模式，且html or body的背景色足够深时，我就简单粗暴的认为这个网站是有深色样式的，跳过一切逻辑让网页原样显示\n    // 有的网站会把深色样式加在html上有的会在body上，也可能会加在别的地方吧，但我们就只处理两种最常见的情况，其他的就随缘吧\n    await wait(10); // 网页切换深色模式有时会有一定延迟，我们也稍微等一下\n    const html = document.querySelector(\"html\"),\n      htmlBrightness = getNodeStyleBrightness(html, \"background-color\");\n    const body = document.body,\n      bodyBrightness = getNodeStyleBrightness(body, \"background-color\");\n    // 亮度 = 0时是纯黑色，不需要改色\n    // 亮度为true且小于阈值时不需要改色\n    if (htmlBrightness === 0 || (htmlBrightness && htmlBrightness < OPTIONS.basic.bgColorBrightnessThreshold) || bodyBrightness === 0 || (bodyBrightness && bodyBrightness < OPTIONS.basic.bgColorBrightnessThreshold)) {\n      return false;\n    }\n    setStyle(body, \"background-color\", OPTIONS.basic.replaceBgWithColor);\n    setStyle(body, \"transition\", \"background-color .3s ease\");\n    // 这边稍微有点区别，非深色模式时无论body的背景色深浅代码都会遍历DOM树来检查是否有需要变色的元素\n    // 但是在深色模式下仅Google就会存在误判的情况，所以想了想还是直接全部跳过了，这样可能会有漏网之鱼但是逻辑最简单\n    Array.from(body.children).forEach(replaceColor);\n    observer.observe(body, observerConfig);\n  }\n}\n\n// benchmark\nconst benchmark = new Benchmark();\n// 保存域名\nconst host = getHost(document.location.href);\n// 设置改变时重新读取设置\nchrome.storage.onChanged.addListener(init);\n// 检查浏览器是否开启了Night Mode\nconst { matches: isNightMode } = window.matchMedia(\"(prefers-color-scheme: dark)\");\n// GO\ninit();\n"
  },
  {
    "path": "js/option.options.js",
    "content": "var CLICKLISTENERS = {\n  check: function () {\n    var key = this.id;\n\n    // 根据选项类型反转选项\n    var option = OPTIONS.basic[key];\n    if (typeof option == \"boolean\") {\n      OPTIONS.basic[key] = !option;\n    }\n    saveOption();\n\n    // toggle class\n    this.classList.toggle(\"checked\");\n  },\n  radio: function () {\n    var key = this.getAttribute(\"name\"),\n      val = this.id,\n      nodes = $$(\".item[name=\" + key + \"]\");\n\n    nodes.forEach(function (node) {\n      node.classList.remove(\"checked\");\n    });\n    this.classList.add(\"checked\");\n\n    OPTIONS.basic[key] = this.id;\n    saveOption();\n  },\n  pickColor: function () {\n    var color = this.value.trim().replace(/[^0-9a-fA-F]/g, \"\"),\n      preview = $(\"color-preview\"),\n      btn = $(\"color-save\");\n    // 确保color中不包含非0-9a-fA-F的字符，且长度为3或6\n    if (color == this.value && (color.length == 3 || color.length == 6)) {\n      preview.style.backgroundColor = \"#\" + color;\n      btn.disabled = false;\n    } else {\n      preview.style.backgroundColor = \"#fff\";\n      btn.disabled = true;\n    }\n  },\n  saveColor: function () {\n    var color = $(\"color\").value.trim().toUpperCase();\n    if (color.length == 3) {\n      color = [color[0], color[0], color[1], color[1], color[2], color[2]].join(\"\");\n    }\n    OPTIONS.basic.replaceBgWithColor = \"#\" + color;\n    saveOption(function () {\n      $(\"color-save-success\").style.display = \"block\";\n    });\n  },\n  restoreColor: function () {\n    var color = OPTIONS.basic.defaultBgColor;\n    OPTIONS.basic.replaceBgWithColor = color;\n    saveOption(function () {\n      $(\"color-preview\").style.backgroundColor = color;\n      $(\"color\").value = color.slice(1);\n    });\n  },\n};\n\nfunction onRangeInput(e) {\n  $(\"current-brightness\").textContent = this.value;\n}\n\nfunction onRangeChange(e) {\n  OPTIONS.basic.bgColorBrightnessThreshold = this.value;\n  saveOption();\n}\n\nasync function init() {\n  // i18n\n  i18n();\n\n  // 读取设置\n  await readOption();\n  var options = OPTIONS.basic,\n    key,\n    node;\n  for (key in options) {\n    if (options[key] === true) {\n      node = $(key);\n      if (node) {\n        node.classList.add(\"checked\");\n      }\n    }\n  }\n\n  // 工作模式暂时没想到怎么做通用，就做个特例吧\n  $(OPTIONS.basic.mode).classList.add(\"checked\");\n\n  // 背景色\n  $(\"color-preview\").style.backgroundColor = OPTIONS.basic.replaceBgWithColor;\n  $(\"color\").value = OPTIONS.basic.replaceBgWithColor.slice(1);\n\n  // 亮度阈值\n  $(\"brightness\").value = OPTIONS.basic.bgColorBrightnessThreshold;\n\n  // 修改设置\n  var nodes = $$(\".item\");\n  nodes.forEach(function (node) {\n    node.addEventListener(\"click\", CLICKLISTENERS[node.getAttribute(\"type\")]);\n  });\n\n  // 修改背景色\n  $(\"color\").addEventListener(\"input\", CLICKLISTENERS.pickColor);\n  $(\"color-save\").addEventListener(\"click\", CLICKLISTENERS.saveColor);\n  $(\"color-restore\").addEventListener(\"click\", CLICKLISTENERS.restoreColor);\n\n  // 亮度阈值\n  $(\"brightness\").addEventListener(\"input\", onRangeInput);\n  $(\"brightness\").addEventListener(\"change\", onRangeChange);\n\n  // options页面在打开的情况下，如果用户切到其他tabs修改了OPTIONS，这边需要及时更新\n  // 不然可能出现options页面打开太久，OPTIONS中的list已经不是最新的，然后options触发了一次saveOptions造成\n  // options页面打开之后的修改丢失的情况\n  chrome.storage.onChanged.addListener(readOption);\n}\n\ninit();\n"
  },
  {
    "path": "js/option.popup.js",
    "content": "var url, host;\n\nfunction onClick() {\n  var list = OPTIONS[this.id + \"List\"];\n\n  if (list.indexOf(host) > -1) {\n    list.remove(host);\n  } else {\n    list.push(host);\n  }\n  saveOption();\n\n  // toggle class\n  this.classList.toggle(\"checked\");\n}\n\nfunction init() {\n  i18n();\n\n  // 获取当前激活tab的域名\n  chrome.tabs.query({ active: true, currentWindow: true }, async function (tabs) {\n    (url = tabs[0].url), (host = getHost(url));\n    if (!host) {\n      $(\"normal\").style.display = \"none\";\n      $(\"error\").textContent = _(\"strProtocolMsg\");\n      return;\n    }\n\n    // 读取当前域名的配置\n    await readOption();\n    var mode = OPTIONS.basic.mode,\n      list = OPTIONS[mode + \"List\"],\n      btn = $(mode);\n\n    // 显示当前模式\n    $(\"mode\").textContent = mode == \"positive\" ? _(\"strPositive\") : _(\"strPassive\");\n    $(\"mode\").href = chrome.runtime.getURL(\"/options.html\");\n\n    // 根据运行模式产生按钮状态\n    btn.style.display = \"block\";\n    if (list.indexOf(host) > -1) {\n      btn.classList.add(\"checked\");\n    }\n  });\n\n  // 修改设置\n  $$(\".item\").forEach(function (node) {\n    node.addEventListener(\"click\", onClick);\n  });\n}\n\ninit();\n"
  },
  {
    "path": "js/utility.js",
    "content": "var OPTIONS = {\n  basic: {\n    // 工作模式\n    mode: \"positive\",\n    // 背景色\n    replaceBgWithColor: \"#C1E6C6\",\n    // 豆沙绿\n    defaultBgColor: \"#C1E6C6\",\n    // 替换背景色的亮度阈值\n    bgColorBrightnessThreshold: 0.9,\n    // 边框色\n    replaceBorderWithColor: \"rgba(0, 0, 0, .35)\",\n    // 替换边框色的亮度阈值\n    borderColorBrightnessThreshold: 0.5,\n    // 是否替换文字颜色\n    replaceTextColor: true,\n    // 是否替换文本输入框背景色\n    replaceTextInput: false,\n  },\n  // 忽略的特殊class\n  ignoreClass: [\"highlight\", \"syntax\", \"code\", \"player\"],\n  // 忽略的特殊nodeType\n  skipNodeTypes: [\"SCRIPT\", \"BR\", \"CANVAS\", \"IMG\", \"svg\", \"CODE\"],\n  // 主动模式 - 忽略的网站列表\n  positiveList: [],\n  // 被动模式 - 要替换的域名列表\n  passiveList: [],\n};\n\n// 检查对象是否为空\nfunction is_object_empty(obj) {\n  for (var key in obj) {\n    return false;\n  }\n  return true;\n}\n\n// array.remove\nArray.prototype.remove = function (key) {\n  var index = this.indexOf(key);\n  if (index > -1) this.splice(index, 1);\n  return this;\n};\n\n// shortcut\nfunction $(id) {\n  return document.getElementById(id);\n}\nfunction $$(selector) {\n  return Array.from(document.querySelectorAll(selector));\n}\n\nvar storage = chrome.storage.sync;\n\nfunction readOption() {\n  return new Promise((resolve) => {\n    storage.get(\"option\", function (obj) {\n      if (obj.option && obj.option.basic) {\n        OPTIONS = obj[\"option\"];\n      }\n      // add ignoreNodeTypes to defaultOption\n      if (!OPTIONS.skipNodeTypes) {\n        OPTIONS.skipNodeTypes = [\"SCRIPT\", \"BR\", \"CANVAS\", \"IMG\", \"svg\", \"CODE\"];\n        saveOption();\n      }\n      //\n      resolve();\n    });\n  });\n}\n\nfunction saveOption(callback) {\n  storage.set({ option: OPTIONS }, callback);\n}\n\n// 获取当前激活标签页域名\nfunction getHost(url) {\n  var host = /https?:\\/\\/([^/]+)\\//.exec(url);\n  if (host && host.length > 1) {\n    host = host[1];\n    if (host.startsWith(\"www.\")) {\n      return host.slice(4);\n    } else {\n      return host;\n    }\n  } else {\n    return false;\n  }\n}\n\n// i18n\nfunction _(msg) {\n  return chrome.i18n.getMessage(msg);\n}\nfunction i18n() {\n  // render texts\n  var nodes = $$(\"[data-text]\");\n  nodes.forEach(function (node) {\n    node.innerHTML = _(node.dataset.text);\n  });\n\n  // add language to body\n  document.body.setAttribute(\"lang\", chrome.i18n.getUILanguage());\n}\n\n// benchmark\nclass Benchmark {\n  constructor() {\n    this.start = new Date();\n    this.end = null;\n    this.ticker = null;\n  }\n  tick() {\n    this.end = new Date();\n    clearTimeout(this.ticker);\n    this.ticker = setTimeout(() => {\n      const elapsed = (this.end - this.start) / 1000;\n      console.log(`[Eyeprotector] Runs for ${elapsed.toFixed(2)}s`);\n      // 只记录初始化那波就够了\n      this.tick = () => {};\n    }, 999);\n  }\n}\n"
  },
  {
    "path": "manifest.json",
    "content": "{\n  \"manifest_version\": 3,\n\n  \"name\": \"__MSG_extName__\",\n  \"description\": \"__MSG_extDescription__\",\n  \"version\": \"2.4\",\n\n  \"permissions\": [\n    \"storage\",\n    \"activeTab\"\n  ],\n  \"icons\":{\n  \t\"16\": \"images/icon.png\",\n  \t\"48\":\"images/icon.png\",\n  \t\"128\":\"images/icon.png\"\n  },\n  \"action\": {\n    \"default_icon\": {\n      \"19\": \"images/icon.png\",\n      \"38\": \"images/icon.png\"\n    },\n    \"default_title\": \"设置设置\",\n    \"default_popup\": \"popup.html\"\n  },\n  \"browser_specific_settings\": {\n    \"gecko\": {\n      \"id\": \"eye_protector\",\n      \"strict_min_version\": \"68.0\"\n    }\n  },\n  \"options_ui\": {\n    \"page\": \"options.html\"\n  },\n  \"content_scripts\":[{\n  \t\"matches\":[\n  \t\t\"http://*/*\",\n      \"https://*/*\"\n  \t],\n  \t\"js\":[\n      \"js/utility.js\",\n  \t\t\"js/main.js\"\n  \t],\n  \t\"run_at\": \"document_idle\"\n  }],\n  \"default_locale\": \"en\"\n}"
  },
  {
    "path": "options.html",
    "content": "<!doctype html>\n<html lang=\"cn\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<link rel=\"stylesheet\" href=\"css/options.css\">\n</head>\n<body id=\"option-page\">\n\n\t<div id=\"col-2\">\n\t\t<div class=\"title\" data-text=\"strAbout\">关于</div>\n\t\t<div class=\"about\">· Made by <a href=\"http://weibo.com/chitosai\" target=\"_blank\">@千歳TheC</a></div>\n\t\t<div class=\"about\" data-text=\"strRepository\">· 代码托管于<a href='https://github.com/chitosai/eye_protector' target='_blank'>Github</a>，如果喜欢这个扩展请给我一个Star，谢谢。</div>\n\t\t<div class=\"about\" style=\"margin: 15px 0;\" data-text=\"strDonate\">如果你愿意赞助我一杯可乐就太感谢了：）</div>\n\t\t<img class=\"donate-icon zfb\" src=\"images/zfb.png\" alt=\"支付宝\">\n\t\t<img class=\"donate-icon paypal\" src=\"images/paypal.jpg\" alt=\"Paypal\">\n\t</div>\n\t<div id=\"col-1\">\n\t\t<div class=\"title\" data-text=\"strMode\">工作模式</div>\n\t\t<div class=\"list\">\n\t\t\t<div class=\"item\" type=\"radio\" id=\"positive\" name=\"mode\" data-text=\"strPositiveModeDesc\">\n\t\t\t\t主动模式\n\t\t\t\t<p class=\"desc\">默认<b>替换</b>所有网页的颜色，您可以在不需要替换颜色的域名下关闭护眼功能。</p>\n\t\t\t</div>\n\t\t\t<div class=\"item\" type=\"radio\" id=\"passive\" name=\"mode\" data-text=\"strPassiveModeDesc\">\n\t\t\t\t被动模式\n\t\t\t\t<p class=\"desc\">默认<b>不替换</b>任何网页的颜色，您需要自行设置哪些域名需要开启护眼功能。</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"title\" data-text=\"strGlobalSetting\">全局设置</div>\n\t\t<div class=\"list\">\n\t\t\t<div class=\"item\" type=\"check\" id=\"replaceTextColor\" data-text=\"strReplaceTextColor\">文字替换为黑色</div>\n\t\t\t<div class=\"item\" type=\"check\" id=\"replaceTextInput\" data-text=\"strReplaceInputColor\">替换输入框颜色</div>\n\t\t</div>\n\t\t<div class=\"title\" data-text=\"strCustomBgColor\">自定义背景色</div>\n\t\t<div class=\"color\">\n\t\t\t<span id=\"color-preview\"></span>\n\t\t\t#<input type=\"text\" id=\"color\">\n\t\t\t<button id=\"color-save\" data-text=\"strSave\">保存</button>\n\t\t\t<button id=\"color-restore\" data-text=\"strRestore\">还原为豆沙绿</button>\n\t\t</div>\n\t\t<div class=\"title\" data-text=\"strBrightnessThreshold\">亮度阈值</div>\n\t\t<div class=\"brightness\">\n\t\t\t<p data-text=\"strBrightnessThresholdTip1\">设置一个适合你的亮度阈值</p>\n\t\t\t<p data-text=\"strBrightnessThresholdTip2\" style=\"color: #aaa; margin: -15px 0 15px;\">（一般来说直接用默认值就可以了）</p>\n\t\t\t<input id=\"brightness\" type=\"range\" min=\"0\" max=\"0.99\" step=\"0.01\">\n\t\t\t<span id=\"current-brightness\"></span>\n\t\t</div>\n\t\t<p id=\"color-save-success\" data-text=\"strSaveSuccess\">保存成功</p>\n\t</div>\n\t<!-- end -->\n\t<script src=\"js/utility.js\"></script>\n\t<script src=\"js/option.options.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "popup.html",
    "content": "<!doctype html>\n<html lang=\"cn\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<style>\n\t\tbody{\n\t\t\tfont: 14px \"PingFangSC-Light\", \"Segoe UI\", \"Microsoft YaHei\";\n\t\t\twidth: 160px;\n\t\t\tmargin: 0;\n\t\t}\n\t\tbody[lang=en] .item,\n\t\tbody[lang=en-US] .item,\n\t\tbody[lang=en-GB] .item{\n\t\t\tfont-size: 12px;\n\t\t}\n\t\t#error{\n\t\t  color: #aaa;\n\t\t  padding: .5rem;\n\t\t}\n\t\t#error:empty{\n\t\t\tdisplay: none;\n\t\t}\n\t\t.list {\n\t\t  padding: .5rem;\n\t\t}\n\t\t.item {\n\t\t  border-radius: 3px;\n\t\t  cursor: pointer;\n\t\t  display: none;\n\t\t  line-height: 2em;\n\t\t  text-align: center;\n\t\t  margin-bottom: 1px\n\t\t}\n\t\t.item:hover {\n\t\t  background: #eee;\n\t\t}\n\t\t.item.checked {\n\t\t  background: #41ad49;\n\t\t  color: #fff;\n\t\t  cursor: default;\n\t\t}\n\t\t#current{\n\t\t\tbackground: #f7f7f7;\n\t\t\tborder-top: 1px solid #e3e3e3;\n\t    color: #888;\n\t    font-size: 10px;\n\t    line-height: 3em;\n\t    margin: 0;\n\t    text-align: center;\n\t\t}\n\t\t#mode{\n\t    color: #000;\n\t    outline: 0;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<div id=\"normal\">\n\t\t<div class=\"list\">\n\t\t\t<div class=\"item\" id=\"passive\" data-text=\"strEnableForCurrentDomain\">此域名启用护眼模式</div>\n\t\t\t<div class=\"item\" id=\"positive\" data-text=\"strDisableForCurrentDomain\">此域名关闭护眼模式</div>\n\t\t</div>\n\t\t<p id=\"current\" data-text=\"strCurrentMode\">正在<a href=\"\" target=\"_blank\" id=\"mode\"></a>下运行</p>\n\t</div>\n\t<div id=\"error\"></div>\n\t<!-- end -->\n\t<script src=\"js/utility.js\"></script>\n\t<script src=\"js/option.popup.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "readme.md",
    "content": "Eye Protector\n---\n\nThis extension tries its best to keep your page clean and tidy while removing colors which are too bright.\n\nYou can find details in [Chrome Store](https://chrome.google.com/webstore/detail/%E4%BF%9D%E6%8A%A4%E7%9C%BC%E7%9D%9B/fgadnbmmolnmbkbklpaojbogcopipopl)."
  }
]