Мне нужна помощью полностью переписать расширение для Firefox. Оно было сделано для Chrome и используется часть апи которые есть только в Chrome. Вот логи ошибок которые выдает валидатор расширений от Mozilla

/permissions: Invalid permissions "tabCapture" at 1.

Warning: See https://mzl.la/1R1n1t0 (MDN Docs) for more information.
manifest.json
"content_security_policy" allows remote code execution in manifest.json

Warning: A custom "content_security_policy" needs additional review.
manifest.json
"storage.sync" can cause issues when loaded temporarily

Warning: This API can cause issues when loaded temporarily using about:debugging in Firefox unless you specify "browser_specific_settings.gecko.id" in the manifest. Please see: https://mzl.la/2hizK4a for more.
bg.js line 27 column 7
"tabs.getSelected" is deprecated or unimplemented

Warning: This API has been deprecated by Firefox.
bg.js line 556 column 11
tabCapture.capture is not supported

Warning: This API has not been implemented by Firefox.
bg.js line 560 column 13
tabCapture.onStatusChanged is not supported

Warning: This API has not been implemented by Firefox.
bg.js line 659 column 7
Unsafe assignment to innerHTML

Warning: Due to both security and performance concerns, this may not be set using dynamic values which have not been adequately sanitized. This can lead to security issues or fairly serious performance degradation.
snap.svg-min.js line 19 column 20477
The Function constructor is eval.

Warning: Evaluation of strings as code can lead to security vulnerabilities and performance issues, even in the most innocuous of circumstances. Please avoid using `eval` and the `Function` constructor when at all possible.
snap.svg-min.js line 21 column 2955

Перепиши код исправляя ошибки совместимости а также я думаю что стоит полностью отказаться от snap.svg и заменить его на что-то другое. Сейчас я тебе пришлю код, постарайся обойтись без лишний комментариев и пожалуйста убедись что код точно не сломается и будет работать. 

