Guest User

global-media-controls-7.9.js

a guest
Apr 16th, 2026
178
0
Never
5
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 76.32 KB | Source Code | 0 0
  1. /*
  2.  * Global Media Controls Panel
  3.  * Written by Tam710562
  4.  */
  5.  
  6. (() => {
  7.   'use strict';
  8.  
  9.   const gnoh = {
  10.     i18n: {
  11.       getMessageName(message, type) {
  12.         message = (type ? type + '\x04' : '') + message;
  13.         return message.replace(/[^a-z0-9]/g, (i) => '_' + i.codePointAt(0) + '_') + '0';
  14.       },
  15.       getMessage(message, type) {
  16.         return chrome.i18n.getMessage(this.getMessageName(message, type)) || message;
  17.       },
  18.     },
  19.     createElement(tagName, attribute, parent, inner, options) {
  20.       if (typeof tagName === 'undefined') {
  21.         return;
  22.       }
  23.       if (typeof options === 'undefined') {
  24.         options = {};
  25.       }
  26.       if (typeof options.isPrepend === 'undefined') {
  27.         options.isPrepend = false;
  28.       }
  29.       const el = document.createElement(tagName);
  30.       if (!!attribute && typeof attribute === 'object') {
  31.         for (const key in attribute) {
  32.           if (key === 'text') {
  33.             el.textContent = attribute[key];
  34.           } else if (key === 'html') {
  35.             el.innerHTML = attribute[key];
  36.           } else if (key === 'style' && typeof attribute[key] === 'object') {
  37.             for (const css in attribute.style) {
  38.               el.style.setProperty(css, attribute.style[css]);
  39.             }
  40.           } else if (key === 'events' && typeof attribute[key] === 'object') {
  41.             for (const event in attribute.events) {
  42.               if (typeof attribute.events[event] === 'function') {
  43.                 el.addEventListener(event, attribute.events[event]);
  44.               }
  45.             }
  46.           } else if (typeof el[key] !== 'undefined') {
  47.             el[key] = attribute[key];
  48.           } else {
  49.             if (typeof attribute[key] === 'object') {
  50.               attribute[key] = JSON.stringify(attribute[key]);
  51.             }
  52.             el.setAttribute(key, attribute[key]);
  53.           }
  54.         }
  55.       }
  56.       if (inner) {
  57.         if (!Array.isArray(inner)) {
  58.           inner = [inner];
  59.         }
  60.         for (const element of inner) {
  61.           if (element.nodeName) {
  62.             el.append(element);
  63.           } else {
  64.             el.append(this.createElementFromHTML(element));
  65.           }
  66.         }
  67.       }
  68.       if (typeof parent === 'string') {
  69.         parent = document.querySelector(parent);
  70.       }
  71.       if (parent) {
  72.         if (options.isPrepend) {
  73.           parent.prepend(el);
  74.         } else {
  75.           parent.append(el);
  76.         }
  77.       }
  78.       return el;
  79.     },
  80.     createElementFromHTML(html) {
  81.       return this.createElement('template', {
  82.         html: (html || '').trim(),
  83.       }).content;
  84.     },
  85.     color: {
  86.       rgbToHex(r, g, b) {
  87.         return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  88.       },
  89.       rgb2lab(rgb) {
  90.         let r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255, x, y, z;
  91.         r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
  92.         g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
  93.         b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
  94.         x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
  95.         y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
  96.         z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
  97.         x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116;
  98.         y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116;
  99.         z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116;
  100.         return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)]
  101.       },
  102.       deltaE(rgbA, rgbB) {
  103.         const labA = this.rgb2lab(rgbA);
  104.         const labB = this.rgb2lab(rgbB);
  105.         const deltaL = labA[0] - labB[0];
  106.         const deltaA = labA[1] - labB[1];
  107.         const deltaB = labA[2] - labB[2];
  108.         const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
  109.         const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
  110.         const deltaC = c1 - c2;
  111.         let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
  112.         deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
  113.         const sc = 1.0 + 0.045 * c1;
  114.         const sh = 1.0 + 0.015 * c1;
  115.         const deltaLKlsl = deltaL / (1.0);
  116.         const deltaCkcsc = deltaC / (sc);
  117.         const deltaHkhsh = deltaH / (sh);
  118.         const i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
  119.         return i < 0 ? 0 : Math.sqrt(i);
  120.       },
  121.       getLuminance(r, g, b) {
  122.         return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  123.       },
  124.       isLight(r, g, b) {
  125.         return this.getLuminance(r, g, b) < 156;
  126.       },
  127.       shadeColor(r, g, b, percent) {
  128.         const t = percent < 0 ? 0 : 255 * percent;
  129.         const p = percent < 0 ? 1 + percent : 1 - percent;
  130.         return {
  131.           r: Math.round(parseInt(r) * p + t),
  132.           g: Math.round(parseInt(g) * p + t),
  133.           b: Math.round(parseInt(b) * p + t),
  134.         };
  135.       },
  136.     },
  137.     element: {
  138.       appendAtIndex(element, parentElement, index) {
  139.         if (index >= parentElement.children.length) {
  140.           parentElement.append(element)
  141.         } else {
  142.           parentElement.insertBefore(element, parentElement.children[index])
  143.         }
  144.       },
  145.     },
  146.     getReactProps(element) {
  147.       if (typeof element === 'string') {
  148.         element = document.querySelector(element);
  149.       }
  150.       if (!element || element.ownerDocument !== document) {
  151.         return;
  152.       }
  153.       if (!this.reactPropsKey) {
  154.         this.reactPropsKey = Object.keys(element).find((key) => key.startsWith('__reactProps'));
  155.       }
  156.       return element[this.reactPropsKey];
  157.     },
  158.     string: {
  159.       removeDiacritics(str) {
  160.         if (!this._diacriticsMap) {
  161.           const defaultDiacriticsRemovalMap = [
  162.             { 'base': 'A', 'letters': '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F' },
  163.             { 'base': 'AA', 'letters': '\uA732' },
  164.             { 'base': 'AE', 'letters': '\u00C6\u01FC\u01E2' },
  165.             { 'base': 'AO', 'letters': '\uA734' },
  166.             { 'base': 'AU', 'letters': '\uA736' },
  167.             { 'base': 'AV', 'letters': '\uA738\uA73A' },
  168.             { 'base': 'AY', 'letters': '\uA73C' },
  169.             { 'base': 'B', 'letters': '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181' },
  170.             { 'base': 'C', 'letters': '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E' },
  171.             { 'base': 'D', 'letters': '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0' },
  172.             { 'base': 'DZ', 'letters': '\u01F1\u01C4' },
  173.             { 'base': 'Dz', 'letters': '\u01F2\u01C5' },
  174.             { 'base': 'E', 'letters': '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E' },
  175.             { 'base': 'F', 'letters': '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' },
  176.             { 'base': 'G', 'letters': '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E' },
  177.             { 'base': 'H', 'letters': '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D' },
  178.             { 'base': 'I', 'letters': '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197' },
  179.             { 'base': 'J', 'letters': '\u004A\u24BF\uFF2A\u0134\u0248' },
  180.             { 'base': 'K', 'letters': '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2' },
  181.             { 'base': 'L', 'letters': '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780' },
  182.             { 'base': 'LJ', 'letters': '\u01C7' },
  183.             { 'base': 'Lj', 'letters': '\u01C8' },
  184.             { 'base': 'M', 'letters': '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C' },
  185.             { 'base': 'N', 'letters': '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4' },
  186.             { 'base': 'NJ', 'letters': '\u01CA' },
  187.             { 'base': 'Nj', 'letters': '\u01CB' },
  188.             { 'base': 'O', 'letters': '\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C' },
  189.             { 'base': 'OI', 'letters': '\u01A2' },
  190.             { 'base': 'OO', 'letters': '\uA74E' },
  191.             { 'base': 'OU', 'letters': '\u0222' },
  192.             { 'base': 'OE', 'letters': '\u008C\u0152' },
  193.             { 'base': 'oe', 'letters': '\u009C\u0153' },
  194.             { 'base': 'P', 'letters': '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754' },
  195.             { 'base': 'Q', 'letters': '\u0051\u24C6\uFF31\uA756\uA758\u024A' },
  196.             { 'base': 'R', 'letters': '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782' },
  197.             { 'base': 'S', 'letters': '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784' },
  198.             { 'base': 'T', 'letters': '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786' },
  199.             { 'base': 'TZ', 'letters': '\uA728' },
  200.             { 'base': 'U', 'letters': '\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244' },
  201.             { 'base': 'V', 'letters': '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245' },
  202.             { 'base': 'VY', 'letters': '\uA760' },
  203.             { 'base': 'W', 'letters': '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72' },
  204.             { 'base': 'X', 'letters': '\u0058\u24CD\uFF38\u1E8A\u1E8C' },
  205.             { 'base': 'Y', 'letters': '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE' },
  206.             { 'base': 'Z', 'letters': '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762' },
  207.             { 'base': 'a', 'letters': '\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250' },
  208.             { 'base': 'aa', 'letters': '\uA733' },
  209.             { 'base': 'ae', 'letters': '\u00E6\u01FD\u01E3' },
  210.             { 'base': 'ao', 'letters': '\uA735' },
  211.             { 'base': 'au', 'letters': '\uA737' },
  212.             { 'base': 'av', 'letters': '\uA739\uA73B' },
  213.             { 'base': 'ay', 'letters': '\uA73D' },
  214.             { 'base': 'b', 'letters': '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253' },
  215.             { 'base': 'c', 'letters': '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184' },
  216.             { 'base': 'd', 'letters': '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A' },
  217.             { 'base': 'dz', 'letters': '\u01F3\u01C6' },
  218.             { 'base': 'e', 'letters': '\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD' },
  219.             { 'base': 'f', 'letters': '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C' },
  220.             { 'base': 'g', 'letters': '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F' },
  221.             { 'base': 'h', 'letters': '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265' },
  222.             { 'base': 'hv', 'letters': '\u0195' },
  223.             { 'base': 'i', 'letters': '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131' },
  224.             { 'base': 'j', 'letters': '\u006A\u24D9\uFF4A\u0135\u01F0\u0249' },
  225.             { 'base': 'k', 'letters': '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3' },
  226.             { 'base': 'l', 'letters': '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747' },
  227.             { 'base': 'lj', 'letters': '\u01C9' },
  228.             { 'base': 'm', 'letters': '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F' },
  229.             { 'base': 'n', 'letters': '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5' },
  230.             { 'base': 'nj', 'letters': '\u01CC' },
  231.             { 'base': 'o', 'letters': '\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275' },
  232.             { 'base': 'oi', 'letters': '\u01A3' },
  233.             { 'base': 'ou', 'letters': '\u0223' },
  234.             { 'base': 'oo', 'letters': '\uA74F' },
  235.             { 'base': 'p', 'letters': '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755' },
  236.             { 'base': 'q', 'letters': '\u0071\u24E0\uFF51\u024B\uA757\uA759' },
  237.             { 'base': 'r', 'letters': '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783' },
  238.             { 'base': 's', 'letters': '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B' },
  239.             { 'base': 't', 'letters': '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787' },
  240.             { 'base': 'tz', 'letters': '\uA729' },
  241.             { 'base': 'u', 'letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289' },
  242.             { 'base': 'v', 'letters': '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C' },
  243.             { 'base': 'vy', 'letters': '\uA761' },
  244.             { 'base': 'w', 'letters': '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73' },
  245.             { 'base': 'x', 'letters': '\u0078\u24E7\uFF58\u1E8B\u1E8D' },
  246.             { 'base': 'y', 'letters': '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF' },
  247.             { 'base': 'z', 'letters': '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763' }
  248.           ];
  249.  
  250.           this._diacriticsMap = {};
  251.  
  252.           for (const diacritic of defaultDiacriticsRemovalMap) {
  253.             for (const letter of diacritic.letters) {
  254.               this._diacriticsMap[letter] = diacritic.base;
  255.             }
  256.           }
  257.         }
  258.  
  259.         return str.replace(/[^\u0000-\u007E]/g, (a) => {
  260.           return this._diacriticsMap[a] || a;
  261.         });
  262.       },
  263.     },
  264.     addStyle(css, id, isNotMin) {
  265.       this.styles = this.styles || {};
  266.       if (Array.isArray(css)) {
  267.         css = css.join(isNotMin === true ? '\n' : '');
  268.       }
  269.       id = id || this.uuid.generate(Object.keys(this.styles));
  270.       this.styles[id] = this.createElement('style', {
  271.         html: css || '',
  272.         'data-id': id,
  273.       }, document.head);
  274.       return this.styles[id];
  275.     },
  276.     timeOut(callback, condition, timeOut = 300) {
  277.       let timeOutId = setTimeout(function wait() {
  278.         let result;
  279.         if (!condition) {
  280.           result = document.getElementById('browser');
  281.         } else if (typeof condition === 'string') {
  282.           result = document.querySelector(condition);
  283.         } else if (typeof condition === 'function') {
  284.           result = condition();
  285.         } else {
  286.           return;
  287.         }
  288.         if (result) {
  289.           callback(result);
  290.         } else {
  291.           timeOutId = setTimeout(wait, timeOut);
  292.         }
  293.       }, timeOut);
  294.  
  295.       function stop() {
  296.         if (timeOutId) {
  297.           clearTimeout(timeOutId);
  298.         }
  299.       }
  300.  
  301.       return {
  302.         stop,
  303.       };
  304.     },
  305.     observeDOM(obj, callback, config) {
  306.       const obs = new MutationObserver((mutations, observer) => {
  307.         if (config || (mutations[0].addedNodes.length || mutations[0].removedNodes.length)) {
  308.           callback(mutations, observer);
  309.         }
  310.       });
  311.       obs.observe(obj, config || {
  312.         childList: true,
  313.         subtree: true,
  314.       });
  315.     },
  316.     uuid: {
  317.       generate(ids) {
  318.         let d = Date.now() + performance.now();
  319.         let r;
  320.         const id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  321.           r = (d + Math.random() * 16) % 16 | 0;
  322.           d = Math.floor(d / 16);
  323.           return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  324.         });
  325.  
  326.         if (Array.isArray(ids) && ids.includes(id)) {
  327.           return this.generate(ids);
  328.         }
  329.         return id;
  330.       },
  331.     },
  332.   };
  333.  
  334.   const tabs = {};
  335.  
  336.   const name = 'Global Media Controls';
  337.   const messageType = 'global-media-controls';
  338.   const nameAttribute = 'global-media-controls';
  339.   const code = 'data:text/html,' + encodeURIComponent('<title>' + name + '</title>');
  340.   const webPanelId = 'WEBPANEL_c650d566-8020-4841-8a5d-1555b86da114';
  341.   const colorLoaded = {};
  342.   const buttonBadges = [];
  343.   let dragSource = null;
  344.   let lucidModeVideo = false;
  345.  
  346.   const langs = {
  347.     search: gnoh.i18n.getMessage('Search', 'verb'),
  348.     closePanel: gnoh.i18n.getMessage('Close Panel'),
  349.   };
  350.  
  351.   const icons = {
  352.     playlistMusic: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M12 13c0 1.105-1.12 2-2.5 2S7 14.105 7 13s1.12-2 2.5-2s2.5.895 2.5 2z"/><path fill-rule="evenodd" d="M12 3v10h-1V3h1z"/><path d="M11 2.82a1 1 0 0 1 .804-.98l3-.6A1 1 0 0 1 16 2.22V4l-5 1V2.82z"/><path fill-rule="evenodd" d="M0 11.5a.5.5 0 0 1 .5-.5H4a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 .5 7H8a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5zm0-4A.5.5 0 0 1 .5 3H8a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5z"/></svg>',
  353.     play: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z"/></svg>',
  354.     pause: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M14,19H18V5H14M6,19H10V5H6V19Z"/></svg>',
  355.     close: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/></svg>',
  356.     closePanel: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="m12.5 5-1.4-1.4-3.1 3-3.1-3L3.5 5l3.1 3.1-3 2.9 1.5 1.4L8 9.5l2.9 2.9 1.5-1.4-3-2.9"/></svg>',
  357.     pictureInPicture: {
  358.       off: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M19,11H11V17H19V11M17,15H13V13H17V15M21,3H3A2,2 0 0,0 1,5V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19V5C23,3.88 22.1,3 21,3M21,19H3V4.97H21V19Z"/></svg>',
  359.       on: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M19,11H11V17H19V11M23,19V5C23,3.88 22.1,3 21,3H3A2,2 0 0,0 1,5V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19M21,19H3V4.97H21V19Z"/></svg>'
  360.     },
  361.     tab: {
  362.       on: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M21,3H3A2,2 0 0,0 1,5V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19V5A2,2 0 0,0 21,3M21,19H3V5H13V9H21V19Z"/></svg>',
  363.       off: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M1,9H3V7H1V9M1,13H3V11H1V13M1,5H3V3A2,2 0 0,0 1,5M9,21H11V19H9V21M1,17H3V15H1V17M3,21V19H1A2,2 0 0,0 3,21M21,3H13V9H23V5A2,2 0 0,0 21,3M21,17H23V15H21V17M9,5H11V3H9V5M5,21H7V19H5V21M5,5H7V3H5V5M21,21A2,2 0 0,0 23,19H21V21M21,13H23V11H21V13M13,21H15V19H13V21M17,21H19V19H17V21Z"/></svg>'
  364.     },
  365.     volume: {
  366.       high: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z"/></svg>',
  367.       medium: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M5,9V15H9L14,20V4L9,9M18.5,12C18.5,10.23 17.5,8.71 16,7.97V16C17.5,15.29 18.5,13.76 18.5,12Z"/></svg>',
  368.       off: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M12,4L9.91,6.09L12,8.18M4.27,3L3,4.27L7.73,9H3V15H7L12,20V13.27L16.25,17.53C15.58,18.04 14.83,18.46 14,18.7V20.77C15.38,20.45 16.63,19.82 17.68,18.96L19.73,21L21,19.73L12,10.73M19,12C19,12.94 18.8,13.82 18.46,14.64L19.97,16.15C20.62,14.91 21,13.5 21,12C21,7.72 18,4.14 14,3.23V5.29C16.89,6.15 19,8.83 19,12M16.5,12C16.5,10.23 15.5,8.71 14,7.97V10.18L16.45,12.63C16.5,12.43 16.5,12.21 16.5,12Z"/></svg>'
  369.     },
  370.     lucidModeVideo: {
  371.       on: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M19,1L17.74,3.75L15,5L17.74,6.26L19,9L20.25,6.26L23,5L20.25,3.75M9,4L6.5,9.5L1,12L6.5,14.5L9,20L11.5,14.5L17,12L11.5,9.5M19,15L17.74,17.74L15,19L17.74,20.25L19,23L20.25,20.25L23,19L20.25,17.74"/></svg>',
  372.       off: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 4L11.5 9.5L17 12L11.5 14.5L9 20L6.5 14.5L1 12L6.5 9.5L9 4M9 8.83L8 11L5.83 12L8 13L9 15.17L10 13L12.17 12L10 11L9 8.83M19 9L17.74 6.26L15 5L17.74 3.75L19 1L20.25 3.75L23 5L20.25 6.26L19 9M19 23L17.74 20.26L15 19L17.74 17.75L19 15L20.25 17.75L23 19L20.25 20.26L19 23Z"/></svg>',
  373.     },
  374.     sidebar: {
  375.       left: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M20 4H4A2 2 0 0 0 2 6V18A2 2 0 0 0 4 20H20A2 2 0 0 0 22 18V6A2 2 0 0 0 20 4M20 18H9V6H20Z"/></svg>',
  376.       right: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M20 4H4A2 2 0 0 0 2 6V18A2 2 0 0 0 4 20H20A2 2 0 0 0 22 18V6A2 2 0 0 0 20 4M15 18H4V6H15Z"/></svg>',
  377.     },
  378.   };
  379.  
  380.   icons.dataURLs = {
  381.     playlistMusic: 'data:image/svg+xml, ' + icons.playlistMusic,
  382.   };
  383.  
  384.   const buttons = {
  385.     pause: {
  386.       icon: icons.pause,
  387.       disabled: true,
  388.       click(event) {
  389.         event.preventDefault();
  390.         for (const key in tabs) {
  391.           const tab = tabs[key];
  392.           if (!tab.paused) {
  393.             tab.buttonControl.click();
  394.           }
  395.         }
  396.       }
  397.     },
  398.     volume: {
  399.       icon: icons.volume.high,
  400.       disabled: true,
  401.       muted: false,
  402.       click(event) {
  403.         event.preventDefault();
  404.         for (const key in tabs) {
  405.           const tab = tabs[key];
  406.           if (buttons.volume.muted) {
  407.             if (tab.muted || tab.volume === 0) {
  408.               tab.buttonVolume.click();
  409.             }
  410.           } else if (!tab.muted && tab.volume !== 0) {
  411.             tab.buttonVolume.click();
  412.           }
  413.         }
  414.       }
  415.     },
  416.     lucidModeVideo: {
  417.       icon: icons.lucidModeVideo.off,
  418.       disabled: true,
  419.       click(event) {
  420.         event.preventDefault();
  421.         lucidModeVideo = !lucidModeVideo;
  422.         chrome.storage.local.set({
  423.           LUCID_MODE_VIDEO: lucidModeVideo,
  424.         });
  425.       }
  426.     }
  427.   };
  428.  
  429.   const panelContent = gnoh.createElement('div', {
  430.     class: 'global-media-controls-content'
  431.   });
  432.  
  433.   function inject(messageType) {
  434.     if (window.globalMediaControls) {
  435.       return;
  436.     } else {
  437.       window.globalMediaControls = true;
  438.     }
  439.  
  440.     chrome.runtime.onMessage.addListener((info, sender, sendResponse) => {
  441.       if (info.type === messageType) {
  442.         function awaitSendResponse(event) {
  443.           if (
  444.             event?.data
  445.             && event.data.type === messageType + '-internal'
  446.             && event.data.data && event.data.data.action === info.action + '-end'
  447.           ) {
  448.             window.removeEventListener('message', awaitSendResponse);
  449.             if (event.data.data.hasSendResponse) {
  450.               sendResponse();
  451.             }
  452.           }
  453.         }
  454.         window.addEventListener('message', awaitSendResponse);
  455.         window.postMessage({
  456.           type: messageType + '-internal',
  457.           data: info,
  458.         });
  459.         return true;
  460.       }
  461.     });
  462.  
  463.     window.addEventListener('message', (event) => {
  464.       if (event?.data && event.data.type === messageType) {
  465.         chrome.runtime.sendMessage(event.data.data);
  466.       }
  467.     });
  468.   }
  469.  
  470.   function injectMain(messageType, nameAttribute) {
  471.     if (window.globalMediaControlsMain) {
  472.       return;
  473.     } else {
  474.       window.globalMediaControlsMain = true;
  475.     }
  476.     let currentVideo;
  477.  
  478.     const playVideoOriginal = HTMLVideoElement.prototype.play;
  479.     HTMLVideoElement.prototype.play = function () {
  480.       if (!this.globalMediaControls) {
  481.         addEventListeners(this);
  482.       }
  483.       return playVideoOriginal.apply(this, arguments);
  484.     };
  485.  
  486.     const playAudioOriginal = HTMLAudioElement.prototype.play;
  487.     HTMLAudioElement.prototype.play = function () {
  488.       if (!this.globalMediaControls) {
  489.         addEventListeners(this);
  490.       }
  491.       return playAudioOriginal.apply(this, arguments);
  492.     };
  493.  
  494.     const addEventListenerVideoOriginal = HTMLVideoElement.prototype.addEventListener;
  495.     HTMLVideoElement.prototype.addEventListener = function () {
  496.       if (!this.globalMediaControls) {
  497.         addEventListeners(this);
  498.       }
  499.       return addEventListenerVideoOriginal.apply(this, arguments);
  500.     };
  501.  
  502.     const addEventListenerAudioOriginal = HTMLAudioElement.prototype.addEventListener;
  503.     HTMLAudioElement.prototype.addEventListener = function () {
  504.       if (!this.globalMediaControls) {
  505.         addEventListeners(this);
  506.       }
  507.       addEventListenerAudioOriginal.apply(this, arguments);
  508.     };
  509.  
  510.     window.addEventListener('message', (event) => {
  511.       if (
  512.         !event?.data
  513.         || event.data.type !== messageType + '-internal'
  514.         || !event.data.data?.action
  515.         || event.data.data.action.endsWith('-end')
  516.       ) {
  517.         return;
  518.       }
  519.  
  520.       const info = event.data.data;
  521.       let hasSendResponse = false;
  522.  
  523.       switch (info.action) {
  524.         case 'play':
  525.         case 'pause':
  526.           if (currentVideo) {
  527.             currentVideo[info.action]();
  528.           }
  529.           break;
  530.         case 'muted':
  531.           if (currentVideo) {
  532.             if (currentVideo.volume === 0) {
  533.               currentVideo[info.action] = false;
  534.               currentVideo.volume = 1;
  535.             } else {
  536.               currentVideo[info.action] = !currentVideo[info.action];
  537.             }
  538.           }
  539.           break;
  540.         case 'volume':
  541.           if (currentVideo) {
  542.             currentVideo[info.action] = info.volume;
  543.             currentVideo.muted = false;
  544.           }
  545.           break;
  546.         case 'picture-in-picture':
  547.           if (document.pictureInPictureEnabled) {
  548.             if (document.pictureInPictureElement) {
  549.               document.exitPictureInPicture();
  550.             } else if (
  551.               currentVideo
  552.               && !currentVideo.disablePictureInPicture
  553.               && currentVideo.webkitAudioDecodedByteCount
  554.               && currentVideo.webkitVideoDecodedByteCount
  555.             ) {
  556.               currentVideo.requestPictureInPicture();
  557.             }
  558.           }
  559.           break;
  560.         case 'scroll-into-view':
  561.           if (currentVideo) {
  562.             if (info.frameId !== 0) {
  563.               document.documentElement.scrollIntoView({
  564.                 behavior: 'auto',
  565.                 block: 'center',
  566.                 inline: 'center',
  567.               });
  568.             }
  569.             currentVideo.scrollIntoView({
  570.               behavior: 'auto',
  571.               block: 'center',
  572.               inline: 'center',
  573.             });
  574.             if (document.pictureInPictureEnabled && document.pictureInPictureElement) {
  575.               document.exitPictureInPicture();
  576.             }
  577.           }
  578.           break;
  579.         case 'close':
  580.           if (document.pictureInPictureEnabled && document.pictureInPictureElement) {
  581.             document.exitPictureInPicture();
  582.           }
  583.           currentVideo.setAttribute(nameAttribute, '');
  584.           currentVideo.removeEventListener('pause', pauseVideo);
  585.           currentVideo.addEventListener('pause', () => {
  586.             currentVideo.addEventListener('pause', pauseVideo);
  587.             currentVideo = null;
  588.           }, { once: true });
  589.           currentVideo.pause();
  590.           hasSendResponse = true;
  591.           break;
  592.       }
  593.       event.source.postMessage({
  594.         type: messageType + '-internal',
  595.         data: {
  596.           action: info.action + '-end',
  597.           hasSendResponse,
  598.         }
  599.       });
  600.     });
  601.  
  602.     function isPlaying(video) {
  603.       return !video.paused && !video.ended && video.webkitAudioDecodedByteCount && video.getAttribute(nameAttribute);
  604.     }
  605.  
  606.     function getImage(video) {
  607.       let image = null;
  608.       if (video.poster) {
  609.         image = video.poster;
  610.       } else if (navigator.mediaSession.metadata?.artwork?.[0]) {
  611.         image = navigator.mediaSession.metadata.artwork[0].src;
  612.       }
  613.       return image;
  614.     }
  615.  
  616.     function getTitle() {
  617.       let title = null;
  618.       if (navigator.mediaSession.metadata?.title) {
  619.         title = navigator.mediaSession.metadata.title;
  620.       }
  621.       return title;
  622.     }
  623.  
  624.     function getArtist() {
  625.       let artist = null;
  626.       if (navigator.mediaSession.metadata?.artist) {
  627.         artist = navigator.mediaSession.metadata.artist;
  628.       }
  629.       return artist;
  630.     }
  631.  
  632.     function hasVideoPlaying() {
  633.       return Array.from(document.querySelectorAll('video, audio')).find((video) => isPlaying(video));
  634.     }
  635.  
  636.     function getDataControl(video) {
  637.       return {
  638.         type: messageType,
  639.         image: getImage(video),
  640.         title: getTitle(),
  641.         artist: getArtist(),
  642.         paused: video.paused,
  643.         audio: !video.webkitVideoDecodedByteCount || video.disablePictureInPicture,
  644.         pictureInPicture: !!document.pictureInPictureElement,
  645.         volume: video.volume,
  646.         muted: video.muted,
  647.         duration: video.duration,
  648.         currentTime: video.currentTime,
  649.       };
  650.     }
  651.  
  652.     function timeupdateVideo(event) {
  653.       let enable = event.target.getAttribute(nameAttribute);
  654.       if (!event.target.muted) {
  655.         enable = 'on';
  656.         event.target.setAttribute(nameAttribute, enable);
  657.       }
  658.       if (enable) {
  659.         if (event.target.paused && !event.target.webkitAudioDecodedByteCount && !event.target.webkitVideoDecodedByteCount) {
  660.           endedVideo(event);
  661.         } else if (!event.target.paused) {
  662.           currentVideo = event.target;
  663.           window.postMessage({
  664.             type: messageType,
  665.             data: getDataControl(currentVideo),
  666.             eventType: event.type,
  667.           });
  668.         }
  669.       }
  670.     }
  671.  
  672.     function pauseVideo(event) {
  673.       const enable = event.target.getAttribute(nameAttribute);
  674.       if (enable) {
  675.         if (!event.target.webkitAudioDecodedByteCount && !event.target.webkitVideoDecodedByteCount) {
  676.           endedVideo(event);
  677.         } else if (!hasVideoPlaying()) {
  678.           currentVideo = event.target;
  679.           window.postMessage({
  680.             type: messageType,
  681.             data: getDataControl(currentVideo),
  682.             eventType: event.type,
  683.           });
  684.         }
  685.       }
  686.     }
  687.  
  688.     function volumechangeVideo(event) {
  689.       if (currentVideo === event.target) {
  690.         window.postMessage({
  691.           type: messageType,
  692.           data: getDataControl(currentVideo),
  693.           eventType: event.type,
  694.         });
  695.       }
  696.     }
  697.  
  698.     function endedVideo(event) {
  699.       const enable = event.target.getAttribute(nameAttribute);
  700.       if (enable) {
  701.         if (!hasVideoPlaying()) {
  702.           currentVideo = null;
  703.           window.postMessage({
  704.             type: messageType,
  705.             data: {
  706.               type: messageType,
  707.               ended: true,
  708.             },
  709.             eventType: event.type,
  710.           });
  711.         }
  712.       }
  713.     }
  714.  
  715.     function enterpictureinpictureVideo(event) {
  716.       if (currentVideo === event.target) {
  717.         window.postMessage({
  718.           type: messageType,
  719.           data: getDataControl(currentVideo),
  720.           eventType: event.type,
  721.         });
  722.       }
  723.     }
  724.  
  725.     function leavepictureinpictureVideo(event) {
  726.       if (currentVideo === event.target) {
  727.         window.postMessage({
  728.           type: messageType,
  729.           data: getDataControl(currentVideo),
  730.           eventType: event.type,
  731.         });
  732.       }
  733.     }
  734.  
  735.     function addEventListeners(video) {
  736.       if (video.globalMediaControls) {
  737.         return;
  738.       } else {
  739.         video.globalMediaControls = true;
  740.       }
  741.  
  742.       video.setAttribute(nameAttribute, '');
  743.       addEventListenerAudioOriginal.apply(video, ['play', timeupdateVideo]);
  744.       addEventListenerAudioOriginal.apply(video, ['timeupdate', timeupdateVideo]);
  745.       addEventListenerAudioOriginal.apply(video, ['volumechange', volumechangeVideo]);
  746.       addEventListenerAudioOriginal.apply(video, ['playing', timeupdateVideo]);
  747.       addEventListenerAudioOriginal.apply(video, ['pause', pauseVideo]);
  748.       addEventListenerAudioOriginal.apply(video, ['ended', endedVideo]);
  749.       addEventListenerAudioOriginal.apply(video, ['error', endedVideo]);
  750.       addEventListenerAudioOriginal.apply(video, ['enterpictureinpicture', enterpictureinpictureVideo]);
  751.       addEventListenerAudioOriginal.apply(video, ['leavepictureinpicture', leavepictureinpictureVideo]);
  752.     }
  753.  
  754.     function observeDOM(obj, callback) {
  755.       const obs = new MutationObserver((mutations, observer) => {
  756.         if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) {
  757.           callback(mutations, observer);
  758.         }
  759.       });
  760.       obs.observe(obj, {
  761.         childList: true,
  762.         subtree: true,
  763.       });
  764.     }
  765.  
  766.     function injectVideo() {
  767.       const videos = document.querySelectorAll('video:not([global-media-controls]), audio:not([global-media-controls])');
  768.  
  769.       videos.forEach((video) => {
  770.         addEventListeners(video);
  771.       });
  772.     }
  773.  
  774.     injectVideo();
  775.     observeDOM(document, () => injectVideo());
  776.   }
  777.  
  778.   function syncData(keyStorage) {
  779.     chrome.storage.local.get(keyStorage, (result) => {
  780.       for (const key in result[keyStorage]) {
  781.         if (!tabs[key]) {
  782.           createItem(result[keyStorage][key], result[keyStorage][key]);
  783.         }
  784.       }
  785.     });
  786.   }
  787.  
  788.   function resizeCrop(image, width, height, itemInfo, canvas) {
  789.     const crop = width === 0 || height === 0;
  790.     const hasCanvas = !!canvas;
  791.  
  792.     // not resize
  793.     if (image.width <= width && height === 0) {
  794.       width = image.width;
  795.       height = image.height;
  796.     }
  797.     // resize
  798.     if (image.width > width && height === 0) {
  799.       height = image.height * (width / image.width);
  800.     }
  801.  
  802.     // check scale
  803.     const xScale = width / image.width;
  804.     const yScale = height / image.height;
  805.     const scale = crop ? Math.min(xScale, yScale) : Math.max(xScale, yScale);
  806.     // create empty canvas
  807.     canvas = hasCanvas ? canvas : gnoh.createElement('canvas');
  808.     canvas.width = width || Math.round(image.width * scale);
  809.     canvas.height = height || Math.round(image.height * scale);
  810.     const ctx = canvas.getContext('2d');
  811.     ctx.scale(scale, scale);
  812.     // crop it top center
  813.     ctx.drawImage(image, (canvas.width - (image.width * scale)) * 0.5, (canvas.height - (image.height * scale)) * 0.5);
  814.  
  815.     if (!colorLoaded[image.src]) {
  816.       const data = ctx.getImageData(0, 0, width, height).data;
  817.       const blockSize = hasCanvas ? canvas.width : 6;
  818.       const colors = {};
  819.  
  820.       let i = 0;
  821.       const length = data.length;
  822.  
  823.       while (i < length) {
  824.         const rgb = {
  825.           r: data[i],
  826.           g: data[i + 1],
  827.           b: data[i + 2],
  828.           a: data[i + 3],
  829.         };
  830.         i += blockSize * 4;
  831.         if (rgb.a < 255) {
  832.           continue;
  833.         }
  834.  
  835.         let colorKey = Object.keys(colors).find(c => {
  836.           const [r, g, b] = c.split(',');
  837.           return gnoh.color.deltaE(rgb, { r: +r, g: +g, b: +b }) <= 2;
  838.         })
  839.         if (!colorKey) {
  840.           colorKey = [rgb.r, rgb.g, rgb.b].join(',');
  841.           colors[colorKey] = {
  842.             rgb,
  843.             count: 0
  844.           };
  845.         }
  846.         colors[colorKey].count++;
  847.       }
  848.  
  849.       const entryColors = Object.entries(colors).sort((a, b) => b[1].count - a[1].count);
  850.       let filterEntryColors = entryColors.filter((entryColor) => {
  851.         const luminance = gnoh.color.getLuminance(entryColor[1].rgb.r, entryColor[1].rgb.g, entryColor[1].rgb.b);
  852.         return luminance >= 30 && luminance <= 200;
  853.       });
  854.  
  855.       let rgbMax = {
  856.         r: 0,
  857.         g: 0,
  858.         b: 0,
  859.       };
  860.       if (filterEntryColors.length > 0) {
  861.         rgbMax = filterEntryColors[0][1].rgb;
  862.       } else {
  863.         filterEntryColors = entryColors.filter((entryColor) => {
  864.           const [r, g, b] = entryColor[0].split(',');
  865.           return gnoh.color.deltaE({ r: 0, g: 0, b: 0 }, { r: +r, g: +g, b: +b }) > 2
  866.             && gnoh.color.deltaE({ r: 255, g: 255, b: 255 }, { r: +r, g: +g, b: +b }) > 2;
  867.         });
  868.         if (filterEntryColors.length > 0) {
  869.           rgbMax = filterEntryColors[0][1].rgb;
  870.         } else if (entryColors.length > 0) {
  871.           rgbMax = entryColors[0][1].rgb;
  872.         }
  873.       }
  874.  
  875.       const isLightBg = gnoh.color.isLight(rgbMax.r, rgbMax.g, rgbMax.b);
  876.       const rgbProgressBar = gnoh.color.shadeColor(rgbMax.r, rgbMax.g, rgbMax.b, isLightBg ? 0.4 : -0.4);
  877.  
  878.       colorLoaded[image.src] = {
  879.         backgroundColor: gnoh.color.rgbToHex(rgbMax.r, rgbMax.g, rgbMax.b),
  880.         color: isLightBg ? '#f6f6f6' : '#111111',
  881.         progressBarBackgroundColor: gnoh.color.rgbToHex(rgbProgressBar.r, rgbProgressBar.g, rgbProgressBar.b),
  882.       };
  883.     }
  884.  
  885.     itemInfo.isLight = colorLoaded[image.src].isLight;
  886.     itemInfo.backgroundColor = colorLoaded[image.src].backgroundColor;
  887.     itemInfo.color = colorLoaded[image.src].color;
  888.     itemInfo.item.style.setProperty('--colorGMCBg', colorLoaded[image.src].backgroundColor);
  889.     itemInfo.item.style.setProperty('--colorGMCFg', colorLoaded[image.src].color);
  890.     itemInfo.item.style.setProperty('--colorGMCProgressBarBg', colorLoaded[image.src].progressBarBackgroundColor);
  891.  
  892.     return canvas;
  893.   }
  894.  
  895.   function toHHMMSS(seconds) {
  896.     const h = Math.floor(seconds / 3600);
  897.     const m = Math.floor((seconds % 3600) / 60);
  898.     const s = Math.floor(seconds % 60);
  899.     return [
  900.       h,
  901.       m > 9 ? m : (h ? '0' + m : m || '0'),
  902.       s > 9 ? s : '0' + s,
  903.     ].filter(Boolean).join(':');
  904.   }
  905.  
  906.   function createItem(tab, info) {
  907.     const itemInfo = {
  908.       tabId: tab.id || tab.tabId,
  909.       frameId: info.frameId,
  910.       windowId: tab.windowId,
  911.       webPanelId: tab.vivExtData?.panelId?.split('_').slice(0, 2).join('_'),
  912.       setTitle(title) {
  913.         if (title != null && itemInfo.title !== title) {
  914.           itemInfo.title = title;
  915.           itemInfo.titleItem.title = itemInfo.title;
  916.           itemInfo.titleItem.textContent = itemInfo.title;
  917.         }
  918.       },
  919.       setArtist(artist) {
  920.         if (artist != null && itemInfo.artist !== artist) {
  921.           itemInfo.artist = artist;
  922.           itemInfo.domainItem.textContent = itemInfo.artist;
  923.         }
  924.       },
  925.       setUrl(url) {
  926.         if (url == null || url === itemInfo.url) {
  927.           return;
  928.         }
  929.         itemInfo.url = url;
  930.         const urlObject = new URL(url);
  931.         itemInfo.hostname = urlObject.hostname;
  932.         itemInfo.defaultImage = 'chrome://favicon/' + urlObject.origin;
  933.         if (itemInfo.artist == null) {
  934.           itemInfo.domainItem.textContent = itemInfo.hostname;
  935.         }
  936.       },
  937.       setImage(src) {
  938.         if (itemInfo.image !== undefined && (src == null || src === itemInfo.image)) {
  939.           return;
  940.         }
  941.         itemInfo.image = src;
  942.         gnoh.createElement('img', {
  943.           src: itemInfo.image || itemInfo.defaultImage,
  944.           crossOrigin: 'Anonymous',
  945.           events: {
  946.             load(e) {
  947.               if (e.target.src === itemInfo.defaultImage) {
  948.                 itemInfo.hasImage = false;
  949.                 resizeCrop(e.target, 100, 100, itemInfo);
  950.               } else {
  951.                 itemInfo.hasImage = true;
  952.                 itemInfo.imageItem.style.display = '';
  953.                 resizeCrop(e.target, 100, 100, itemInfo, itemInfo.imageItem);
  954.               }
  955.             },
  956.             error(e) {
  957.               if (e.target.src !== itemInfo.defaultImage) {
  958.                 e.target.src = itemInfo.defaultImage;
  959.               }
  960.             }
  961.           }
  962.         });
  963.       },
  964.       setPaused(paused) {
  965.         if (paused != null && itemInfo.paused !== paused) {
  966.           itemInfo.paused = paused;
  967.           itemInfo.buttonControl.innerHTML = itemInfo.paused ? icons.play : icons.pause;
  968.         }
  969.       },
  970.       setPictureInPicture(pictureInPicture) {
  971.         if (pictureInPicture != null && itemInfo.pictureInPicture !== pictureInPicture) {
  972.           itemInfo.pictureInPicture = pictureInPicture;
  973.           itemInfo.buttonPictureInPicture.innerHTML = itemInfo.pictureInPicture ? icons.pictureInPicture.on : icons.pictureInPicture.off;
  974.           if (itemInfo.pictureInPicture) {
  975.             itemInfo.buttonPictureInPicture.classList.add('active');
  976.           } else {
  977.             itemInfo.buttonPictureInPicture.classList.remove('active');
  978.           }
  979.         }
  980.       },
  981.       setActive(active) {
  982.         if (active != null && itemInfo.active !== active) {
  983.           itemInfo.active = active;
  984.           if (itemInfo.webPanelId) {
  985.             itemInfo.buttonTab.innerHTML = icons.sidebar.left;
  986.           } else {
  987.             itemInfo.buttonTab.innerHTML = itemInfo.active ? icons.tab.on : icons.tab.off;
  988.             if (active) {
  989.               itemInfo.buttonTab.classList.add('active');
  990.             } else {
  991.               itemInfo.buttonTab.classList.remove('active');
  992.             }
  993.           }
  994.         }
  995.       },
  996.       setAudio(audio) {
  997.         if (audio != null && itemInfo.audio !== audio) {
  998.           itemInfo.audio = audio;
  999.           if (itemInfo.audio) {
  1000.             itemInfo.buttonPictureInPicture.style.display = 'none';
  1001.           } else {
  1002.             itemInfo.buttonPictureInPicture.style.display = '';
  1003.           }
  1004.         }
  1005.       },
  1006.       setVolume(volume) {
  1007.         if (volume != null && itemInfo.volume !== volume) {
  1008.           itemInfo.volume = volume;
  1009.           itemInfo.muted = false;
  1010.           if (itemInfo.volume === 0) {
  1011.             itemInfo.buttonVolume.innerHTML = icons.volume.off;
  1012.             itemInfo.rangeVolume.value = 0;
  1013.           } else if (itemInfo.volume <= 0.5) {
  1014.             itemInfo.buttonVolume.innerHTML = icons.volume.medium;
  1015.             itemInfo.rangeVolume.value = itemInfo.volume;
  1016.           } else {
  1017.             itemInfo.buttonVolume.innerHTML = icons.volume.high;
  1018.             itemInfo.rangeVolume.value = itemInfo.volume;
  1019.           }
  1020.         }
  1021.       },
  1022.       setMuted(muted) {
  1023.         if (muted != null && itemInfo.muted !== muted) {
  1024.           itemInfo.muted = muted;
  1025.           if (itemInfo.muted) {
  1026.             itemInfo.buttonVolume.innerHTML = icons.volume.off;
  1027.             itemInfo.rangeVolume.value = 0;
  1028.           } else if (itemInfo.volume === 0) {
  1029.             itemInfo.muted = false;
  1030.             itemInfo.buttonVolume.innerHTML = icons.volume.high;
  1031.             itemInfo.rangeVolume.value = itemInfo.volume;
  1032.           } else if (itemInfo.volume <= 0.5) {
  1033.             itemInfo.buttonVolume.innerHTML = icons.volume.medium;
  1034.             itemInfo.rangeVolume.value = itemInfo.volume;
  1035.           } else {
  1036.             itemInfo.buttonVolume.innerHTML = icons.volume.high;
  1037.             itemInfo.rangeVolume.value = itemInfo.volume;
  1038.           }
  1039.         }
  1040.       },
  1041.       setProgress(duration, currentTime) {
  1042.         if (duration != null && itemInfo.duration !== duration || currentTime != null && itemInfo.currentTime !== currentTime) {
  1043.           itemInfo.duration = duration;
  1044.           itemInfo.currentTime = currentTime;
  1045.           itemInfo.durationStr = toHHMMSS(itemInfo.duration);
  1046.           itemInfo.currentTimeStr = toHHMMSS(itemInfo.currentTime);
  1047.           itemInfo.currentTimeDuration.textContent = itemInfo.currentTimeStr + ' / ' + itemInfo.durationStr;
  1048.           itemInfo.item.style.setProperty('--colorGMCProgressBarValue', itemInfo.currentTime / itemInfo.duration * 100 + '%');
  1049.         }
  1050.       },
  1051.     };
  1052.     itemInfo.domainItem = gnoh.createElement('div', {
  1053.       class: 'domain',
  1054.     });
  1055.     itemInfo.setArtist(info.artist);
  1056.     itemInfo.setUrl(tab?.url);
  1057.     itemInfo.imageItem = gnoh.createElement('canvas', {
  1058.       width: 100,
  1059.       height: 100,
  1060.       style: {
  1061.         display: 'none',
  1062.       },
  1063.     });
  1064.     itemInfo.setImage(info.image);
  1065.     itemInfo.buttonClose = gnoh.createElement('button', {
  1066.       type: 'button',
  1067.       class: 'close-button',
  1068.       html: icons.close,
  1069.       tabindex: -1,
  1070.       draggable: true,
  1071.       events: {
  1072.         dragstart(e) {
  1073.           e.preventDefault();
  1074.           e.stopPropagation();
  1075.         },
  1076.         async click(event) {
  1077.           event.preventDefault();
  1078.           chrome.tabs.sendMessage(itemInfo.tabId, {
  1079.             type: messageType,
  1080.             tabId: itemInfo.tabId,
  1081.             frameId: itemInfo.frameId,
  1082.             action: 'close'
  1083.           }, {
  1084.             frameId: itemInfo.frameId,
  1085.           }, () => {
  1086.             deleteItem(itemInfo.tabId);
  1087.           });
  1088.         },
  1089.       },
  1090.     });
  1091.     itemInfo.titleItem = gnoh.createElement('div', {
  1092.       class: 'title',
  1093.     });
  1094.     itemInfo.setTitle(info.title || tab.title);
  1095.     itemInfo.buttonControl = gnoh.createElement('button', {
  1096.       type: 'button',
  1097.       tabindex: -1,
  1098.       draggable: true,
  1099.       events: {
  1100.         dragstart(e) {
  1101.           e.preventDefault();
  1102.           e.stopPropagation();
  1103.         },
  1104.         click(event) {
  1105.           event.preventDefault();
  1106.           const request = {
  1107.             type: messageType,
  1108.             tabId: itemInfo.tabId,
  1109.             frameId: itemInfo.frameId,
  1110.           };
  1111.           if (itemInfo.paused) {
  1112.             request.action = 'play';
  1113.           } else {
  1114.             request.action = 'pause';
  1115.           }
  1116.           itemInfo.setPaused(!itemInfo.paused);
  1117.           chrome.tabs.sendMessage(itemInfo.tabId, request, {
  1118.             frameId: itemInfo.frameId,
  1119.           });
  1120.         },
  1121.       },
  1122.     });
  1123.     itemInfo.setPaused(info.paused);
  1124.     itemInfo.buttonPictureInPicture = gnoh.createElement('button', {
  1125.       type: 'button',
  1126.       tabindex: -1,
  1127.       draggable: true,
  1128.       events: {
  1129.         dragstart(e) {
  1130.           e.preventDefault();
  1131.           e.stopPropagation();
  1132.         },
  1133.         click(event) {
  1134.           event.preventDefault();
  1135.           if (!itemInfo.audio) {
  1136.             chrome.tabs.sendMessage(itemInfo.tabId, {
  1137.               type: messageType,
  1138.               action: 'picture-in-picture',
  1139.               tabId: itemInfo.tabId,
  1140.               frameId: itemInfo.frameId,
  1141.             }, {
  1142.               frameId: itemInfo.frameId,
  1143.             });
  1144.           }
  1145.         },
  1146.       },
  1147.     });
  1148.     itemInfo.setPictureInPicture(info.pictureInPicture);
  1149.     itemInfo.setAudio(info.audio);
  1150.     itemInfo.buttonTab = gnoh.createElement('button', {
  1151.       type: 'button',
  1152.       tabindex: -1,
  1153.       draggable: true,
  1154.       events: {
  1155.         dragstart(e) {
  1156.           e.preventDefault();
  1157.           e.stopPropagation();
  1158.         },
  1159.         click(event) {
  1160.           event.preventDefault();
  1161.           if (!itemInfo.active) {
  1162.             if (itemInfo.webPanelId) {
  1163.               if (itemInfo.windowId === vivaldiWindowId) {
  1164.                 simulateWebviewButtonClick({ webPanelId: itemInfo.webPanelId, openOnly: true });
  1165.               } else {
  1166.                 chrome.windows.update(itemInfo.windowId, { focused: true });
  1167.                 chrome.runtime.sendMessage({
  1168.                   type: messageType,
  1169.                   action: 'open-webpanel',
  1170.                   windowId: itemInfo.windowId,
  1171.                   webPanelId: itemInfo.webPanelId,
  1172.                 });
  1173.               }
  1174.             } else {
  1175.               chrome.tabs.update(itemInfo.tabId, { active: true }, () => {
  1176.                 chrome.windows.update(itemInfo.windowId, { focused: true });
  1177.               });
  1178.             }
  1179.           }
  1180.  
  1181.           if (!itemInfo.audio) {
  1182.             chrome.tabs.sendMessage(itemInfo.tabId, {
  1183.               type: messageType,
  1184.               action: 'scroll-into-view',
  1185.               tabId: itemInfo.tabId,
  1186.               frameId: itemInfo.frameId,
  1187.             }, {
  1188.               frameId: itemInfo.frameId,
  1189.             });
  1190.           }
  1191.           activeItem(itemInfo.tabId);
  1192.         },
  1193.       },
  1194.     });
  1195.     itemInfo.setActive(info.active || false);
  1196.     itemInfo.volumeControl = gnoh.createElement('div', {
  1197.       className: 'volume-control',
  1198.       draggable: true,
  1199.       events: {
  1200.         dragstart(e) {
  1201.           e.preventDefault();
  1202.           e.stopPropagation();
  1203.         },
  1204.       },
  1205.     });
  1206.     itemInfo.buttonVolume = gnoh.createElement('button', {
  1207.       type: 'button',
  1208.       tabindex: -1,
  1209.       events: {
  1210.         click(event) {
  1211.           event.preventDefault();
  1212.           chrome.tabs.sendMessage(itemInfo.tabId, {
  1213.             type: messageType,
  1214.             action: 'muted',
  1215.             tabId: itemInfo.tabId,
  1216.             frameId: itemInfo.frameId,
  1217.           }, {
  1218.             frameId: itemInfo.frameId,
  1219.           });
  1220.           itemInfo.setMuted(!itemInfo.muted);
  1221.         },
  1222.       },
  1223.     }, itemInfo.volumeControl);
  1224.     itemInfo.rangeVolume = gnoh.createElement('input', {
  1225.       type: 'range',
  1226.       tabindex: -1,
  1227.       className: 'range-volume',
  1228.       min: 0,
  1229.       max: 1,
  1230.       step: 0.01,
  1231.       events: {
  1232.         input(event) {
  1233.           event.preventDefault();
  1234.           chrome.tabs.sendMessage(itemInfo.tabId, {
  1235.             type: messageType,
  1236.             action: 'volume',
  1237.             tabId: itemInfo.tabId,
  1238.             frameId: itemInfo.frameId,
  1239.             volume: event.target.value,
  1240.           }, {
  1241.             frameId: itemInfo.frameId,
  1242.           });
  1243.         },
  1244.       },
  1245.     }, itemInfo.volumeControl);
  1246.     itemInfo.setVolume(info.volume);
  1247.     itemInfo.setMuted(info.muted);
  1248.     itemInfo.currentTimeDuration = gnoh.createElement('div', {
  1249.       className: 'current-time-duration',
  1250.       draggable: true,
  1251.       events: {
  1252.         dragstart(e) {
  1253.           e.preventDefault();
  1254.           e.stopPropagation();
  1255.         },
  1256.       },
  1257.     });
  1258.     itemInfo.actionItem = gnoh.createElement('div', {
  1259.       class: 'action',
  1260.     }, null, [itemInfo.buttonControl, itemInfo.buttonPictureInPicture, itemInfo.buttonTab, itemInfo.volumeControl, itemInfo.currentTimeDuration]);
  1261.     itemInfo.contentItem = gnoh.createElement('div', {
  1262.       class: 'content',
  1263.     }, null, [itemInfo.titleItem, itemInfo.domainItem, itemInfo.actionItem]);
  1264.     itemInfo.item = gnoh.createElement('div', {
  1265.       class: 'item',
  1266.       'data-tab-id': itemInfo.tabId,
  1267.       draggable: true,
  1268.       events: {
  1269.         dragstart(e) {
  1270.           this.classList.add('dragstart');
  1271.           e.dataTransfer.effectAllowed = 'move';
  1272.           e.dataTransfer.setData('text/plain', itemInfo.tabId);
  1273.           dragSource = this;
  1274.         },
  1275.         dragover(e) {
  1276.           const target = e.target.closest('.item');
  1277.           if (target === dragSource) {
  1278.             return;
  1279.           }
  1280.  
  1281.           const bounding = target.getBoundingClientRect();
  1282.           const offset = bounding.y + (bounding.height / 2);
  1283.           if (e.clientY - offset > 0) {
  1284.             if (target.nextSibling) {
  1285.               if (target.nextSibling === dragSource) {
  1286.                 return;
  1287.               }
  1288.               target.nextSibling.classList.add('dragover-top');
  1289.             } else {
  1290.               target.classList.add('dragover-bottom');
  1291.             }
  1292.             target.classList.remove('dragover-top');
  1293.           } else {
  1294.             if (target.previousSibling === dragSource) {
  1295.               return;
  1296.             }
  1297.             if (target.nextSibling) {
  1298.               target.nextSibling.classList.remove('dragover-top');
  1299.             } else {
  1300.               target.classList.remove('dragover-bottom');
  1301.             }
  1302.             target.classList.add('dragover-top');
  1303.           }
  1304.  
  1305.           e.preventDefault();
  1306.           e.dataTransfer.dropEffect = 'move';
  1307.         },
  1308.         dragenter(e) {
  1309.           this.classList.add('dragover');
  1310.         },
  1311.         dragleave(e) {
  1312.           this.classList.remove('dragover');
  1313.           this.classList.remove('dragover-top');
  1314.           if (this.nextSibling) {
  1315.             this.nextSibling.classList.remove('dragover-top');
  1316.           } else {
  1317.             this.classList.remove('dragover-bottom');
  1318.           }
  1319.         },
  1320.         drop(e) {
  1321.           e.preventDefault();
  1322.  
  1323.           const target = e.target.closest('.item');
  1324.  
  1325.           if (target.classList.contains('dragover-top')) {
  1326.             target.classList.remove('dragover-top');
  1327.             target.parentNode.insertBefore(dragSource, target);
  1328.           } else {
  1329.             if (target.nextSibling) {
  1330.               target.nextSibling.classList.remove('dragover-top');
  1331.             } else {
  1332.               target.classList.remove('dragover-bottom');
  1333.             }
  1334.             target.parentNode.insertBefore(dragSource, target.nextSibling);
  1335.           }
  1336.         },
  1337.         dragend(e) {
  1338.           this.classList.remove('dragstart');
  1339.  
  1340.           for (const key in tabs) {
  1341.             const tab = tabs[key];
  1342.             tab.item.classList.remove('dragover');
  1343.           }
  1344.         },
  1345.       },
  1346.     }, panelContent, [itemInfo.contentItem, itemInfo.imageItem, itemInfo.buttonClose]);
  1347.     itemInfo.setProgress(info.duration, info.currentTime);
  1348.     tabs[itemInfo.tabId] = itemInfo;
  1349.  
  1350.     const index = Object.keys(tabs).indexOf(itemInfo.tabId + '');
  1351.     gnoh.element.appendAtIndex(itemInfo.item, panelContent, index);
  1352.     return itemInfo;
  1353.   }
  1354.  
  1355.   function deleteItem(tabId) {
  1356.     if (tabs[tabId]) {
  1357.       tabs[tabId].item.remove();
  1358.       delete tabs[tabId];
  1359.     }
  1360.  
  1361.     updateButtonToolbar();
  1362.     updateNumberOfItems();
  1363.   }
  1364.  
  1365.   function replaceItem(addedTabId, removedTabId) {
  1366.     if (tabs[removedTabId]) {
  1367.       tabs[addedTabId] = tabs[removedTabId];
  1368.       delete tabs[removedTabId];
  1369.     }
  1370.   }
  1371.  
  1372.   function activeItem(tabId) {
  1373.     if (!tabs[tabId]?.webPanelId && !tabs[tabId]?.active) {
  1374.       for (const key in tabs) {
  1375.         const tab = tabs[key];
  1376.         tab.setActive(key === tabId + '');
  1377.       }
  1378.     }
  1379.   }
  1380.  
  1381.   async function updateItem(tab, info) {
  1382.     const tabId = tab?.id || tab?.tabId;
  1383.     tab = { ...await chrome.tabs.get(tabId), ...tab };
  1384.     tab.vivExtData = tab.vivExtData ? JSON.parse(tab.vivExtData) : {};
  1385.     if (info.paused !== undefined) {
  1386.       if (!tabs[tabId]) {
  1387.         createItem(tab, info);
  1388.         if (tab.active && tab.windowId === vivaldiWindowId) {
  1389.           activeItem(tab.id);
  1390.         }
  1391.       } else {
  1392.         tabs[tabId].tabId = tabId;
  1393.         tabs[tabId].windowId = tab?.windowId;
  1394.         tabs[tabId].frameId = info.frameId;
  1395.         tabs[tabId].setTitle(info.title || tab.title);
  1396.         tabs[tabId].setArtist(info.artist);
  1397.         tabs[tabId].setUrl(tab?.url);
  1398.         tabs[tabId].setImage(info.image);
  1399.         tabs[tabId].setPaused(info.paused);
  1400.         tabs[tabId].setPictureInPicture(info.pictureInPicture);
  1401.         tabs[tabId].setAudio(info.audio);
  1402.         tabs[tabId].setVolume(info.volume);
  1403.         tabs[tabId].setMuted(info.muted);
  1404.         tabs[tabId].setProgress(info.duration, info.currentTime);
  1405.       }
  1406.  
  1407.       updateButtonToolbar();
  1408.       updateNumberOfItems();
  1409.     } else if (info.ended) {
  1410.       deleteItem(tabId);
  1411.     }
  1412.   }
  1413.  
  1414.   function updateButtonToolbar() {
  1415.     if (lucidModeVideo) {
  1416.       buttons.lucidModeVideo.pressed = true;
  1417.       if (buttons.lucidModeVideo.iconEL) {
  1418.         buttons.lucidModeVideo.iconEL.innerHTML = icons.lucidModeVideo.on;
  1419.       }
  1420.       if (buttons.lucidModeVideo.buttonEl) {
  1421.         buttons.lucidModeVideo.buttonEl.classList.add('button-pressed');
  1422.       }
  1423.     } else {
  1424.       buttons.lucidModeVideo.pressed = false;
  1425.       if (buttons.lucidModeVideo.iconEL) {
  1426.         buttons.lucidModeVideo.iconEL.innerHTML = icons.lucidModeVideo.off;
  1427.       }
  1428.       if (buttons.lucidModeVideo.buttonEl) {
  1429.         buttons.lucidModeVideo.buttonEl.classList.remove('button-pressed');
  1430.       }
  1431.     }
  1432.  
  1433.     if (buttons.lucidModeVideo.disabled) {
  1434.       buttons.lucidModeVideo.disabled = false;
  1435.       if (buttons.lucidModeVideo.buttonEl) {
  1436.         buttons.lucidModeVideo.buttonEl.disabled = false;
  1437.       }
  1438.     }
  1439.  
  1440.     if (Object.keys(tabs).length === 0) {
  1441.       if (buttons.volume.iconEL && buttons.volume.muted) {
  1442.         buttons.volume.iconEL.innerHTML = icons.volume.high;
  1443.       }
  1444.       if (buttons.volume.buttonEl && !buttons.volume.buttonEl.disabled) {
  1445.         buttons.volume.disabled = true;
  1446.         buttons.volume.buttonEl.disabled = true;
  1447.       }
  1448.       buttons.volume.muted = false;
  1449.       buttons.volume.icon = icons.volume.high;
  1450.  
  1451.       if (buttons.pause.buttonEl && !buttons.pause.buttonEl.disabled) {
  1452.         buttons.pause.disabled = true;
  1453.         buttons.pause.buttonEl.disabled = true;
  1454.       }
  1455.     } else {
  1456.       let iconMute = icons.volume.off;
  1457.       let muted = true;
  1458.  
  1459.       let pauseDisabled = true;
  1460.  
  1461.       for (const key in tabs) {
  1462.         const tab = tabs[key];
  1463.         if (!tab.muted && tab.volume !== 0) {
  1464.           iconMute = icons.volume.high;
  1465.           muted = false;
  1466.         }
  1467.  
  1468.         if (!tab.paused) {
  1469.           pauseDisabled = false;
  1470.         }
  1471.       }
  1472.  
  1473.       if (buttons.volume.iconEL && buttons.volume.muted !== muted) {
  1474.         buttons.volume.iconEL.innerHTML = iconMute;
  1475.       }
  1476.       if (buttons.volume.buttonEl?.disabled) {
  1477.         buttons.volume.disabled = false;
  1478.         buttons.volume.buttonEl.disabled = false;
  1479.       }
  1480.       buttons.volume.icon = iconMute;
  1481.       buttons.volume.muted = muted;
  1482.  
  1483.       if (buttons.pause.buttonEl && buttons.pause.buttonEl.disabled !== pauseDisabled) {
  1484.         buttons.pause.disabled = pauseDisabled;
  1485.         buttons.pause.buttonEl.disabled = pauseDisabled;
  1486.       }
  1487.     }
  1488.   }
  1489.  
  1490.   function updateNumberOfItems() {
  1491.     buttonBadges.forEach((buttonBadge) => {
  1492.       if (!buttonBadge) {
  1493.         return;
  1494.       }
  1495.       const numberOfItems = Object.keys(tabs).length;
  1496.  
  1497.       if (numberOfItems > 0) {
  1498.         if (Number(buttonBadge.textContent) !== numberOfItems) {
  1499.           buttonBadge.textContent = numberOfItems;
  1500.         }
  1501.         buttonBadge.style.display = '';
  1502.       } else {
  1503.         buttonBadge.style.display = 'none';
  1504.       }
  1505.     });
  1506.   }
  1507.  
  1508.   function simulateWebviewButtonClick({ webPanelId, webviewButton, openOnly }) {
  1509.     if (webPanelId) {
  1510.       webviewButton = document.querySelector('.toolbar > .button-toolbar > .ToolbarButton-Button[data-name*="' + webPanelId + '"]');
  1511.     }
  1512.  
  1513.     if (openOnly && webviewButton.parentNode?.classList.contains('active')) {
  1514.       return;
  1515.     }
  1516.  
  1517.     const pointerDown = new PointerEvent('pointerdown', {
  1518.       view: window,
  1519.       bubbles: true,
  1520.       cancelable: true,
  1521.       buttons: 0,
  1522.       pointerType: 'mouse',
  1523.     });
  1524.     pointerDown.persist = () => { };
  1525.     gnoh.getReactProps(webviewButton)?.onPointerDown(pointerDown);
  1526.  
  1527.     webviewButton.dispatchEvent(new PointerEvent('pointerup', {
  1528.       view: window,
  1529.       bubbles: true,
  1530.       cancelable: true,
  1531.       buttons: 0,
  1532.       pointerType: 'mouse',
  1533.     }));
  1534.   }
  1535.  
  1536.   async function createPanelCustom(panel, webviewButton) {
  1537.     if (!chrome.extension.inIncognitoContext) {
  1538.       if (panel.dataset.globalMediaControls) {
  1539.         return;
  1540.       }
  1541.       panel.dataset.globalMediaControls = true;
  1542.  
  1543.       let showCloseButton = await vivaldi.prefs.get('vivaldi.panels.show_close_button');
  1544.       let autoClose = await vivaldi.prefs.get('vivaldi.panels.as_overlay.auto_close');
  1545.       let asOverlayEnabled = await vivaldi.prefs.get('vivaldi.panels.as_overlay.enabled');
  1546.  
  1547.       const buttonClose = gnoh.createElement('button', {
  1548.         class: 'close transparent',
  1549.         title: langs.closePanel,
  1550.         style: {
  1551.           display: showCloseButton && asOverlayEnabled && autoClose || !showCloseButton ? 'none' : 'flex',
  1552.         },
  1553.         events: {
  1554.           click() {
  1555.             simulateWebviewButtonClick({ webviewButton });
  1556.           },
  1557.         },
  1558.       });
  1559.  
  1560.       vivaldi.prefs.onChanged.addListener(({ path, value }) => {
  1561.         switch (path) {
  1562.           case 'vivaldi.panels.show_close_button':
  1563.             showCloseButton = value;
  1564.             break;
  1565.           case 'vivaldi.panels.as_overlay.auto_close':
  1566.             autoClose = value;
  1567.             break;
  1568.           case 'vivaldi.panels.as_overlay.enabled':
  1569.             asOverlayEnabled = value;
  1570.             break;
  1571.         }
  1572.         buttonClose.style.display = showCloseButton && asOverlayEnabled && autoClose || !showCloseButton ? 'none' : 'flex';
  1573.       });
  1574.  
  1575.       gnoh.createElement('span', {
  1576.         class: 'VivaldiSvgIcon',
  1577.         style: {
  1578.           '--IconSize': 16,
  1579.         },
  1580.         html: icons.closePanel
  1581.       }, buttonClose);
  1582.       const title = gnoh.createElement('h1', {
  1583.         html: '<span>' + name + '</span>',
  1584.       }, null, buttonClose);
  1585.  
  1586.       const inputSearch = gnoh.createElement('input', {
  1587.         type: 'search',
  1588.         placeholder: langs.search,
  1589.         events: {
  1590.           input(e) {
  1591.             for (const key in tabs) {
  1592.               const tab = tabs[key];
  1593.               const value = e.target.value.trim().toLowerCase().replace(/\s\s+/g, ' ');
  1594.               const title = tab.title.trim().toLowerCase().replace(/\s\s+/g, ' ');
  1595.               const hostname = tab.hostname.trim().toLowerCase().replace(/\s\s+/g, ' ');
  1596.  
  1597.               if (
  1598.                 title.match(value)
  1599.                 || gnoh.string.removeDiacritics(title).match(value)
  1600.                 || hostname.match(value)
  1601.               ) {
  1602.                 tab.item.style.display = '';
  1603.               } else {
  1604.                 tab.item.style.display = 'none';
  1605.               }
  1606.             }
  1607.           },
  1608.         },
  1609.       });
  1610.  
  1611.       const toolbarGroup = gnoh.createElement('div', {
  1612.         class: 'toolbar-group',
  1613.       });
  1614.  
  1615.       for (const key in buttons) {
  1616.         const button = buttons[key];
  1617.         const iconEl = gnoh.createElement('span', {
  1618.           html: button.icon,
  1619.         });
  1620.         const buttonEl = gnoh.createElement('button', {
  1621.           tabindex: '-1',
  1622.           class: ('ToolbarButton-Button' + (button.pressed ? ' button-pressed' : '')),
  1623.           disabled: button.disabled,
  1624.           events: {
  1625.             click: button.click,
  1626.           },
  1627.         }, null, iconEl);
  1628.         const buttonToolbar = gnoh.createElement('div', {
  1629.           class: 'button-toolbar',
  1630.         }, toolbarGroup, buttonEl);
  1631.         button.iconEL = iconEl;
  1632.         button.buttonEl = buttonEl;
  1633.       }
  1634.  
  1635.       const toolbar = gnoh.createElement('div', {
  1636.         class: 'toolbar',
  1637.       }, null, toolbarGroup);
  1638.       const toolbarWrap = gnoh.createElement('div', {
  1639.         class: 'toolbar toolbar-default toolbar-medium toolbar-wrap',
  1640.       }, null, [inputSearch, toolbar]);
  1641.  
  1642.       const panelHeader = gnoh.createElement('header', null, panel, [title, toolbarWrap]);
  1643.       panel.append(panelContent);
  1644.     } else if (webviewButton) {
  1645.       if (panel.dataset.globalMediaControls) {
  1646.         return;
  1647.       }
  1648.       panel.dataset.globalMediaControls = true;
  1649.       if (panel.classList.contains('visible')) {
  1650.         simulateWebviewButtonClick({ webviewButton });
  1651.       }
  1652.     }
  1653.   }
  1654.  
  1655.   const style = !chrome.extension.inIncognitoContext ? [
  1656.     '#panels-container.left #panels .webpanel-stack [data-global-media-controls] header { padding-left: 9px; }',
  1657.     '#panels-container.right #panels .webpanel-stack [data-global-media-controls] header { padding-left: 12px; }',
  1658.     '#panels-container #panels .webpanel-stack [data-global-media-controls] header { padding-right: var(--scrollbarWidth); padding-top: 12px; }',
  1659.     '#panels-container #panels .webpanel-stack [data-global-media-controls] header.webpanel-header { display: none; }',
  1660.     '#panels-container #panels .webpanel-stack [data-global-media-controls] .webpanel-content { display: none; }',
  1661.     '.global-media-controls-content { display: flex; flex-direction: column; overflow: auto; }',
  1662.     '.global-media-controls-content .item { position: relative; display: flex; overflow: hidden; min-height: 100px; background-color: var(--colorGMCBg); color: var(--colorGMCFg); }',
  1663.     '.global-media-controls-content .item:after { position: absolute; content: ""; bottom: 0; height: 4px; width: var(--colorGMCProgressBarValue, 0); z-index: 1; background-color: var(--colorGMCProgressBarBg); }',
  1664.     '.global-media-controls-content .item .content { display: inline-grid; grid-template-rows: auto 1fr auto; flex: 1; padding: 10px; z-index: 1; box-shadow: var(--colorGMCBg) 0px 0px 15px 15px; }',
  1665.     '.global-media-controls-content .item .content .title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
  1666.     '.global-media-controls-content .item .content .action { display: flex; align-items: center; margin-top: auto; position: absolute; bottom: 10px; }',
  1667.     '.global-media-controls-content .item .content .action button { margin-right: 10px; flex: 1 0 auto; }',
  1668.     '.global-media-controls-content .item button { background-color: var(--colorBgLightIntense); color: var(--colorFg); padding: 0; width: 28px; height: 28px; border-radius: 14px; border: 0; }',
  1669.     '.global-media-controls-content .item button:hover { background-color: var(--colorBg); }',
  1670.     '.global-media-controls-content .item button:active { background-color: var(--colorBgDark); }',
  1671.     '.global-media-controls-content .item button.active { background-color: var(--colorHighlightBg); color: var(--colorHighlightFg); }',
  1672.     '.global-media-controls-content .item button.disabled { pointer-events: none; }',
  1673.     '.global-media-controls-content .item button svg { width: 16px; height: 16px; top: 2px; position: relative; }',
  1674.     '.global-media-controls-content .item button.close-button { position: absolute; top: 6px; right: 6px; display: none; z-index: 1; }',
  1675.     '.global-media-controls-content .item .content .action .volume-control { background-color: var(--colorBgLightIntense); color: var(--colorFg); padding: 0; width: 28px; height: 28px; border-radius: 14px; margin-right: 10px; overflow: hidden; padding-right: 10px; flex: 1 0 auto; }',
  1676.     '.global-media-controls-content .item .content .action .volume-control:hover { width: auto; display: flex; flex-direction: row; align-items: center; }',
  1677.     '.global-media-controls-content .item .content .action .volume-control button { margin-right: 0; }',
  1678.     '.global-media-controls-content .item .content .action .volume-control .range-volume { width: 80px; display: none; }',
  1679.     '.global-media-controls-content .item .content .action .volume-control:hover .range-volume { display: block; }',
  1680.     '.global-media-controls-content .item .content .action .current-time-duration { background-color: var(--colorBgLightIntense); color: var(--colorFg); padding: 0; height: 28px; line-height: 28px; border-radius: 14px; margin-right: 10px; overflow: hidden; padding: 0 10px; flex: 1 0 auto; }',
  1681.     '.global-media-controls-content .item:hover button.close-button { display: block; }',
  1682.     '.global-media-controls-content .item.dragstart { opacity: 0.4; }',
  1683.     '.global-media-controls-content .item.dragover-top::before { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; box-shadow: 0 2px var(--colorHighlightBg) inset, 0 -2px var(--colorHighlightBg); pointer-events: none; z-index: 2; }',
  1684.     '.global-media-controls-content .item.dragover-bottom::before { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; box-shadow: 0 -2px var(--colorHighlightBg) inset, 0 2px var(--colorHighlightBg); pointer-events: none; z-index: 2; }',
  1685.     'button[data-name="' + webPanelId + '"] > img { display:none; }',
  1686.     'button[data-name="' + webPanelId + '"]:before { width: 16px; height: 16px; content: ""; background-color: var(--colorFg); -webkit-mask-box-image: url(' + JSON.stringify(icons.dataURLs.playlistMusic) + '); }',
  1687.     '.color-behind-tabs-off .toolbar-mainbar button[data-name="' + webPanelId + '"]:before { background-color: var(--colorAccentFg); }',
  1688.     '.button-toolbar:active button[data-name="' + webPanelId + '"]:before { transform: scale(0.9); }',
  1689.   ] : [
  1690.     '.button-toolbar:has(button[data-name="' + webPanelId + '"]) { display:none !important; }',
  1691.     '.draggable-button:has(button[data-name="' + webPanelId + '"]) { display:none !important; }',
  1692.   ];
  1693.  
  1694.   gnoh.addStyle(style, nameAttribute);
  1695.  
  1696.   function updateIconAndTitle() {
  1697.     const webviewButtons = Array.from(document.querySelectorAll('.toolbar > .button-toolbar > .ToolbarButton-Button[data-name*="' + webPanelId + '"]'));
  1698.  
  1699.     const webPanelStack = gnoh.getReactProps('.panel-group .webpanel-stack')?.children?.filter(webPanel => webPanel) ?? [];
  1700.     const webPanelIndex = webPanelStack.findIndex(webPanel => webPanel.key === webPanelId) + 1;
  1701.     const panel = document.querySelector('.panel-group .webpanel-stack .panel.webpanel:nth-child(' + webPanelIndex + ')');
  1702.  
  1703.     if (panel && webviewButtons.length) {
  1704.       createPanelCustom(panel, webviewButtons[0]);
  1705.     }
  1706.  
  1707.     webviewButtons.forEach((wvb) => {
  1708.       if (!chrome.extension.inIncognitoContext) {
  1709.         if (wvb.dataset.globalMediaControls) {
  1710.           return;
  1711.         }
  1712.  
  1713.         wvb.dataset.globalMediaControls = true;
  1714.  
  1715.         const buttonBadge = gnoh.createElement('span', {
  1716.           class: 'button-badge',
  1717.           style: {
  1718.             display: 'none',
  1719.           },
  1720.         });
  1721.  
  1722.         buttonBadges.push(buttonBadge);
  1723.  
  1724.         wvb.append(buttonBadge);
  1725.       }
  1726.     });
  1727.   }
  1728.  
  1729.   function createWebPanel() {
  1730.     vivaldi.prefs.get('vivaldi.panels.web.elements', (elements) => {
  1731.       let element = elements.find((e) => e.id === webPanelId);
  1732.       if (!element) {
  1733.         element = {
  1734.           activeUrl: code,
  1735.           faviconUrl: icons.dataURLs.playlistMusic,
  1736.           faviconUrlValid: true,
  1737.           id: webPanelId,
  1738.           mobileMode: true,
  1739.           origin: 'user',
  1740.           resizable: false,
  1741.           title: name,
  1742.           url: 'chrome://' + nameAttribute,
  1743.           width: -1,
  1744.           zoom: 1,
  1745.         };
  1746.         elements.unshift(element);
  1747.  
  1748.         vivaldi.prefs.set({
  1749.           path: 'vivaldi.panels.web.elements',
  1750.           value: elements,
  1751.         });
  1752.       }
  1753.  
  1754.       Promise.all(
  1755.         [
  1756.           'vivaldi.toolbars.panel',
  1757.           'vivaldi.toolbars.navigation',
  1758.           'vivaldi.toolbars.status',
  1759.           'vivaldi.toolbars.mail',
  1760.           'vivaldi.toolbars.mail_message',
  1761.           'vivaldi.toolbars.mail_composer',
  1762.         ].map((path) => vivaldi.prefs.get(path))
  1763.       ).then((toolbars) => {
  1764.         const hasGlobalMediaControl = toolbars.some((toolbar) => toolbar.some((p) => p === webPanelId));
  1765.  
  1766.         if (!hasGlobalMediaControl) {
  1767.           const panels = toolbars[0];
  1768.  
  1769.           const panelIndex = panels.findIndex(panel => panel.startsWith('WEBPANEL_'));
  1770.           panels.splice(panelIndex, 0, webPanelId);
  1771.  
  1772.           vivaldi.prefs.set({
  1773.             path: 'vivaldi.toolbars.panel',
  1774.             value: panels,
  1775.           });
  1776.         }
  1777.       });
  1778.     });
  1779.   }
  1780.  
  1781.   vivaldi.windowPrivate.onActivated.addListener((windowId, active) => {
  1782.     if (active) {
  1783.       chrome.tabs.query({ active: true, windowId: windowId }, (tabs) => {
  1784.         const tab = tabs[0];
  1785.         if (tab) {
  1786.           activeItem(tab.id);
  1787.         }
  1788.       });
  1789.     }
  1790.   });
  1791.  
  1792.   if (!chrome.extension.inIncognitoContext) {
  1793.     chrome.tabs.onActivated.addListener((activeInfo) => {
  1794.       activeItem(activeInfo.tabId);
  1795.     });
  1796.  
  1797.     chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  1798.       if (changeInfo.status === 'loading') {
  1799.         deleteItem(tabId);
  1800.       }
  1801.     });
  1802.  
  1803.     chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => {
  1804.       replaceItem(addedTabId, removedTabId);
  1805.     });
  1806.  
  1807.     chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
  1808.       deleteItem(tabId);
  1809.     });
  1810.  
  1811.     chrome.windows.getAll({ windowTypes: ['normal'] }, (windows) => {
  1812.       const windowNotIncognitos = windows.filter((w) => !w.incognito);
  1813.       if (windowNotIncognitos.length < 2) {
  1814.         chrome.storage.local.remove('GLOBAL_MEDIA_CONTROLS');
  1815.       } else {
  1816.         syncData('GLOBAL_MEDIA_CONTROLS');
  1817.       }
  1818.     });
  1819.  
  1820.     chrome.runtime.onMessage.addListener(async (info, sender, sendResponse) => {
  1821.       if (info.type === messageType && (!sender.tab || (sender.tab && !sender.tab.incognito))) {
  1822.         switch (info.action) {
  1823.           case 'open-webpanel':
  1824.             if (info.windowId === vivaldiWindowId) {
  1825.               simulateWebviewButtonClick({ webPanelId: info.webPanelId, openOnly: true });
  1826.             }
  1827.             break;
  1828.           default:
  1829.             info.frameId = sender.frameId;
  1830.             await updateItem(sender.tab, info);
  1831.             chrome.storage.local.set({
  1832.               GLOBAL_MEDIA_CONTROLS: tabs,
  1833.             });
  1834.             break;
  1835.         }
  1836.       }
  1837.     });
  1838.  
  1839.     gnoh.timeOut(() => {
  1840.       chrome.tabs.query({ windowId: window.vivaldiWindowId, windowType: 'normal' }, (tabs) => {
  1841.         tabs.forEach((tab) => {
  1842.           if (!tab.incognito) {
  1843.             chrome.scripting.executeScript({
  1844.               target: {
  1845.                 tabId: tab.id,
  1846.                 allFrames: true,
  1847.               },
  1848.               func: injectMain,
  1849.               world: 'MAIN',
  1850.               args: [messageType, nameAttribute],
  1851.             });
  1852.             chrome.scripting.executeScript({
  1853.               target: {
  1854.                 tabId: tab.id,
  1855.                 allFrames: true,
  1856.               },
  1857.               func: inject,
  1858.               args: [messageType],
  1859.             });
  1860.           }
  1861.         });
  1862.       });
  1863.  
  1864.       chrome.webNavigation.onCommitted.addListener((details) => {
  1865.         chrome.scripting.executeScript({
  1866.           target: {
  1867.             tabId: details.tabId,
  1868.             frameIds: [details.frameId],
  1869.           },
  1870.           func: injectMain,
  1871.           world: 'MAIN',
  1872.           args: [messageType, nameAttribute],
  1873.         });
  1874.         chrome.scripting.executeScript({
  1875.           target: {
  1876.             tabId: details.tabId,
  1877.             frameIds: [details.frameId],
  1878.           },
  1879.           func: inject,
  1880.           args: [messageType],
  1881.         });
  1882.       });
  1883.     }, () => window.vivaldiWindowId != null);
  1884.  
  1885.     function injectToggleLucidModeVideo(enable) {
  1886.       let style = document.querySelector('style[lucid-mode-video]');
  1887.       if (style && !enable) {
  1888.         style.remove();
  1889.       } else if (!style && enable) {
  1890.         style = document.createElement('style');
  1891.         style.setAttribute('lucid-mode-video', '');
  1892.         style.innerHTML = 'video { filter: url(\'data:image/svg+xml, <svg xmlns="http://www.w3.org/2000/svg"> <filter id="sharpen"> <feConvolveMatrix order="3" preserveAlpha="true" kernelMatrix="1 -1 1 -1 -1 -1 1 -1 1"/> </filter> </svg>#sharpen\'); }';
  1893.         document.head.append(style);
  1894.       }
  1895.     }
  1896.  
  1897.     function toggleLucidModeVideo() {
  1898.       updateButtonToolbar();
  1899.  
  1900.       chrome.tabs.query({ windowType: 'normal' }, (tabs) => {
  1901.         tabs.forEach((tab) => {
  1902.           chrome.scripting.executeScript({
  1903.             target: {
  1904.               tabId: tab.id,
  1905.               allFrames: true,
  1906.             },
  1907.             func: injectToggleLucidModeVideo,
  1908.             args: [lucidModeVideo],
  1909.           });
  1910.         });
  1911.       });
  1912.     }
  1913.  
  1914.     chrome.storage.local.get({
  1915.       LUCID_MODE_VIDEO: false
  1916.     }, (result) => {
  1917.       lucidModeVideo = result.LUCID_MODE_VIDEO;
  1918.       toggleLucidModeVideo();
  1919.  
  1920.       chrome.webNavigation.onCommitted.addListener((details) => {
  1921.         chrome.scripting.executeScript({
  1922.           target: {
  1923.             tabId: details.tabId,
  1924.             frameIds: [details.frameId],
  1925.           },
  1926.           func: injectToggleLucidModeVideo,
  1927.           args: [lucidModeVideo],
  1928.         });
  1929.       });
  1930.     });
  1931.  
  1932.     chrome.storage.local.onChanged.addListener((changes, namespace) => {
  1933.       if (changes.LUCID_MODE_VIDEO) {
  1934.         lucidModeVideo = changes.LUCID_MODE_VIDEO.newValue;
  1935.  
  1936.         toggleLucidModeVideo();
  1937.       }
  1938.     });
  1939.   }
  1940.  
  1941.   gnoh.timeOut(() => {
  1942.     const webviewButtons = Array.from(document.querySelectorAll('.toolbar > .button-toolbar > .ToolbarButton-Button[data-name*="' + webPanelId + '"]'));
  1943.     if (webviewButtons.length) {
  1944.       updateIconAndTitle();
  1945.     } else {
  1946.       createWebPanel();
  1947.     }
  1948.   }, '#browser');
  1949.  
  1950.   gnoh.observeDOM(document, () => {
  1951.     updateIconAndTitle();
  1952.   });
  1953. })();
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
Add Comment
Please, Sign In to add comment