```popup.js

var _gaq = _gaq || [];
function scope() {
  var e = chrome.runtime.getManifest().version;
  _gaq.push(["_setAccount", "UA-64913318-2"]);
  _gaq.push(["_trackPageview"]);
  _gaq.push(["_trackEvent", "popupOpen", e]);
  var t = localStorage;
  var p = null;
  function N() {}
  document.addEventListener("DOMContentLoaded", function (e) {
    _();
    n();
    a();
  });
  document.addEventListener("DOMContentLoaded", function () {
    var n = document.getElementById("presetNameInput");
    var e = document.getElementById("resetFiltersButton");
    e.onclick = function () {
      n.value = "";
      chrome.runtime.sendMessage({
        type: "resetFilters"
      });
    };
    var t = document.getElementById("bassBoostButton");
    t.onclick = function () {
      n.value = "";
      chrome.runtime.sendMessage({
        type: "preset",
        preset: "bassBoost"
      });
    };
    var r = document.getElementById("requiresProDiv");
    function a(e) {
      r.textContent = e;
      r.classList.add("show");
      setTimeout(function () {
        r.classList.remove("show");
      }, 5000);
    }
    var i = document.getElementById("savePresetButton");
    i.onclick = function () {
      if (p && userPresetSpan.children.length >= p + 1) {
        a("Saving more than 3 presets requires Ears Pro. Click the Pro tab above to start your free trial!");
        return;
      }
      var e = n.value.trim();
      if (e != "") {
        chrome.runtime.sendMessage({
          type: "savePreset",
          preset: e
        });
      } else {
        a("Type a name in the Preset Name box, then click Save Preset or press Enter.");
        n.focus();
      }
    };
    var o = document.getElementById("deletePresetButton");
    o.onclick = function () {
      var e = n.value.trim();
      if (e != "") {
        chrome.runtime.sendMessage({
          type: "deletePreset",
          preset: e
        });
      }
    };
    n.onkeypress = function (e) {
      if (!e) {
        e = window.event;
      }
      var t = e.keyCode || e.which;
      if (t == "13" && document.activeElement == n) {
        i.click();
        return false;
      }
    };
    var s = document.getElementById("exportPresetsButton");
    s.onclick = function () {
      chrome.runtime.sendMessage({
        type: "exportPresets"
      });
    };
    var c = document.getElementById("importPresetsButton");
    var u = document.getElementById("importPresetsFile");
    c.onclick = function () {
      u.click();
    };
    u.onchange = function () {
      var e = u.files;
      for (var t = 0; t < e.length; t++) {
        var n = new FileReader();
        n.onload = function (e) {
          chrome.runtime.sendMessage({
            type: "importPresets",
            presets: JSON.parse(e.target.result)
          });
        };
        n.readAsText(e[t]);
      }
    };
    var l = document.getElementById("vizButton");
    function f() {
      if (y()) {
        l.classList.add("on");
      } else {
        l.classList.remove("on");
      }
    }
    f();
    l.onclick = function () {
      if (p) {
        a("The frequency spectrum visualizer requires Ears Pro. Click the Pro tab above to start your free trial!");
      }
      H();
      f();
    };
    var v = ["tab-1", "tab-2", "tab-3"];
    for (var d = 0; d < v.length; d++) {
      var h = v[d];
      document.getElementById(h).addEventListener("change", function (e) {
        return function () {
          if (this.checked) {
            localStorage["last-tab"] = e;
          }
        };
      }(h));
    }
    var m = localStorage["last-tab"];
    if (m) {
      var g = document.getElementById(m);
      if (g) {
        g.click();
      }
    }
    if (window.innerWidth && window.innerWidth > 1000) {
      document.getElementById("fullscreen-link").style.display = "none";
    }
  });
  function n() {
    chrome.runtime.sendMessage({
      type: "getFullRefresh"
    });
  }
  function _() {
    chrome.runtime.sendMessage({
      type: "onPopupOpen"
    });
  }
  var E = 44100;
  var O = "eqSvg";
  var z = "eqTabButton";
  var A = "How was your day of beautiful audio? Let us know what you think at the link below! If you would like to continue using Ears, please support us by making a purchase. If you have already purchased Ears, please restart Chrome. Enjoy!";
  var D = "https://chrome.google.com/webstore/detail/ears-audio-toolkit/nfdfiepdkbnoanddpianalelglmfooik";
  var i = true;
  function r() {
    if (i) {
      n();
      setTimeout(r, 1000);
    }
  }
  setTimeout(r, 1000);
  chrome.runtime.onMessage.addListener(function (e, t, n) {
    if (e.type == "sendCurrentTabStatus") {
      if (e.streaming) {
        G();
      } else {
        W();
      }
    }
    if (e.type == "sendWorkspaceStatus") {
      i = false;
      J(e);
    }
    if (e.type == "sendSampleRate") {
      E = e.Fs;
      console.log("set sample rate to " + E);
    }
    if (e.type == "sendPresets") {
      j(e);
    }
    if (e.type == "F") {
      var r = document.getElementById(O);
      if (r) {
        r.remove();
      }
      var a = document.getElementById(z);
      if (a) {
        a.remove();
      }
      document.getElementById("lt").textContent = A;
      document.getElementById("pl").style.display = "block";
    }
  });
  function U(e) {
    if (S) {
      var t = 1000 / 30 - (performance.now() - S);
      if (t > 0) {
        setTimeout(h, t);
        return;
      }
    }
    S = performance.now();
    if (C) {
      C.remove();
    }
    if (!y()) {
      return;
    }
    var n = e.fft;
    if (I && n.length > 0) {
      strokeGradient = I.gradient("l(.5, 0, .5, 1)" + m + "-" + q);
      var r = [];
      function a(e) {
        return B - 1 - e;
      }
      for (var i in n) {
        var o = i * E / (n.length * 2);
        if (o < 10) {
          continue;
        }
        var s = P(o);
        if (s > T) {
          break;
        }
        var c = (n[i] + 100) / 100 * B;
        r.push([s, c]);
      }
      var u = [];
      for (var i in r) {
        var l = r[i];
        if (u.length == 0) {
          u.push(l);
          continue;
        }
        var f = u[u.length - 1];
        var v = 2;
        if (l[0] - f[0] < v) {
          if (l[1] > f[1]) {
            f[1] = l[1];
          }
        } else {
          u.push(l);
        }
      }
      var d = [];
      for (var i in u) {
        var f = u[i];
        d = d.concat([f[0], a(f[1])]);
      }
      C = I.polyline(d).attr({
        "fill-opacity": "0",
        stroke: strokeGradient,
        "pointer-events": "none"
      });
    }
    h();
  }
  function h() {
    chrome.runtime.sendMessage({
      type: "getFFT"
    }, U);
  }
  function j(e) {
    var t = e.presets;
    var n = document.getElementById("userPresetSpan");
    while (n.firstChild) {
      n.removeChild(n.firstChild);
    }
    var r = Object.keys(t);
    for (var a = 0; a < r.length; a++) {
      (function (e) {
        var t = document.createElement("button");
        t.textContent = e;
        n.appendChild(t);
        t.onclick = function () {
          chrome.runtime.sendMessage({
            type: "preset",
            preset: e
          });
          document.getElementById("presetNameInput").value = e;
        };
      })(r[a]);
    }
  }
  function a() {
    chrome.runtime.sendMessage({
      type: "eqTab",
      on: true
    });
  }
  function W() {
    var e = document.getElementById("eqTabButton");
    e.onclick = function () {
      a();
    };
    e.textContent = "EQ This Tab";
  }
  function G() {
    var e = document.getElementById("eqTabButton");
    e.onclick = function () {
      chrome.runtime.sendMessage({
        type: "eqTab",
        on: false
      });
    };
    e.textContent = "Stop EQing This Tab";
  }
  var o = "SHOW_VISUALIZER";
  function y() {
    return localStorage[o] == true;
  }
  function H() {
    if (p) {
      return;
    }
    t[o] ^= true;
    if (y()) {
      h();
    }
  }
  var s = [];
  function J(e) {
    s = [];
    for (var t = 0; t < e.eqFilters.length; t++) {
      var n = e.eqFilters[t];
      var r = {};
      r.x = P(n.frequency);
      r.y = F(n.gain);
      var a = n.frequency / n.q;
      r.w = n.frequency / n.q;
      r.t = n.type;
      r.gain = n.gain;
      r.q = n.q;
      r.frequency = n.frequency;
      s.push(r);
    }
    var i = {
      gain: e.gain
    };
    i.y = F(fe(i.gain));
    $(s, i);
    K(e.streams);
  }
  function K(e) {
    var t = document.getElementById("eqTabList");
    t.innerHTML = "";
    if (e.length == 0) {
      t.textContent = "No tabs active. Click 'EQ This Tab' below to activate this tab.";
      return;
    }
    var n = document.createElement("table");
    for (var r = 0; r < e.length; r++) {
      n.appendChild(V(e[r]));
    }
    t.appendChild(n);
  }
  function V(e) {
    var t = document.createElement("tr");
    var n = document.createElement("button");
    n.textContent = "Stop EQing";
    n.onclick = function () {
      chrome.runtime.sendMessage({
        type: "disconnectTab",
        tab: e
      });
    };
    var r = document.createElement("img");
    r.className = "tabFavIcon";
    r.src = e.favIconUrl;
    r.alt = "";
    n.appendChild(r);
    var a = document.createElement("td");
    a.appendChild(n);
    t.appendChild(a);
    var i = document.createElement("td");
    var o = e.title;
    if (o.length > 45) {
      o = o.substring(0, 45);
    }
    i.textContent = o;
    t.appendChild(i);
    return t;
  }
  function Y(e) {
    return Math.log(e) / Math.log(2);
  }
  var m = "wheat";
  var q = "#2C3E50";
  var b = "#9573A8";
  var w = "#CDF7E1";
  var T = 600;
  var B = 300;
  var g = 30;
  var c = 22050;
  var u = 30;
  var M = 10;
  var k = -30;
  var Z = 0;
  var X = 0;
  var I = null;
  var C = null;
  var S = null;
  var x = null;
  function $(e, t) {
    if (I) {
      I.clear();
    }
    if (x) {
      x.clear();
    }
    I = Snap("#eqSvg");
    I.attr({
      fill: q,
      height: B,
      width: T
    });
    x = Snap("#gainSvg");
    x.attr({
      fill: q,
      height: B,
      width: g
    });
    var n = {
      fill: m,
      stroke: m
    };
    var r = I.rect(0, 0, T, B).attr({
      stroke: m
    });
    re(I, e);
    ae(I);
    var a = x.line(g / 2, F(k), g / 2, F(M)).attr({
      stroke: "wheat",
      opacity: 0.5
    });
    var i = x.text(g / 2, F(M) - 10, "volume").attr({
      fill: "wheat",
      "text-anchor": "middle",
      "font-size": 8
    });
    var o = x.line(g / 2 - 5, F(0), g / 2 + 5, F(0)).attr({
      stroke: "wheat"
    });
    var s = x.line(0, t.y, g, t.y).attr({
      stroke: "wheat"
    }).addClass("gainLine");
    s.drag(oe(s), Q, se(s));
    for (var c = 0; c < e.length; c++) {
      var u = e[c];
      var l = u.x;
      var f = u.y;
      var v = n;
      if (u.t == "peaking") {
        v = {
          fill: w,
          stroke: w
        };
      }
      if (u.t == "highshelf" || u.t == "lowshelf") {
        v = {
          fill: b,
          stroke: b
        };
      }
      var d = I.circle(l, f, 4).attr(v).addClass("filterDot");
      d.drag(ce(u, c, I), Q, he(u, c));
      d.dblclick(ee(u, c));
    }
  }
  function ee(e, t) {
    return function () {
      e.gain = 0;
      e.y = F(0);
      de(t);
      n();
    };
  }
  function l(e) {
    return L(e / T) * c;
  }
  function P(e) {
    return te(e / c) * T;
  }
  function te(e) {
    return Math.pow(e, 1 / 4);
  }
  function L(e) {
    return Math.pow(e, 4);
  }
  function F(e) {
    var t = u - k;
    return B * (1 - (e - k) / t);
  }
  function f(e) {
    var t = u - k;
    return (1 - e / B) * t + k;
  }
  function ne(e, t, n) {
    if (e < t) {
      return t;
    }
    if (e > n) {
      return n;
    }
    return e;
  }
  function v(e) {
    var t = e.node.getClientRects()[0];
    return [t.left, t.top];
  }
  var R = {};
  function d(e, t, n) {
    var r = t.frequency;
    var a = t.q;
    var i = t.gain;
    var o = Math.tan(Math.PI * r / E);
    var s = 1 / (1 + 1 / a * o + o * o);
    var c = Math.pow(10, Math.abs(i) / 20);
    var u = 0;
    var l = 0;
    var f = 0;
    var v = 0;
    var d = 0;
    var h = "wheat";
    if (t.t == "peaking") {
      h = w;
      if (i >= 0) {
        s = 1 / (1 + 1 / a * o + o * o);
        u = (1 + c / a * o + o * o) * s;
        l = (o * o - 1) * 2 * s;
        f = (1 - c / a * o + o * o) * s;
        v = l;
        d = (1 - 1 / a * o + o * o) * s;
      } else {
        s = 1 / (1 + c / a * o + o * o);
        u = (1 + 1 / a * o + o * o) * s;
        l = (o * o - 1) * 2 * s;
        f = (1 - 1 / a * o + o * o) * s;
        v = l;
        d = (1 - c / a * o + o * o) * s;
      }
    } else if (t.t == "highshelf") {
      h = b;
      if (i >= 0) {
        s = 1 / (1 + Math.SQRT2 * o + o * o);
        u = (c + Math.sqrt(c * 2) * o + o * o) * s;
        l = (o * o - c) * 2 * s;
        f = (c - Math.sqrt(c * 2) * o + o * o) * s;
        v = (o * o - 1) * 2 * s;
        d = (1 - Math.SQRT2 * o + o * o) * s;
      } else {
        s = 1 / (c + Math.sqrt(c * 2) * o + o * o);
        u = (1 + Math.SQRT2 * o + o * o) * s;
        l = (o * o - 1) * 2 * s;
        f = (1 - Math.SQRT2 * o + o * o) * s;
        v = (o * o - c) * 2 * s;
        d = (c - Math.sqrt(c * 2) * o + o * o) * s;
      }
    } else if (t.t == "lowshelf") {
      h = b;
      if (i >= 0) {
        s = 1 / (1 + Math.SQRT2 * o + o * o);
        u = (1 + Math.sqrt(c * 2) * o + c * o * o) * s;
        l = (c * o * o - 1) * 2 * s;
        f = (1 - Math.sqrt(c * 2) * o + c * o * o) * s;
        v = (o * o - 1) * 2 * s;
        d = (1 - Math.SQRT2 * o + o * o) * s;
      } else {
        s = 1 / (1 + Math.sqrt(c * 2) * o + c * o * o);
        u = (1 + Math.SQRT2 * o + o * o) * s;
        l = (o * o - 1) * 2 * s;
        f = (1 - Math.SQRT2 * o + o * o) * s;
        v = (c * o * o - 1) * 2 * s;
        d = (1 - Math.sqrt(c * 2) * o + c * o * o) * s;
      }
    }
    var m = [];
    for (var g = 0; g < T; g += 2) {
      var p = L(g / T) * Math.PI;
      var y = Math.pow(Math.sin(p / 2), 2);
      var M = Math.log((Math.pow(u + l + f, 2) - (u * l + u * 4 * f + l * f) * 4 * y + u * 16 * f * y * y) / (Math.pow(1 + v + d, 2) - (v + d * 4 + v * d) * 4 * y + d * 16 * y * y));
      M = M * 10 / Math.LN10;
      M = F(M);
      if (M == -Infinity) {
        M = B - 1;
      }
      if (Math.abs(M - B / 2) > 1) {
        m = m.concat([g, M]);
      }
    }
    var k = null;
    if (i >= 0) {
      k = e.gradient("l(.5, 0, .5, 1)" + h + "-" + q);
    } else {
      k = e.gradient("l(.5, 1, .5, 0)" + h + "-" + q);
    }
    if (R[n]) {
      R[n].remove();
    }
    R[n] = e.polyline(m).attr({
      stroke: k,
      "fill-opacity": "0",
      "pointer-events": "none"
    });
  }
  function re(e, t) {
    var n = [];
    for (var r = 0; r < t.length; r++) {
      var a = t[r];
      d(e, a, r);
    }
  }
  function ae(e) {
    for (var t = 5; t < c; t *= 2) {
      var n = P(t);
      e.line(n, B / 2 + 10, n, B / 2 - 10).attr({
        stroke: "wheat",
        "stroke-opacity": 0.25
      });
      e.line(n, B, n, B - 15).attr({
        stroke: "wheat"
      });
      e.text(n, B - 18, "" + Math.round(l(n))).attr({
        fill: "wheat",
        "text-anchor": "middle",
        "font-size": 10
      });
      e.line(n, 0, n, 15).attr({
        stroke: "wheat"
      });
    }
    var r = 5;
    for (var a = k; a < u; a += r) {
      var i = F(a);
      if (Math.abs(u) - Math.abs(a) > r / 2) {
        e.line(0, i, 5, i).attr({
          stroke: "wheat"
        });
        e.text(7, i, "" + a).attr({
          fill: "wheat",
          "font-size": 10,
          "dominant-baseline": "middle"
        });
      }
    }
  }
  function ie(e) {
    if (e < 0.2) {
      e = 0.2;
    }
    if (e > 11) {
      e = 11;
    }
    return e;
  }
  function oe(o) {
    return function (e, t, n, r, a) {
      var i = v(x);
      r = r - i[1];
      if (r < 0 || r >= B) {
        return;
      }
      if (f(r) > M) {
        t -= r - F(M);
        r = F(M);
      }
      o.y = r;
      o.gain = le(f(r));
      this.attr({
        transform: this.data("origTransform") + (this.data("origTransform") ? "T" : "t") + [0, t]
      });
      ve(o);
    };
  }
  function se(e) {
    return function () {
      this.attr({
        fill: q
      });
      chrome.runtime.sendMessage({
        type: "gainUpdated",
        gain: e.gain
      });
      n();
    };
  }
  function ce(o, s, c) {
    return function (e, t, n, r, a) {
      if (a.shiftKey) {
        o.q = ie(o.q + a.movementY / 10);
      } else {
        var i = v(I);
        n = n - i[0];
        r = r - i[1];
        if (n < 0 || n >= T || r < 0 || r >= B) {
          return;
        }
        o.x = n;
        o.y = r;
        o.gain = f(r);
        o.frequency = l(n);
        this.attr({
          transform: this.data("origTransform") + (this.data("origTransform") ? "T" : "t") + [e, t]
        });
      }
      d(c, o, s);
      ue(o, s);
    };
  }
  function ue(e, t) {
    chrome.runtime.sendMessage({
      type: "modifyFilter",
      index: t,
      frequency: l(e.x),
      gain: f(e.y),
      q: e.q
    });
  }
  function le(e) {
    return Math.pow(10, e / 10);
  }
  function fe(e) {
    return Math.log10(e) * 10;
  }
  function ve(e) {
    chrome.runtime.sendMessage({
      type: "modifyGain",
      gain: e.gain
    });
  }
  function de(e) {
    chrome.runtime.sendMessage({
      type: "resetFilter",
      index: e
    });
  }
  function Q() {
    this.data("origTransform", this.transform().local);
    this.attr({
      fill: "black"
    });
  }
  function he(e, t) {
    return function () {
      this.attr({
        fill: q
      });
      chrome.runtime.sendMessage({
        type: "filterUpdated",
        filterType: e.t,
        frequency: e.frequency,
        gain: e.gain,
        q: e.q
      });
      n();
    };
  }
  if (y()) {
    h();
  }
}
(function () {
  scope();
})();

```

```bg.js
var _gaq = _gaq || [];
function scope() {
  var J = chrome.runtime.getManifest().version;
  var e = "11/22/2017";
  _gaq.push(["_setAccount", "UA-64913318-2"]);
  _gaq.push(["_trackPageview"]);
  _gaq.push(["_trackEvent", "backgroundOpen", J]);
  var U = "VERSION";
  var i = "PRESETS";
  var a = "PRESETS.";
  var s = {
    get: function (e, t) {
      t({});
    },
    set: function (e, t) {
      t();
    },
    remove: function (e, t) {
      t();
    }
  };
  var W = null;
  var t = "ears_pro";
  var n = "ears_pro_1";
  var r = "ACTIVE";
  localStorage[" "] = " ";
  s = chrome.storage.sync;
  W = null;
  var o = false;
  function D(e) {
    if (o) {
      console.log(e);
    }
  }
  var M = null;
  var B = null;
  var G = null;
  var Q = null;
  var L = null;
  var V = [];
  var z = [20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480];
  var H = [0.7071, 0.7071, 0.7071, 0.7071, 0.7071, 0.7071, 0.7071, 0.7071, 0.7071, 0.7071, 0.7071];
  var K = 11;
  function X() {}
  var Y = {};
  var Z = false;
  var $;
  function l(e) {
    return a + e;
  }
  function ee(n) {
    if (!localStorage[i]) {
      localStorage[i] = JSON.stringify({});
    }
    var r = JSON.parse(localStorage[i]);
    if (r == null || typeof r != "object") {
      r = {};
    }
    s.get(null, function (e) {
      if (chrome.runtime.lastError) {
        console.log(chrome.runtime.lastError);
      }
      for (var t in e) {
        if (t.startsWith(a)) {
          r[t.slice(a.length)] = e[t];
        }
      }
      n(r);
    });
  }
  function te(e, t, n) {
    var r = JSON.parse(localStorage[i]);
    if (r == null || typeof r != "object") {
      r = {};
    }
    var a = Object.keys(r).length;
    r[e] = t;
    var o = Object.keys(r).length;
    if (W && Object.keys(r) > W && o >= a) {
      n();
      return;
    }
    localStorage[i] = JSON.stringify(r);
    syncPresets = {};
    syncPresets[l(e)] = t;
    s.set(syncPresets, function () {
      if (chrome.runtime.lastError) {
        console.log(chrome.runtime.lastError);
      }
      n();
    });
  }
  function ne(e, t) {
    _gaq.push(["_trackEvent", "deletePreset", e.preset]);
    var n = JSON.parse(localStorage[i]);
    if (n == null || typeof n != "object") {
      n = {};
    }
    delete n[e.preset];
    localStorage[i] = JSON.stringify(n);
    s.remove(l(e.preset), t);
  }
  chrome.storage.onChanged.addListener(function (e, t) {});
  function c() {
    if (Z) {
      M.close();
      M = null;
      Z = false;
      chrome.runtime.onMessage.removeListener($);
      var e = Object.keys(Y);
      for (var t = 0; t < e.length; t++) {
        var n = e[t];
        Y[n].stream.getTracks()[0].stop();
        delete Y[n];
      }
    }
  }
  function re() {
    return new AudioContext({
      latencyHint: "playback"
    });
  }
  function ae() {
    if (Z) {
      M.close();
      M = null;
      Z = false;
      Q = null;
      L = null;
      var e = Object.values(Y);
      Y = {};
      for (var t = 0; t < e.length; t++) {
        var n = e[t].audioSource;
        n.disconnect(B);
      }
      u(e);
    }
  }
  function u(e) {
    var t = null;
    if (Z) {
      return;
    }
    Z = true;
    M = re();
    M.suspend();
    console.log("starting sampleRate: " + M.sampleRate);
    var n = false;
    X = function () {
      if (n) {
        return;
      }
      var e = re();
      var t = M.sampleRate && e.sampleRate && M.sampleRate != e.sampleRate;
      if (t) {
        console.log(e);
        console.log(M);
        n = true;
      }
      e.close();
      if (t) {
        console.log("sampleRate changed... reloading AudioContext");
        ae();
      }
    };
    B = M.createGain();
    B.gain.value = 1;
    G = M.createGain();
    G.gain.value = m();
    if (t) {
      var r = M.createScriptProcessor(t);
      r.onaudioprocess = function (e) {
        var t = e.inputBuffer;
        var n = e.outputBuffer;
        for (var r = 0; r < n.numberOfChannels; r++) {
          n.copyToChannel(t.getChannelData(r), r);
        }
      };
      G.connect(r);
      r.connect(M.destination);
    } else {
      G.connect(M.destination);
    }
    if (!localStorage[U]) {
      localStorage[U] = "0.0.0";
    }
    if (localStorage[" "] == " ") {
      if (localStorage.sync != " ") {
        ee(function (e) {
          var t = Object.keys(e);
          for (var n = 0; n < t.length; n++) {
            var r = t[n];
            te(r, e[r], function () {});
          }
        });
        localStorage.sync = " ";
      }
    }
    localStorage[U] = J;
    V = [];
    var a = {};
    function o() {
      var e = G;
      var t = false;
      for (var n = V.length - 1; n >= 0; n--) {
        if (V[n].gain.value != 0) {
          if (!a[n]) {
            t = true;
          }
          if (t) {
            D(n + " -> " + e.idx);
            V[n].disconnect();
            V[n].connect(e);
          }
          if (a[n]) {
            t = false;
          }
          a[n] = true;
          e = V[n];
          e.idx = n;
        } else if (a[n]) {
          a[n] = false;
          t = true;
        }
      }
      if (t) {
        D("preGain -> " + e.idx);
        B.disconnect();
        B.connect(e);
      }
    }
    for (var i = 0; i < K; i++) {
      var s = M.createBiquadFilter();
      if (i == 0) {
        s.type = "lowshelf";
      } else if (i == K - 1) {
        s.type = "highshelf";
      } else {
        s.type = "peaking";
      }
      var l = "filter" + i;
      var c = localStorage[l];
      if (c) {
        c = JSON.parse(c);
        s.frequency.value = c.f;
        s.gain.value = c.g;
        s.Q.value = c.q;
      } else {
        s.frequency.value = z[i];
        s.gain.value = 0;
        s.Q.value = H[i];
      }
      V.push(s);
      localStorage[l] = JSON.stringify({
        f: s.frequency.value,
        g: s.gain.value,
        q: s.Q.value
      });
    }
    B.connect(G);
    function u(e) {
      f = V[e.index];
      f.gain.value = v(e.gain);
      f.frequency.value = p(e.frequency);
      f.Q.value = d(e.q);
      var t = "filter" + e.index;
      localStorage[t] = JSON.stringify({
        f: f.frequency.value,
        g: f.gain.value,
        q: f.Q.value
      });
      o();
    }
    function g(e) {
      if (e > 10) {
        return 10;
      }
      if (e < 0.00316) {
        return 0.00316;
      }
      return e;
    }
    function v(e) {
      if (e < -30) {
        return -30;
      }
      if (e > 30) {
        return 30;
      }
      return e;
    }
    function p(e) {
      if (e < 5) {
        return 5;
      }
      if (e > 20000) {
        return 20000;
      }
      return e;
    }
    function d(e) {
      if (e < 0.2) {
        e = 0.2;
      }
      if (e > 11) {
        e = 11;
      }
      return e;
    }
    function y(e) {
      var t = g(e.gain);
      G.gain.value = g(t);
      h(t);
    }
    function m() {
      if (!localStorage.GAIN) {
        localStorage.GAIN = JSON.stringify(1);
      }
      return JSON.parse(localStorage.GAIN);
    }
    function h(e) {
      localStorage.GAIN = JSON.stringify(e);
    }
    function S(e) {
      _gaq.push(["_trackEvent", "preset", "save"]);
      var t = [];
      var n = [];
      var r = [];
      for (var a = 0; a < V.length; a++) {
        var o = V[a];
        t.push(o.frequency.value);
        n.push(o.gain.value);
        r.push(o.Q.value);
      }
      var i = {
        frequencies: t,
        gains: n,
        qs: r
      };
      te(e.preset, i, E);
    }
    function q(i) {
      ee(function (e) {
        var t = [];
        var n = [];
        var r = [];
        if (i.preset == "bassBoost") {
          for (var a = 0; a < z.length; a++) {
            t.push(z[a]);
            n.push(0);
            r.push(H[a]);
          }
          t[0] = 340;
          n[0] = 5;
        } else {
          var o = e[i.preset];
          if (o) {
            t = o.frequencies;
            n = o.gains;
            r = o.qs;
          } else {
            return;
          }
        }
        _gaq.push(["_trackEvent", "preset", "set"]);
        for (var a = 0; a < t.length; a++) {
          u({
            index: a,
            frequency: t[a],
            gain: n[a],
            q: r[a]
          });
        }
        E();
      });
    }
    function b(t) {
      N(function (e) {
        T(t, e, true);
        o();
        E();
      });
    }
    function _() {
      var e = {
        type: "sendWorkspaceStatus",
        eqFilters: [],
        streams: [],
        gain: G.gain.value
      };
      for (var t = 0; t < V.length; t++) {
        var n = V[t];
        e.eqFilters.push({
          frequency: n.frequency.value,
          gain: n.gain.value,
          type: n.type,
          q: n.Q.value
        });
      }
      for (var r in Y) {
        var a = Y[r].tab;
        e.streams.push(Y[r].tab);
      }
      chrome.runtime.sendMessage(e);
    }
    function k() {
      chrome.runtime.sendMessage({
        type: "sendSampleRate",
        Fs: M.sampleRate
      });
    }
    function O() {
      ee(function (e) {
        chrome.runtime.sendMessage({
          type: "sendPresets",
          presets: e
        });
      });
    }
    function E() {
      w();
      _();
      k();
      O();
    }
    function N(t) {
      chrome.tabs.query({
        currentWindow: true,
        active: true
      }, function (e) {
        if (e.length != 1) {
          console.error("num active tabs != 1");
        } else {
          t(e[0]);
        }
      });
    }
    function w() {
      N(function (e) {
        chrome.runtime.sendMessage({
          type: "sendCurrentTabStatus",
          streaming: e.id in Y
        });
      });
    }
    function T(e, t, n) {
      if (!e) {
        console.log("null stream, aborting");
        return;
      }
      if (n) {
        _gaq.push(["_trackEvent", "tabStream", "added"]);
      }
      if (Object.keys(Y).length == 0) {
        M.resume();
      }
      if (t.id in Y) {
        console.log("had stream, stopping");
        _gaq.push(["_trackEvent", "tabStream", "hadDuplicate"]);
        Y[t.id].stream.getTracks()[0].stop();
        delete Y[t.id];
      }
      var r = M.createMediaStreamSource(e);
      r.connect(B);
      Y[t.id] = {
        stream: e,
        tab: t,
        audioSource: r
      };
    }
    var R = {};
    var C = {};
    function x(t) {
      console.log(t);
      chrome.windows.get(chrome.windows.WINDOW_ID_CURRENT, null, function (e) {
        if (e.state != "fullscreen") {
          C[e.id] = e.state;
        }
        if (t.fullscreen) {
          chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, {
            state: "fullscreen"
          }, null);
        } else {
          chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, {
            state: C[e.id] || "fullscreen"
          }, null);
        }
      });
      if (t.status == "active") {
        R[t.tabId] = t;
      } else if (t.status == "stopped" || t.status == "error") {
        delete R[t.tabId];
      }
    }
    function j() {
      _gaq.push(["_trackEvent", "currentTab", "removed"]);
      N(I);
    }
    function I(e) {
      console.log("disconnectedTab id " + e.id);
      if (e.id in Y) {
        _gaq.push(["_trackEvent", "tabStream", "removed"]);
        var t = Y[e.id].stream;
        t.getTracks()[0].stop();
        delete Y[e.id];
      }
      if (Object.keys(Y).length == 0) {
        M.suspend();
      }
      E();
    }
    function P() {
      for (var e = 0; e < K; e++) {
        u({
          index: e,
          gain: 0,
          frequency: z[e],
          q: H[e]
        });
      }
      y({
        gain: 1
      });
      _gaq.push(["_trackEvent", "filterUpdated", "resetAll"]);
      E();
    }
    function A(e) {
      u({
        index: e.index,
        gain: 0,
        frequency: z[e.index],
        q: H[e.index]
      });
      _gaq.push(["_trackEvent", "filterUpdated", "reset"]);
    }
    function F() {
      function t(e, t) {
        var n = document.createElement("a");
        var r = new Blob([t], {
          type: "text/plain;charset=UTF-8"
        });
        n.href = window.URL.createObjectURL(r);
        n.download = e;
        n.style.display = "none";
        document.body.appendChild(n);
        n.click();
        delete n;
      }
      ee(function (e) {
        t("EarsAudioToolkitPresets.json", JSON.stringify(e, null, 2));
      });
    }
    $ = function (e, t, n) {
      if (e.type == "eqTab") {
        if (e.on) {
          chrome.tabs.getSelected(null, function (e) {
            if (e.url.startsWith("chrome-extension://" + chrome.runtime.id)) {
              return;
            }
            chrome.tabCapture.capture({
              audio: true,
              video: false
            }, b);
          });
        } else {
          j();
        }
      }
      if (e.type == "getCurrentTabStatus") {
        w();
      }
      if (e.type == "getWorkspaceStatus") {
        _();
      }
      if (e.type == "getFullRefresh") {
        E();
      }
      if (e.type == "onPopupOpen") {
        X();
      }
      if (e.type == "modifyFilter") {
        u(e);
      }
      if (e.type == "modifyGain") {
        y(e);
      }
      if (e.type == "gainUpdated") {
        _gaq.push(["_trackEvent", "gainUpdated", "gain"]);
      }
      if (e.type == "filterUpdated") {
        _gaq.push(["_trackEvent", "filterUpdated", e.filterType]);
      }
      if (e.type == "disconnectTab") {
        I(e.tab);
      }
      if (e.type == "resetFilters") {
        P();
      }
      if (e.type == "resetFilter") {
        A(e);
      }
      if (e.type == "preset") {
        q(e);
      }
      if (e.type == "savePreset") {
        S(e);
      }
      if (e.type == "importPresets") {
        var r = e.presets;
        var a = Object.keys(r);
        for (var o = 0; o < a.length; o++) {
          te(a[o], r[a[o]], E);
        }
      }
      if (e.type == "deletePreset") {
        ne(e, E);
      }
      if (e.type == "exportPresets") {
        F();
      }
      if (e.type == "getFFT") {
        if (W) {
          chrome.runtime.sendMessage({
            type: "fft",
            fft: []
          });
          return;
        }
        if (Q) {
          L = performance.now();
          var i = new Float32Array(Q.frequencyBinCount);
          Q.getFloatFrequencyData(i);
          n({
            type: "fft",
            fft: Array.from(i)
          });
        } else {
          Q = M.createAnalyser();
          Q.fftSize = 8192;
          Q.smoothingTimeConstant = 0.5;
          console.log("created analyser");
          console.log(Q);
          G.connect(Q);
          n({
            type: "fft",
            fft: []
          });
        }
      }
    };
    if (e) {
      for (var i = 0; i < e.length; i++) {
        T(e[i].stream, e[i].tab, false);
      }
      o();
      E();
    } else {
      chrome.runtime.onMessage.addListener($);
      chrome.tabCapture.onStatusChanged.addListener(x);
    }
  }
  u();
  setInterval(function () {
    if (L) {
      if (performance.now() - L > 1000) {
        if (G && Q) {
          G.disconnect(Q);
          Q = null;
          console.log("disconnected analyser");
        }
      }
    }
  }, 1000);
}
scope();
```

```manifest.json
{
"update_url": "https://clients2.google.com/service/update2/crx",

  "manifest_version": 2,

  "name": "Ears: Bass Boost, EQ Any Audio!",
  "short_name": "Ears",
  "description": "EQ any audio you find on the web, live! Crank the bass, dim the highs, up the vocals: all with Ears!",
  "version": "1.3.12",
  "author": "Kevin King",
  "icons": {
    "16": "ears16.png",
    "32": "ears32.png",
    "48": "ears48.png",
    "64": "ears64.png",
    "128": "ears128.png"
  },

  "browser_action": {
    "default_icon": "ears128.png",
    "default_popup": "popup.html"
  },
  "permissions": ["activeTab", "tabCapture", "tabs", "storage"],
  "background": {
    "scripts": ["bg.js"]
  }
}

```

```popup.html
<link rel="stylesheet" type="text/css" href="popup.css" />
<script src="snap.svg-min.js"></script>
<script src="popup.js"></script>
<body>
  <span id="warning"
    ><em
      >Warning: Loud audio will damage hearing/speakers! Listen responsibly
      :)</em
    ></span
  >
  <span id="contactInfo"
    >Feel free to
    <a
      href="mailto:<PRESIDIO_ANONYMIZED_EMAIL_ADDRESS>"
      target="_blank"
      rel="noopener noreferrer"
      >email me</a
    >
    or visit the
    <a
      target="_blank"
      rel="noopener noreferrer"
      href="https://chrome.google.com/webstore/detail/ears-audio-toolkit/nfdfiepdkbnoanddpianalelglmfooik"
      >chrome web store</a
    >.</span
  >
  <h1>Ears Audio Toolkit</h1>
  <div id="lt"></div>
  <div class="center">
    <a
      id="pl"
      target="_blank"
      href="https://chrome.google.com/webstore/detail/ears-audio-toolkit/nfdfiepdkbnoanddpianalelglmfooik"
      rel="noopener noreferrer"
      >Purchase Ears or Leave Feedback</a
    >
  </div>
  <div id="mainDiv">
    <input id="tab-1" type="radio" name="tab-group" checked="checked" />
    <label id="label-1" for="tab-1">Controls</label>
    <input id="tab-2" type="radio" name="tab-group" />
    <label for="tab-2">Guide</label>
    <input id="tab-3" type="radio" name="tab-group" />
    <label for="tab-3">Active Tabs</label>
    <div id="eqDiv">
      <svg id="gainSvg" width="30" height="300"></svg>
      <svg id="eqSvg" width="600" height="300"></svg>
      <div id="eqOptionsDiv">
        <button id="vizButton" class="on">
          <span>Spectrum<br />Visualizer</span>
        </button>
      </div>
    </div>
    <div id="guideDiv">
      <p>
        When you click the Ears icon, the current tab is added to Ears. To add
        another tab, open that tab, then click the Ears icon again.
      </p>
      <h3>EQ</h3>
      <p>
        The movable dots each represent a filter used to equalize (or EQ) the
        audio. Filters allow you to boost or lower certain frequencies (i.e.
        just part of the treble or bass). The purple dots act as shelf filters
        while the blue dots act as notch filters.
      </p>
      <p>
        Moving a dot left or right selects lower or higher frequencies, while
        moving a dot up or down increases or decreases the volume of those
        frequencies. Shift-dragging a filter up and down will widen or narrow
        the filter, also called changing its Q value.
      </p>

      <p>
        The frequency spectrum visualizer shows you what you are listening to
        and how your EQ has changed it. Lower sounds appear to the left and
        higher to the right (with volume indicated by the height of a peak).
        <a
          href="https://www.youtube.com/watch?v=VMnkYTan5pY"
          rel="noreferrer
			noopener"
          target="_blank"
          >Watch the demo</a
        >.
      </p>

      <p>
        For more information about how to use filters, check out
        <a
          href="https://music.tutsplus.com/tutorials/how-to-use-a-parametric-equalizer--audio-2301"
          rel="noopener noreferrer"
          target="_blank"
          >this guide</a
        >.
      </p>
      <h3>Presets</h3>
      <p>
        To save a configuration of filters for later, type a name in the text
        box below, then click 'Save Preset' or press ENTER. The save and delete
        buttons operate on whichever preset's name is selected in the text box.
      </p>
    </div>
    <div id="tabsDiv">
      <div id="eqTabList"></div>
    </div>
  </div>
  <div id="filterButtonsDiv">
    <input id="presetNameInput" type="text" placeholder="Preset Name" />
    <button id="savePresetButton">+ Save Preset</button>
    <button id="deletePresetButton">- Delete Preset</button>
    <button id="resetFiltersButton">Reset Filters</button>
    <button
      id="exportPresetsButton"
      title="Export all saved presets to a file."
    >
      Export Presets
    </button>
    <button
      id="importPresetsButton"
      title="Import presets that were exported from Ears."
    >
      Import Presets
    </button>
    <input type="file" id="importPresetsFile" />
  </div>
  <div id="presetDiv">
    <button id="bassBoostButton">Bass Boost</button>
    <span id="userPresetSpan"></span>
  </div>
  <div id="eqThisTabDiv">
    <button id="eqTabButton" class="center">EQ Current Tab</button>
  </div>
  <div id="requiresProDiv">
    Saving more than 3 presets requires Ears Pro. Click the Pro tab above to
    start your 7-day free trial!
  </div>
  <!-- <div id='upgradeExtensionDiv'>
	  If you see this, right-click the Ears icon, click 'Manage Extensions', and then click 'Update' to update Ears.
  </div> -->
  <a
    id="fullscreen-link"
    target="_blank"
    href="chrome-extension://nfdfiepdkbnoanddpianalelglmfooik/popup.html"
    style="position: fixed; bottom: 8; right: 8"
    >Open in Full Window</a
  >
  <a
    id="proPromo"
    href="https://www.buymeacoffee.com/earsaudio"
    style="position: fixed; bottom: 8; left: 8; color: #add8e6"
    target="_blank"
    >Enjoying Ears? Buy me a coffee &#9749;&#x1F64F;</a
  >
</body>
```

```popup.css
.center {
    margin-left : auto;
    margin-right : auto;
	display : block;
}

#mainDiv {
	position: relative;
	height: 325px;
}

/*
 * TABS
 */
#mainDiv > input {
	height: 1.2em;
	visibility: hidden;
}

#mainDiv label {
	background: #2C3E50;
	border-color: wheat;
	border-style: solid;
	border-width: 1px;
	border-radius: .5em .5em 0 0;
	color: wheat;
	cursor: pointer;
	display: block;
	float: left;
	font-size: 1em;
	height: 1.5em;
	line-height: 1.5em;
	margin-right: .25em;
	padding: 0 0.5em;
	text-align: center;
}

#label-1 {
	margin-left: 33px;
}

#mainDiv input:hover + label {
	background: #756A56;
}

#mainDiv input:checked + label {
	background-color: wheat;
	color: #2C3E50;
	position: relative;
	z-index: 6;
	-webkit-transition: .1s;
	-moz-transition: .1s;
	-o-transition: .1s;
	-ms-transition: .1s;
}

#mainDiv > div {
	opacity: 0;
	z-index: -100;
	transition: all linear 0.1s;
	height: 305px;
	width: 635px;
	position: absolute;
}

#eqDiv {
	overflow: hidden !important;
}

#mainDiv input#tab-1:checked ~ #eqDiv,
#mainDiv input#tab-2:checked ~ #guideDiv,
#mainDiv input#tab-3:checked ~ #tabsDiv,
#mainDiv input#tab-4:checked ~ #proDiv {
    opacity: 1;
    z-index: 100;
}

#guideDiv {
	overflow: scroll;
}

#guideDiv p {
	margin-top: 5px;
	margin-bottom: 15px;
}

#guideDiv h3 {
	margin-bottom: 0px;
	margin-top: 10px;
	line-height: 1em;
}

#tabsDiv {
	margin-left: auto;
	margin-right: auto;
	display: flex;
	flex-flow: row wrap;
	align-content: center;
	justify-content: center;
	margin-top: 10px;
}

#tabsDiv #eqTabList {
	width: 100%;
	page-break-after: always;
	break-after: always;
	display: flex;
	justify-content: center;
	align-items: center;
}

input.visible {
  visibility: visible !important;
}

#eqSvg, #gainSvg {
	display: inline;
}

#eqTabList {
    clear : both;
}

#filterButtonsDiv {
    float : right;
    margin-bottom : 10px;
}

#filterButtonsDiv button {
    margin-left : 5px;
}

#presetDiv button {
    margin-left : 5px;
	margin-bottom: 5px;
}

#presetDiv {
    float : right;
    margin-bottom : 10px;
    clear: both;
}

#eqTabList span, #infoDiv {
    color : wheat;
    margin-left : 5px;
}

#proDiv {
	display: flex;
	flex-flow: column;
	align-content: center;
	justify-content: center;
	align-items: center;
}

.tabFavIcon {
    max-height : 16px;
    max-width : 16px;
    float : right;
    margin-left : 4px;
}

body {
    background-color : #2C3E50;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    min-width : 640px;
}

html {
    min-height : 500px;
}

h1,h2,h3,#pl,#lt {
    color : wheat;
}

#lt {
    line-height : 1.5;
}

#pl {
    color : wheat;
    outline : none;
    display : none;
    font-size : 14px;
}

#presetNameInput, button {
    background-color : #2C3E50;
    color : wheat;
    outline : none;
    border-width : 1px;
    border-color : wheat;
    border-style : solid;
	border-radius: 2px;
}

#presetNameInput {
    height: 20px;
    width: 75px;
}

#importPresetsFile {
	display: none;
}

em, a, body {
    color : wheat;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

#amzDiv {
	margin-top: 11px;
	float: left;
	margin-bottom: 5px;
}

a.button {
    background-color : #2C3E50;
    border-width : 1px;
	color: wheat;
    border-color : wheat;
    border-style : solid;
	padding: 3px;

    text-decoration: none;
}

form {
	margin-bottom: 0px;
}

a:focus {
    outline: 0;
}

button :focus {

}
#warning, #contactInfo {
    margin-left : auto;
    margin-right : 5px;
    float : right;
    clear: both;
}

.filterDot {
    pointer-events : all;
}
.filterDot:hover {
    stroke : white;
    stroke-width : 2px;
}

.gainLine {
    stroke-width: 5px;
}
.gainLine:hover {
    stroke: white;
}

#infoDiv{
    float:right;
	margin-bottom: 5px;
}
#paypal {
    display: inline;
}

#eqThisTabDiv {
	clear: both;
}

#eqOptionsDiv {
	position: absolute;
	top: 21;
	left: -16;
}

#vizButton {
	font-size: 10px;
	-webkit-transform: rotate(-90deg);   
	-moz-transform: rotate(-90deg);
	-ms-transform: rotate(-90deg);
	-o-transform: rotate(-90deg);
	transform: rotate(-90deg);
}

#vizButton.on {
    background-color : wheat;
    color : #2C3E50;
    outline : none;
    border-width : 1px;
    border-color : wheat;
    border-style : solid;
}

#requiresProDiv {
	position: absolute;
	top: 102px;
    left: 100px;
    z-index: 1000;
    width: 484px;
    text-align: center;
    pointer-events: none;
	opacity: 0;
	transition: all linear 0.2s;
}

#upgradeExtensionDiv {
	position: absolute;
	top: 102px;
    left: 100px;
    width: 484px;
    text-align: center;
    pointer-events: none;
	transition: all linear 0.2s;
}

#requiresProDiv.show {
	opacity: 1;
}
```