/* * Global Media Controls Panel * Written by Tam710562 * Forum link: https://forum.vivaldi.net/topic/66803/global-media-controls-panel */ (() => { 'use strict'; const gnoh = { i18n: { getMessageName(message, type) { message = (type ? type + '\x04' : '') + message; return message.replace(/[^a-z0-9]/g, (i) => '_' + i.codePointAt(0) + '_') + '0'; }, getMessage(message, type) { return chrome.i18n.getMessage(this.getMessageName(message, type)) || message; }, }, createElement(tagName, attribute, parent, inner, options) { if (typeof tagName === 'undefined') { return; } if (typeof options === 'undefined') { options = {}; } if (typeof options.isPrepend === 'undefined') { options.isPrepend = false; } const el = document.createElement(tagName); if (!!attribute && typeof attribute === 'object') { for (const key in attribute) { if (key === 'text') { el.textContent = attribute[key]; } else if (key === 'html') { el.innerHTML = attribute[key]; } else if (key === 'style' && typeof attribute[key] === 'object') { for (const css in attribute.style) { el.style.setProperty(css, attribute.style[css]); } } else if (key === 'events' && typeof attribute[key] === 'object') { for (const event in attribute.events) { if (typeof attribute.events[event] === 'function') { el.addEventListener(event, attribute.events[event]); } } } else if (typeof el[key] !== 'undefined') { el[key] = attribute[key]; } else { if (typeof attribute[key] === 'object') { attribute[key] = JSON.stringify(attribute[key]); } el.setAttribute(key, attribute[key]); } } } if (inner) { if (!Array.isArray(inner)) { inner = [inner]; } for (const element of inner) { if (element.nodeName) { el.append(element); } else { el.append(this.createElementFromHTML(element)); } } } if (typeof parent === 'string') { parent = document.querySelector(parent); } if (parent) { if (options.isPrepend) { parent.prepend(el); } else { parent.append(el); } } return el; }, createElementFromHTML(html) { return this.createElement('template', { html: (html || '').trim(), }).content; }, color: { rgbToHex(r, g, b) { return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); }, rgb2lab(rgb) { let r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255, x, y, z; r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047; y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000; z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883; x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116; y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116; z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116; return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)] }, deltaE(rgbA, rgbB) { const labA = this.rgb2lab(rgbA); const labB = this.rgb2lab(rgbB); const deltaL = labA[0] - labB[0]; const deltaA = labA[1] - labB[1]; const deltaB = labA[2] - labB[2]; const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]); const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]); const deltaC = c1 - c2; let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC; deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH); const sc = 1.0 + 0.045 * c1; const sh = 1.0 + 0.015 * c1; const deltaLKlsl = deltaL / (1.0); const deltaCkcsc = deltaC / (sc); const deltaHkhsh = deltaH / (sh); const i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh; return i < 0 ? 0 : Math.sqrt(i); }, getLuminance(r, g, b) { return 0.2126 * r + 0.7152 * g + 0.0722 * b; }, isLight(r, g, b) { return this.getLuminance(r, g, b) < 156; }, shadeColor(r, g, b, percent) { const t = percent < 0 ? 0 : 255 * percent; const p = percent < 0 ? 1 + percent : 1 - percent; return { r: Math.round(parseInt(r) * p + t), g: Math.round(parseInt(g) * p + t), b: Math.round(parseInt(b) * p + t), }; }, }, element: { appendAtIndex(element, parentElement, index) { if (index >= parentElement.children.length) { parentElement.append(element) } else { parentElement.insertBefore(element, parentElement.children[index]) } }, }, getReactProps(element) { if (typeof element === 'string') { element = document.querySelector(element); } if (!element || element.ownerDocument !== document) { return; } if (!this.reactPropsKey) { this.reactPropsKey = Object.keys(element).find((key) => key.startsWith('__reactProps')); } return element[this.reactPropsKey]; }, string: { removeDiacritics(str) { if (!this._diacriticsMap) { const defaultDiacriticsRemovalMap = [ { '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' }, { 'base': 'AA', 'letters': '\uA732' }, { 'base': 'AE', 'letters': '\u00C6\u01FC\u01E2' }, { 'base': 'AO', 'letters': '\uA734' }, { 'base': 'AU', 'letters': '\uA736' }, { 'base': 'AV', 'letters': '\uA738\uA73A' }, { 'base': 'AY', 'letters': '\uA73C' }, { 'base': 'B', 'letters': '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181' }, { 'base': 'C', 'letters': '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E' }, { 'base': 'D', 'letters': '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0' }, { 'base': 'DZ', 'letters': '\u01F1\u01C4' }, { 'base': 'Dz', 'letters': '\u01F2\u01C5' }, { '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' }, { 'base': 'F', 'letters': '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' }, { 'base': 'G', 'letters': '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E' }, { 'base': 'H', 'letters': '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D' }, { 'base': 'I', 'letters': '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197' }, { 'base': 'J', 'letters': '\u004A\u24BF\uFF2A\u0134\u0248' }, { 'base': 'K', 'letters': '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2' }, { 'base': 'L', 'letters': '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780' }, { 'base': 'LJ', 'letters': '\u01C7' }, { 'base': 'Lj', 'letters': '\u01C8' }, { 'base': 'M', 'letters': '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C' }, { 'base': 'N', 'letters': '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4' }, { 'base': 'NJ', 'letters': '\u01CA' }, { 'base': 'Nj', 'letters': '\u01CB' }, { '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' }, { 'base': 'OI', 'letters': '\u01A2' }, { 'base': 'OO', 'letters': '\uA74E' }, { 'base': 'OU', 'letters': '\u0222' }, { 'base': 'OE', 'letters': '\u008C\u0152' }, { 'base': 'oe', 'letters': '\u009C\u0153' }, { 'base': 'P', 'letters': '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754' }, { 'base': 'Q', 'letters': '\u0051\u24C6\uFF31\uA756\uA758\u024A' }, { 'base': 'R', 'letters': '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782' }, { 'base': 'S', 'letters': '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784' }, { 'base': 'T', 'letters': '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786' }, { 'base': 'TZ', 'letters': '\uA728' }, { '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' }, { 'base': 'V', 'letters': '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245' }, { 'base': 'VY', 'letters': '\uA760' }, { 'base': 'W', 'letters': '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72' }, { 'base': 'X', 'letters': '\u0058\u24CD\uFF38\u1E8A\u1E8C' }, { 'base': 'Y', 'letters': '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE' }, { 'base': 'Z', 'letters': '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762' }, { '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' }, { 'base': 'aa', 'letters': '\uA733' }, { 'base': 'ae', 'letters': '\u00E6\u01FD\u01E3' }, { 'base': 'ao', 'letters': '\uA735' }, { 'base': 'au', 'letters': '\uA737' }, { 'base': 'av', 'letters': '\uA739\uA73B' }, { 'base': 'ay', 'letters': '\uA73D' }, { 'base': 'b', 'letters': '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253' }, { 'base': 'c', 'letters': '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184' }, { 'base': 'd', 'letters': '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A' }, { 'base': 'dz', 'letters': '\u01F3\u01C6' }, { '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' }, { 'base': 'f', 'letters': '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C' }, { 'base': 'g', 'letters': '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F' }, { 'base': 'h', 'letters': '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265' }, { 'base': 'hv', 'letters': '\u0195' }, { 'base': 'i', 'letters': '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131' }, { 'base': 'j', 'letters': '\u006A\u24D9\uFF4A\u0135\u01F0\u0249' }, { 'base': 'k', 'letters': '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3' }, { 'base': 'l', 'letters': '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747' }, { 'base': 'lj', 'letters': '\u01C9' }, { 'base': 'm', 'letters': '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F' }, { 'base': 'n', 'letters': '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5' }, { 'base': 'nj', 'letters': '\u01CC' }, { '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' }, { 'base': 'oi', 'letters': '\u01A3' }, { 'base': 'ou', 'letters': '\u0223' }, { 'base': 'oo', 'letters': '\uA74F' }, { 'base': 'p', 'letters': '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755' }, { 'base': 'q', 'letters': '\u0071\u24E0\uFF51\u024B\uA757\uA759' }, { 'base': 'r', 'letters': '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783' }, { 'base': 's', 'letters': '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B' }, { 'base': 't', 'letters': '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787' }, { 'base': 'tz', 'letters': '\uA729' }, { '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' }, { 'base': 'v', 'letters': '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C' }, { 'base': 'vy', 'letters': '\uA761' }, { 'base': 'w', 'letters': '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73' }, { 'base': 'x', 'letters': '\u0078\u24E7\uFF58\u1E8B\u1E8D' }, { 'base': 'y', 'letters': '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF' }, { 'base': 'z', 'letters': '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763' } ]; this._diacriticsMap = {}; for (const diacritic of defaultDiacriticsRemovalMap) { for (const letter of diacritic.letters) { this._diacriticsMap[letter] = diacritic.base; } } } return str.replace(/[^\u0000-\u007E]/g, (a) => { return this._diacriticsMap[a] || a; }); }, }, addStyle(css, id, isNotMin) { this.styles = this.styles || {}; if (Array.isArray(css)) { css = css.join(isNotMin === true ? '\n' : ''); } id = id || this.uuid.generate(Object.keys(this.styles)); this.styles[id] = this.createElement('style', { html: css || '', 'data-id': id, }, document.head); return this.styles[id]; }, timeOut(callback, condition, timeOut = 300) { let timeOutId = setTimeout(function wait() { let result; if (!condition) { result = document.getElementById('browser'); } else if (typeof condition === 'string') { result = document.querySelector(condition); } else if (typeof condition === 'function') { result = condition(); } else { return; } if (result) { callback(result); } else { timeOutId = setTimeout(wait, timeOut); } }, timeOut); function stop() { if (timeOutId) { clearTimeout(timeOutId); } } return { stop, }; }, observeDOM(obj, callback, config) { const obs = new MutationObserver((mutations, observer) => { if (config || (mutations[0].addedNodes.length || mutations[0].removedNodes.length)) { callback(mutations, observer); } }); obs.observe(obj, config || { childList: true, subtree: true, }); }, uuid: { generate(ids) { let d = Date.now() + performance.now(); let r; const id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); if (Array.isArray(ids) && ids.includes(id)) { return this.generate(ids); } return id; }, }, }; const tabs = {}; const name = 'Global Media Controls'; const messageType = 'global-media-controls'; const nameAttribute = 'global-media-controls'; const code = 'data:text/html,' + encodeURIComponent('' + name + ''); const webPanelId = 'WEBPANEL_c650d566-8020-4841-8a5d-1555b86da114'; const colorLoaded = {}; const buttonBadges = []; let dragSource = null; let lucidModeVideo = false; const langs = { search: gnoh.i18n.getMessage('Search', 'verb'), closePanel: gnoh.i18n.getMessage('Close Panel'), }; const icons = { playlistMusic: '', play: '', pause: '', close: '', closePanel: '', pictureInPicture: { off: '', on: '' }, tab: { on: '', off: '' }, volume: { high: '', medium: '', off: '' }, lucidModeVideo: { on: '', off: '', }, sidebar: { left: '', right: '', }, }; icons.dataURLs = { playlistMusic: 'data:image/svg+xml, ' + icons.playlistMusic, }; const buttons = { pause: { icon: icons.pause, disabled: true, click(event) { event.preventDefault(); for (const key in tabs) { const tab = tabs[key]; if (!tab.paused) { tab.buttonControl.click(); } } } }, volume: { icon: icons.volume.high, disabled: true, muted: false, click(event) { event.preventDefault(); for (const key in tabs) { const tab = tabs[key]; if (buttons.volume.muted) { if (tab.muted || tab.volume === 0) { tab.buttonVolume.click(); } } else if (!tab.muted && tab.volume !== 0) { tab.buttonVolume.click(); } } } }, lucidModeVideo: { icon: icons.lucidModeVideo.off, disabled: true, click(event) { event.preventDefault(); lucidModeVideo = !lucidModeVideo; chrome.storage.local.set({ LUCID_MODE_VIDEO: lucidModeVideo, }); } } }; const panelContent = gnoh.createElement('div', { class: 'global-media-controls-content' }); function inject(messageType) { if (window.globalMediaControls) { return; } else { window.globalMediaControls = true; } chrome.runtime.onMessage.addListener((info, sender, sendResponse) => { if (info.type === messageType) { function awaitSendResponse(event) { if ( event?.data && event.data.type === messageType + '-internal' && event.data.data && event.data.data.action === info.action + '-end' ) { window.removeEventListener('message', awaitSendResponse); if (event.data.data.hasSendResponse) { sendResponse(); } } } window.addEventListener('message', awaitSendResponse); window.postMessage({ type: messageType + '-internal', data: info, }); return true; } }); window.addEventListener('message', (event) => { if (event?.data && event.data.type === messageType) { chrome.runtime.sendMessage(event.data.data); } }); } function injectMain(messageType, nameAttribute) { if (window.globalMediaControlsMain) { return; } else { window.globalMediaControlsMain = true; } let currentVideo; const playVideoOriginal = HTMLVideoElement.prototype.play; HTMLVideoElement.prototype.play = function () { if (!this.globalMediaControls) { addEventListeners(this); } return playVideoOriginal.apply(this, arguments); }; const playAudioOriginal = HTMLAudioElement.prototype.play; HTMLAudioElement.prototype.play = function () { if (!this.globalMediaControls) { addEventListeners(this); } return playAudioOriginal.apply(this, arguments); }; const addEventListenerVideoOriginal = HTMLVideoElement.prototype.addEventListener; HTMLVideoElement.prototype.addEventListener = function () { if (!this.globalMediaControls) { addEventListeners(this); } return addEventListenerVideoOriginal.apply(this, arguments); }; const addEventListenerAudioOriginal = HTMLAudioElement.prototype.addEventListener; HTMLAudioElement.prototype.addEventListener = function () { if (!this.globalMediaControls) { addEventListeners(this); } addEventListenerAudioOriginal.apply(this, arguments); }; window.addEventListener('message', (event) => { if ( !event?.data || event.data.type !== messageType + '-internal' || !event.data.data?.action || event.data.data.action.endsWith('-end') ) { return; } const info = event.data.data; let hasSendResponse = false; switch (info.action) { case 'play': case 'pause': if (currentVideo) { currentVideo[info.action](); } break; case 'muted': if (currentVideo) { if (currentVideo.volume === 0) { currentVideo[info.action] = false; currentVideo.volume = 1; } else { currentVideo[info.action] = !currentVideo[info.action]; } } break; case 'volume': if (currentVideo) { currentVideo[info.action] = info.volume; currentVideo.muted = false; } break; case 'picture-in-picture': if (document.pictureInPictureEnabled) { if (document.pictureInPictureElement) { document.exitPictureInPicture(); } else if ( currentVideo && !currentVideo.disablePictureInPicture && currentVideo.webkitAudioDecodedByteCount && currentVideo.webkitVideoDecodedByteCount ) { currentVideo.requestPictureInPicture(); } } break; case 'scroll-into-view': if (currentVideo) { if (info.frameId !== 0) { document.documentElement.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center', }); } currentVideo.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center', }); if (document.pictureInPictureEnabled && document.pictureInPictureElement) { document.exitPictureInPicture(); } } break; case 'close': if (document.pictureInPictureEnabled && document.pictureInPictureElement) { document.exitPictureInPicture(); } currentVideo.setAttribute(nameAttribute, ''); currentVideo.removeEventListener('pause', pauseVideo); currentVideo.addEventListener('pause', () => { currentVideo.addEventListener('pause', pauseVideo); currentVideo = null; }, { once: true }); currentVideo.pause(); hasSendResponse = true; break; } event.source.postMessage({ type: messageType + '-internal', data: { action: info.action + '-end', hasSendResponse, } }); }); function isPlaying(video) { return !video.paused && !video.ended && video.webkitAudioDecodedByteCount && video.getAttribute(nameAttribute); } function getImage(video) { let image = null; if (video.poster) { image = video.poster; } else if (navigator.mediaSession.metadata?.artwork?.[0]) { image = navigator.mediaSession.metadata.artwork[0].src; } return image; } function getTitle() { let title = null; if (navigator.mediaSession.metadata?.title) { title = navigator.mediaSession.metadata.title; } return title; } function getArtist() { let artist = null; if (navigator.mediaSession.metadata?.artist) { artist = navigator.mediaSession.metadata.artist; } return artist; } function hasVideoPlaying() { return Array.from(document.querySelectorAll('video, audio')).find((video) => isPlaying(video)); } function getDataControl(video) { return { type: messageType, image: getImage(video), title: getTitle(), artist: getArtist(), paused: video.paused, audio: !video.webkitVideoDecodedByteCount || video.disablePictureInPicture, pictureInPicture: !!document.pictureInPictureElement, volume: video.volume, muted: video.muted, duration: video.duration, currentTime: video.currentTime, }; } function timeupdateVideo(event) { let enable = event.target.getAttribute(nameAttribute); if (!event.target.muted) { enable = 'on'; event.target.setAttribute(nameAttribute, enable); } if (enable) { if (event.target.paused && !event.target.webkitAudioDecodedByteCount && !event.target.webkitVideoDecodedByteCount) { endedVideo(event); } else if (!event.target.paused) { currentVideo = event.target; window.postMessage({ type: messageType, data: getDataControl(currentVideo), eventType: event.type, }); } } } function pauseVideo(event) { const enable = event.target.getAttribute(nameAttribute); if (enable) { if (!event.target.webkitAudioDecodedByteCount && !event.target.webkitVideoDecodedByteCount) { endedVideo(event); } else if (!hasVideoPlaying()) { currentVideo = event.target; window.postMessage({ type: messageType, data: getDataControl(currentVideo), eventType: event.type, }); } } } function volumechangeVideo(event) { if (currentVideo === event.target) { window.postMessage({ type: messageType, data: getDataControl(currentVideo), eventType: event.type, }); } } function endedVideo(event) { const enable = event.target.getAttribute(nameAttribute); if (enable) { if (!hasVideoPlaying()) { currentVideo = null; window.postMessage({ type: messageType, data: { type: messageType, ended: true, }, eventType: event.type, }); } } } function enterpictureinpictureVideo(event) { if (currentVideo === event.target) { window.postMessage({ type: messageType, data: getDataControl(currentVideo), eventType: event.type, }); } } function leavepictureinpictureVideo(event) { if (currentVideo === event.target) { window.postMessage({ type: messageType, data: getDataControl(currentVideo), eventType: event.type, }); } } function addEventListeners(video) { if (video.globalMediaControls) { return; } else { video.globalMediaControls = true; } video.setAttribute(nameAttribute, ''); addEventListenerAudioOriginal.apply(video, ['play', timeupdateVideo]); addEventListenerAudioOriginal.apply(video, ['timeupdate', timeupdateVideo]); addEventListenerAudioOriginal.apply(video, ['volumechange', volumechangeVideo]); addEventListenerAudioOriginal.apply(video, ['playing', timeupdateVideo]); addEventListenerAudioOriginal.apply(video, ['pause', pauseVideo]); addEventListenerAudioOriginal.apply(video, ['ended', endedVideo]); addEventListenerAudioOriginal.apply(video, ['error', endedVideo]); addEventListenerAudioOriginal.apply(video, ['enterpictureinpicture', enterpictureinpictureVideo]); addEventListenerAudioOriginal.apply(video, ['leavepictureinpicture', leavepictureinpictureVideo]); } function observeDOM(obj, callback) { const obs = new MutationObserver((mutations, observer) => { if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) { callback(mutations, observer); } }); obs.observe(obj, { childList: true, subtree: true, }); } function injectVideo() { const videos = document.querySelectorAll('video:not([global-media-controls]), audio:not([global-media-controls])'); videos.forEach((video) => { addEventListeners(video); }); } injectVideo(); observeDOM(document, () => injectVideo()); } function syncData(keyStorage) { chrome.storage.local.get(keyStorage, (result) => { for (const key in result[keyStorage]) { if (!tabs[key]) { createItem(result[keyStorage][key], result[keyStorage][key]); } } }); } function resizeCrop(image, width, height, itemInfo, canvas) { const crop = width === 0 || height === 0; const hasCanvas = !!canvas; // not resize if (image.width <= width && height === 0) { width = image.width; height = image.height; } // resize if (image.width > width && height === 0) { height = image.height * (width / image.width); } // check scale const xScale = width / image.width; const yScale = height / image.height; const scale = crop ? Math.min(xScale, yScale) : Math.max(xScale, yScale); // create empty canvas canvas = hasCanvas ? canvas : gnoh.createElement('canvas'); canvas.width = width || Math.round(image.width * scale); canvas.height = height || Math.round(image.height * scale); const ctx = canvas.getContext('2d'); ctx.scale(scale, scale); // crop it top center ctx.drawImage(image, (canvas.width - (image.width * scale)) * 0.5, (canvas.height - (image.height * scale)) * 0.5); if (!colorLoaded[image.src]) { const data = ctx.getImageData(0, 0, width, height).data; const blockSize = hasCanvas ? canvas.width : 6; const colors = {}; let i = 0; const length = data.length; while (i < length) { const rgb = { r: data[i], g: data[i + 1], b: data[i + 2], a: data[i + 3], }; i += blockSize * 4; if (rgb.a < 255) { continue; } let colorKey = Object.keys(colors).find(c => { const [r, g, b] = c.split(','); return gnoh.color.deltaE(rgb, { r: +r, g: +g, b: +b }) <= 2; }) if (!colorKey) { colorKey = [rgb.r, rgb.g, rgb.b].join(','); colors[colorKey] = { rgb, count: 0 }; } colors[colorKey].count++; } const entryColors = Object.entries(colors).sort((a, b) => b[1].count - a[1].count); let filterEntryColors = entryColors.filter((entryColor) => { const luminance = gnoh.color.getLuminance(entryColor[1].rgb.r, entryColor[1].rgb.g, entryColor[1].rgb.b); return luminance >= 30 && luminance <= 200; }); let rgbMax = { r: 0, g: 0, b: 0, }; if (filterEntryColors.length > 0) { rgbMax = filterEntryColors[0][1].rgb; } else { filterEntryColors = entryColors.filter((entryColor) => { const [r, g, b] = entryColor[0].split(','); return gnoh.color.deltaE({ r: 0, g: 0, b: 0 }, { r: +r, g: +g, b: +b }) > 2 && gnoh.color.deltaE({ r: 255, g: 255, b: 255 }, { r: +r, g: +g, b: +b }) > 2; }); if (filterEntryColors.length > 0) { rgbMax = filterEntryColors[0][1].rgb; } else if (entryColors.length > 0) { rgbMax = entryColors[0][1].rgb; } } const isLightBg = gnoh.color.isLight(rgbMax.r, rgbMax.g, rgbMax.b); const rgbProgressBar = gnoh.color.shadeColor(rgbMax.r, rgbMax.g, rgbMax.b, isLightBg ? 0.4 : -0.4); colorLoaded[image.src] = { backgroundColor: gnoh.color.rgbToHex(rgbMax.r, rgbMax.g, rgbMax.b), color: isLightBg ? '#f6f6f6' : '#111111', progressBarBackgroundColor: gnoh.color.rgbToHex(rgbProgressBar.r, rgbProgressBar.g, rgbProgressBar.b), }; } itemInfo.isLight = colorLoaded[image.src].isLight; itemInfo.backgroundColor = colorLoaded[image.src].backgroundColor; itemInfo.color = colorLoaded[image.src].color; itemInfo.item.style.setProperty('--colorGMCBg', colorLoaded[image.src].backgroundColor); itemInfo.item.style.setProperty('--colorGMCFg', colorLoaded[image.src].color); itemInfo.item.style.setProperty('--colorGMCProgressBarBg', colorLoaded[image.src].progressBarBackgroundColor); return canvas; } function toHHMMSS(seconds) { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); return [ h, m > 9 ? m : (h ? '0' + m : m || '0'), s > 9 ? s : '0' + s, ].filter(Boolean).join(':'); } function createItem(tab, info) { const itemInfo = { tabId: tab.id || tab.tabId, frameId: info.frameId, windowId: tab.windowId, webPanelId: tab.vivExtData?.panelId?.split('_').slice(0, 2).join('_'), setTitle(title) { if (title != null && itemInfo.title !== title) { itemInfo.title = title; itemInfo.titleItem.title = itemInfo.title; itemInfo.titleItem.textContent = itemInfo.title; } }, setArtist(artist) { if (artist != null && itemInfo.artist !== artist) { itemInfo.artist = artist; itemInfo.domainItem.textContent = itemInfo.artist; } }, setUrl(url) { if (url == null || url === itemInfo.url) { return; } itemInfo.url = url; const urlObject = new URL(url); itemInfo.hostname = urlObject.hostname; itemInfo.defaultImage = 'chrome://favicon/' + urlObject.origin; if (itemInfo.artist == null) { itemInfo.domainItem.textContent = itemInfo.hostname; } }, setImage(src) { if (itemInfo.image !== undefined && (src == null || src === itemInfo.image)) { return; } itemInfo.image = src; gnoh.createElement('img', { src: itemInfo.image || itemInfo.defaultImage, crossOrigin: 'Anonymous', events: { load(e) { if (e.target.src === itemInfo.defaultImage) { itemInfo.hasImage = false; resizeCrop(e.target, 100, 100, itemInfo); } else { itemInfo.hasImage = true; itemInfo.imageItem.style.display = ''; resizeCrop(e.target, 100, 100, itemInfo, itemInfo.imageItem); } }, error(e) { if (e.target.src !== itemInfo.defaultImage) { e.target.src = itemInfo.defaultImage; } } } }); }, setPaused(paused) { if (paused != null && itemInfo.paused !== paused) { itemInfo.paused = paused; itemInfo.buttonControl.innerHTML = itemInfo.paused ? icons.play : icons.pause; } }, setPictureInPicture(pictureInPicture) { if (pictureInPicture != null && itemInfo.pictureInPicture !== pictureInPicture) { itemInfo.pictureInPicture = pictureInPicture; itemInfo.buttonPictureInPicture.innerHTML = itemInfo.pictureInPicture ? icons.pictureInPicture.on : icons.pictureInPicture.off; if (itemInfo.pictureInPicture) { itemInfo.buttonPictureInPicture.classList.add('active'); } else { itemInfo.buttonPictureInPicture.classList.remove('active'); } } }, setActive(active) { if (active != null && itemInfo.active !== active) { itemInfo.active = active; if (itemInfo.webPanelId) { itemInfo.buttonTab.innerHTML = icons.sidebar.left; } else { itemInfo.buttonTab.innerHTML = itemInfo.active ? icons.tab.on : icons.tab.off; if (active) { itemInfo.buttonTab.classList.add('active'); } else { itemInfo.buttonTab.classList.remove('active'); } } } }, setAudio(audio) { if (audio != null && itemInfo.audio !== audio) { itemInfo.audio = audio; if (itemInfo.audio) { itemInfo.buttonPictureInPicture.style.display = 'none'; } else { itemInfo.buttonPictureInPicture.style.display = ''; } } }, setVolume(volume) { if (volume != null && itemInfo.volume !== volume) { itemInfo.volume = volume; itemInfo.muted = false; if (itemInfo.volume === 0) { itemInfo.buttonVolume.innerHTML = icons.volume.off; itemInfo.rangeVolume.value = 0; } else if (itemInfo.volume <= 0.5) { itemInfo.buttonVolume.innerHTML = icons.volume.medium; itemInfo.rangeVolume.value = itemInfo.volume; } else { itemInfo.buttonVolume.innerHTML = icons.volume.high; itemInfo.rangeVolume.value = itemInfo.volume; } } }, setMuted(muted) { if (muted != null && itemInfo.muted !== muted) { itemInfo.muted = muted; if (itemInfo.muted) { itemInfo.buttonVolume.innerHTML = icons.volume.off; itemInfo.rangeVolume.value = 0; } else if (itemInfo.volume === 0) { itemInfo.muted = false; itemInfo.buttonVolume.innerHTML = icons.volume.high; itemInfo.rangeVolume.value = itemInfo.volume; } else if (itemInfo.volume <= 0.5) { itemInfo.buttonVolume.innerHTML = icons.volume.medium; itemInfo.rangeVolume.value = itemInfo.volume; } else { itemInfo.buttonVolume.innerHTML = icons.volume.high; itemInfo.rangeVolume.value = itemInfo.volume; } } }, setProgress(duration, currentTime) { if (duration != null && itemInfo.duration !== duration || currentTime != null && itemInfo.currentTime !== currentTime) { itemInfo.duration = duration; itemInfo.currentTime = currentTime; itemInfo.durationStr = toHHMMSS(itemInfo.duration); itemInfo.currentTimeStr = toHHMMSS(itemInfo.currentTime); itemInfo.currentTimeDuration.textContent = itemInfo.currentTimeStr + ' / ' + itemInfo.durationStr; itemInfo.item.style.setProperty('--colorGMCProgressBarValue', itemInfo.currentTime / itemInfo.duration * 100 + '%'); } }, }; itemInfo.domainItem = gnoh.createElement('div', { class: 'domain', }); itemInfo.setArtist(info.artist); itemInfo.setUrl(tab?.url); itemInfo.imageItem = gnoh.createElement('canvas', { width: 100, height: 100, style: { display: 'none', }, }); itemInfo.setImage(info.image); itemInfo.buttonClose = gnoh.createElement('button', { type: 'button', class: 'close-button', html: icons.close, tabindex: -1, draggable: true, events: { dragstart(e) { e.preventDefault(); e.stopPropagation(); }, async click(event) { event.preventDefault(); chrome.tabs.sendMessage(itemInfo.tabId, { type: messageType, tabId: itemInfo.tabId, frameId: itemInfo.frameId, action: 'close' }, { frameId: itemInfo.frameId, }, () => { deleteItem(itemInfo.tabId); }); }, }, }); itemInfo.titleItem = gnoh.createElement('div', { class: 'title', }); itemInfo.setTitle(info.title || tab.title); itemInfo.buttonControl = gnoh.createElement('button', { type: 'button', tabindex: -1, draggable: true, events: { dragstart(e) { e.preventDefault(); e.stopPropagation(); }, click(event) { event.preventDefault(); const request = { type: messageType, tabId: itemInfo.tabId, frameId: itemInfo.frameId, }; if (itemInfo.paused) { request.action = 'play'; } else { request.action = 'pause'; } itemInfo.setPaused(!itemInfo.paused); chrome.tabs.sendMessage(itemInfo.tabId, request, { frameId: itemInfo.frameId, }); }, }, }); itemInfo.setPaused(info.paused); itemInfo.buttonPictureInPicture = gnoh.createElement('button', { type: 'button', tabindex: -1, draggable: true, events: { dragstart(e) { e.preventDefault(); e.stopPropagation(); }, click(event) { event.preventDefault(); if (!itemInfo.audio) { chrome.tabs.sendMessage(itemInfo.tabId, { type: messageType, action: 'picture-in-picture', tabId: itemInfo.tabId, frameId: itemInfo.frameId, }, { frameId: itemInfo.frameId, }); } }, }, }); itemInfo.setPictureInPicture(info.pictureInPicture); itemInfo.setAudio(info.audio); itemInfo.buttonTab = gnoh.createElement('button', { type: 'button', tabindex: -1, draggable: true, events: { dragstart(e) { e.preventDefault(); e.stopPropagation(); }, click(event) { event.preventDefault(); if (!itemInfo.active) { if (itemInfo.webPanelId) { if (itemInfo.windowId === vivaldiWindowId) { simulateWebviewButtonClick({ webPanelId: itemInfo.webPanelId, openOnly: true }); } else { chrome.windows.update(itemInfo.windowId, { focused: true }); chrome.runtime.sendMessage({ type: messageType, action: 'open-webpanel', windowId: itemInfo.windowId, webPanelId: itemInfo.webPanelId, }); } } else { chrome.tabs.update(itemInfo.tabId, { active: true }, () => { chrome.windows.update(itemInfo.windowId, { focused: true }); }); } } if (!itemInfo.audio) { chrome.tabs.sendMessage(itemInfo.tabId, { type: messageType, action: 'scroll-into-view', tabId: itemInfo.tabId, frameId: itemInfo.frameId, }, { frameId: itemInfo.frameId, }); } activeItem(itemInfo.tabId); }, }, }); itemInfo.setActive(info.active || false); itemInfo.volumeControl = gnoh.createElement('div', { className: 'volume-control', draggable: true, events: { dragstart(e) { e.preventDefault(); e.stopPropagation(); }, }, }); itemInfo.buttonVolume = gnoh.createElement('button', { type: 'button', tabindex: -1, events: { click(event) { event.preventDefault(); chrome.tabs.sendMessage(itemInfo.tabId, { type: messageType, action: 'muted', tabId: itemInfo.tabId, frameId: itemInfo.frameId, }, { frameId: itemInfo.frameId, }); itemInfo.setMuted(!itemInfo.muted); }, }, }, itemInfo.volumeControl); itemInfo.rangeVolume = gnoh.createElement('input', { type: 'range', tabindex: -1, className: 'range-volume', min: 0, max: 1, step: 0.01, events: { input(event) { event.preventDefault(); chrome.tabs.sendMessage(itemInfo.tabId, { type: messageType, action: 'volume', tabId: itemInfo.tabId, frameId: itemInfo.frameId, volume: event.target.value, }, { frameId: itemInfo.frameId, }); }, }, }, itemInfo.volumeControl); itemInfo.setVolume(info.volume); itemInfo.setMuted(info.muted); itemInfo.currentTimeDuration = gnoh.createElement('div', { className: 'current-time-duration', draggable: true, events: { dragstart(e) { e.preventDefault(); e.stopPropagation(); }, }, }); itemInfo.actionItem = gnoh.createElement('div', { class: 'action', }, null, [itemInfo.buttonControl, itemInfo.buttonPictureInPicture, itemInfo.buttonTab, itemInfo.volumeControl, itemInfo.currentTimeDuration]); itemInfo.contentItem = gnoh.createElement('div', { class: 'content', }, null, [itemInfo.titleItem, itemInfo.domainItem, itemInfo.actionItem]); itemInfo.item = gnoh.createElement('div', { class: 'item', 'data-tab-id': itemInfo.tabId, draggable: true, events: { dragstart(e) { this.classList.add('dragstart'); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', itemInfo.tabId); dragSource = this; }, dragover(e) { const target = e.target.closest('.item'); if (target === dragSource) { return; } const bounding = target.getBoundingClientRect(); const offset = bounding.y + (bounding.height / 2); if (e.clientY - offset > 0) { if (target.nextSibling) { if (target.nextSibling === dragSource) { return; } target.nextSibling.classList.add('dragover-top'); } else { target.classList.add('dragover-bottom'); } target.classList.remove('dragover-top'); } else { if (target.previousSibling === dragSource) { return; } if (target.nextSibling) { target.nextSibling.classList.remove('dragover-top'); } else { target.classList.remove('dragover-bottom'); } target.classList.add('dragover-top'); } e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }, dragenter(e) { this.classList.add('dragover'); }, dragleave(e) { this.classList.remove('dragover'); this.classList.remove('dragover-top'); if (this.nextSibling) { this.nextSibling.classList.remove('dragover-top'); } else { this.classList.remove('dragover-bottom'); } }, drop(e) { e.preventDefault(); const target = e.target.closest('.item'); if (target.classList.contains('dragover-top')) { target.classList.remove('dragover-top'); target.parentNode.insertBefore(dragSource, target); } else { if (target.nextSibling) { target.nextSibling.classList.remove('dragover-top'); } else { target.classList.remove('dragover-bottom'); } target.parentNode.insertBefore(dragSource, target.nextSibling); } }, dragend(e) { this.classList.remove('dragstart'); for (const key in tabs) { const tab = tabs[key]; tab.item.classList.remove('dragover'); } }, }, }, panelContent, [itemInfo.contentItem, itemInfo.imageItem, itemInfo.buttonClose]); itemInfo.setProgress(info.duration, info.currentTime); tabs[itemInfo.tabId] = itemInfo; const index = Object.keys(tabs).indexOf(itemInfo.tabId + ''); gnoh.element.appendAtIndex(itemInfo.item, panelContent, index); return itemInfo; } function deleteItem(tabId) { if (tabs[tabId]) { tabs[tabId].item.remove(); delete tabs[tabId]; } updateButtonToolbar(); updateNumberOfItems(); } function replaceItem(addedTabId, removedTabId) { if (tabs[removedTabId]) { tabs[addedTabId] = tabs[removedTabId]; delete tabs[removedTabId]; } } function activeItem(tabId) { if (!tabs[tabId]?.webPanelId && !tabs[tabId]?.active) { for (const key in tabs) { const tab = tabs[key]; tab.setActive(key === tabId + ''); } } } async function updateItem(tab, info) { const tabId = tab?.id || tab?.tabId; tab = { ...await chrome.tabs.get(tabId), ...tab }; tab.vivExtData = tab.vivExtData ? JSON.parse(tab.vivExtData) : {}; console.log(tab); if (info.paused !== undefined) { if (!tabs[tabId]) { createItem(tab, info); if (tab.active && tab.windowId === vivaldiWindowId) { activeItem(tab.id); } } else { tabs[tabId].tabId = tabId; tabs[tabId].windowId = tab?.windowId; tabs[tabId].frameId = info.frameId; tabs[tabId].setTitle(info.title || tab.title); tabs[tabId].setArtist(info.artist); tabs[tabId].setUrl(tab?.url); tabs[tabId].setImage(info.image); tabs[tabId].setPaused(info.paused); tabs[tabId].setPictureInPicture(info.pictureInPicture); tabs[tabId].setAudio(info.audio); tabs[tabId].setVolume(info.volume); tabs[tabId].setMuted(info.muted); tabs[tabId].setProgress(info.duration, info.currentTime); } updateButtonToolbar(); updateNumberOfItems(); } else if (info.ended) { deleteItem(tabId); } } function updateButtonToolbar() { if (lucidModeVideo) { buttons.lucidModeVideo.pressed = true; if (buttons.lucidModeVideo.iconEL) { buttons.lucidModeVideo.iconEL.innerHTML = icons.lucidModeVideo.on; } if (buttons.lucidModeVideo.buttonEl) { buttons.lucidModeVideo.buttonEl.classList.add('button-pressed'); } } else { buttons.lucidModeVideo.pressed = false; if (buttons.lucidModeVideo.iconEL) { buttons.lucidModeVideo.iconEL.innerHTML = icons.lucidModeVideo.off; } if (buttons.lucidModeVideo.buttonEl) { buttons.lucidModeVideo.buttonEl.classList.remove('button-pressed'); } } if (buttons.lucidModeVideo.disabled) { buttons.lucidModeVideo.disabled = false; if (buttons.lucidModeVideo.buttonEl) { buttons.lucidModeVideo.buttonEl.disabled = false; } } if (Object.keys(tabs).length === 0) { if (buttons.volume.iconEL && buttons.volume.muted) { buttons.volume.iconEL.innerHTML = icons.volume.high; } if (buttons.volume.buttonEl && !buttons.volume.buttonEl.disabled) { buttons.volume.disabled = true; buttons.volume.buttonEl.disabled = true; } buttons.volume.muted = false; buttons.volume.icon = icons.volume.high; if (buttons.pause.buttonEl && !buttons.pause.buttonEl.disabled) { buttons.pause.disabled = true; buttons.pause.buttonEl.disabled = true; } } else { let iconMute = icons.volume.off; let muted = true; let pauseDisabled = true; for (const key in tabs) { const tab = tabs[key]; if (!tab.muted && tab.volume !== 0) { iconMute = icons.volume.high; muted = false; } if (!tab.paused) { pauseDisabled = false; } } if (buttons.volume.iconEL && buttons.volume.muted !== muted) { buttons.volume.iconEL.innerHTML = iconMute; } if (buttons.volume.buttonEl?.disabled) { buttons.volume.disabled = false; buttons.volume.buttonEl.disabled = false; } buttons.volume.icon = iconMute; buttons.volume.muted = muted; if (buttons.pause.buttonEl && buttons.pause.buttonEl.disabled !== pauseDisabled) { buttons.pause.disabled = pauseDisabled; buttons.pause.buttonEl.disabled = pauseDisabled; } } } function updateNumberOfItems() { buttonBadges.forEach((buttonBadge) => { if (!buttonBadge) { return; } const numberOfItems = Object.keys(tabs).length; if (numberOfItems > 0) { if (Number(buttonBadge.textContent) !== numberOfItems) { buttonBadge.textContent = numberOfItems; } buttonBadge.style.display = ''; } else { buttonBadge.style.display = 'none'; } }); } function simulateWebviewButtonClick({ webPanelId, webviewButton, openOnly }) { if (webPanelId) { webviewButton = document.querySelector('.toolbar > .button-toolbar > .ToolbarButton-Button[name*="' + webPanelId + '"]'); } if (openOnly && webviewButton.parentNode?.classList.contains('active')) { return; } const pointerDown = new PointerEvent('pointerdown', { view: window, bubbles: true, cancelable: true, buttons: 0, pointerType: 'mouse', }); pointerDown.persist = () => { }; gnoh.getReactProps(webviewButton)?.onPointerDown(pointerDown); webviewButton.dispatchEvent(new PointerEvent('pointerup', { view: window, bubbles: true, cancelable: true, buttons: 0, pointerType: 'mouse', })); } async function createPanelCustom(panel, webviewButton) { if (!chrome.extension.inIncognitoContext) { if (panel.dataset.globalMediaControls) { return; } panel.dataset.globalMediaControls = true; let showCloseButton = await vivaldi.prefs.get('vivaldi.panels.show_close_button'); let autoClose = await vivaldi.prefs.get('vivaldi.panels.as_overlay.auto_close'); let asOverlayEnabled = await vivaldi.prefs.get('vivaldi.panels.as_overlay.enabled'); const buttonClose = gnoh.createElement('button', { class: 'close transparent', title: langs.closePanel, style: { display: showCloseButton && asOverlayEnabled && autoClose || !showCloseButton ? 'none' : 'flex', }, events: { click() { simulateWebviewButtonClick({ webviewButton }); }, }, }); vivaldi.prefs.onChanged.addListener(({ path, value }) => { switch (path) { case 'vivaldi.panels.show_close_button': showCloseButton = value; buttonClose.style.display = showCloseButton && asOverlayEnabled && autoClose || !showCloseButton ? 'none' : 'flex'; break; case 'vivaldi.panels.as_overlay.auto_close': autoClose = value; buttonClose.style.display = showCloseButton && asOverlayEnabled && autoClose || !showCloseButton ? 'none' : 'flex'; break; case 'vivaldi.panels.as_overlay.enabled': asOverlayEnabled = value; buttonClose.style.display = showCloseButton && asOverlayEnabled && autoClose || !showCloseButton ? 'none' : 'flex'; break; } }); gnoh.createElement('span', { class: 'VivaldiSvgIcon', style: { '--IconSize': 16, }, html: icons.closePanel }, buttonClose); const title = gnoh.createElement('h1', { html: '' + name + '', }, null, buttonClose); const inputSearch = gnoh.createElement('input', { type: 'search', placeholder: langs.search, events: { input(e) { for (const key in tabs) { const tab = tabs[key]; const value = e.target.value.trim().toLowerCase().replace(/\s\s+/g, ' '); const title = tab.title.trim().toLowerCase().replace(/\s\s+/g, ' '); const hostname = tab.hostname.trim().toLowerCase().replace(/\s\s+/g, ' '); if ( title.match(value) || gnoh.string.removeDiacritics(title).match(value) || hostname.match(value) ) { tab.item.style.display = ''; } else { tab.item.style.display = 'none'; } } }, }, }); const toolbarGroup = gnoh.createElement('div', { class: 'toolbar-group', }); for (const key in buttons) { const button = buttons[key]; const iconEl = gnoh.createElement('span', { html: button.icon, }); const buttonEl = gnoh.createElement('button', { tabindex: '-1', class: ('ToolbarButton-Button' + (button.pressed ? ' button-pressed' : '')), disabled: button.disabled, events: { click: button.click, }, }, null, iconEl); const buttonToolbar = gnoh.createElement('div', { class: 'button-toolbar', }, toolbarGroup, buttonEl); button.iconEL = iconEl; button.buttonEl = buttonEl; } const toolbar = gnoh.createElement('div', { class: 'toolbar', }, null, toolbarGroup); const toolbarWrap = gnoh.createElement('div', { class: 'toolbar toolbar-default toolbar-medium toolbar-wrap', }, null, [inputSearch, toolbar]); const panelHeader = gnoh.createElement('header', null, panel, [title, toolbarWrap]); panel.append(panelContent); } else if (webviewButton) { if (panel.dataset.globalMediaControls) { return; } panel.dataset.globalMediaControls = true; if (panel.classList.contains('visible')) { simulateWebviewButtonClick({ webviewButton }); } } } const style = !chrome.extension.inIncognitoContext ? [ '#panels-container.left #panels .webpanel-stack [data-global-media-controls] header { padding-left: 9px; }', '#panels-container.right #panels .webpanel-stack [data-global-media-controls] header { padding-left: 12px; }', '#panels-container #panels .webpanel-stack [data-global-media-controls] header { padding-right: var(--scrollbarWidth); padding-top: 12px; }', '#panels-container #panels .webpanel-stack [data-global-media-controls] header.webpanel-header { display: none; }', '#panels-container #panels .webpanel-stack [data-global-media-controls] .webpanel-content { display: none; }', '.global-media-controls-content { display: flex; flex-direction: column; overflow: auto; }', '.global-media-controls-content .item { position: relative; display: flex; overflow: hidden; min-height: 100px; background-color: var(--colorGMCBg); color: var(--colorGMCFg); }', '.global-media-controls-content .item:after { position: absolute; content: ""; bottom: 0; height: 4px; width: var(--colorGMCProgressBarValue, 0); z-index: 1; background-color: var(--colorGMCProgressBarBg); }', '.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; }', '.global-media-controls-content .item .content .title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }', '.global-media-controls-content .item .content .action { display: flex; align-items: center; margin-top: auto; position: absolute; bottom: 10px; }', '.global-media-controls-content .item .content .action button { margin-right: 10px; flex: 1 0 auto; }', '.global-media-controls-content .item button { background-color: var(--colorBgLightIntense); color: var(--colorFg); padding: 0; width: 28px; height: 28px; border-radius: 14px; border: 0; }', '.global-media-controls-content .item button:hover { background-color: var(--colorBg); }', '.global-media-controls-content .item button:active { background-color: var(--colorBgDark); }', '.global-media-controls-content .item button.active { background-color: var(--colorHighlightBg); color: var(--colorHighlightFg); }', '.global-media-controls-content .item button.disabled { pointer-events: none; }', '.global-media-controls-content .item button svg { width: 16px; height: 16px; top: 2px; position: relative; }', '.global-media-controls-content .item button.close-button { position: absolute; top: 6px; right: 6px; display: none; z-index: 1; }', '.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; }', '.global-media-controls-content .item .content .action .volume-control:hover { width: auto; display: flex; flex-direction: row; align-items: center; }', '.global-media-controls-content .item .content .action .volume-control button { margin-right: 0; }', '.global-media-controls-content .item .content .action .volume-control .range-volume { width: 80px; display: none; }', '.global-media-controls-content .item .content .action .volume-control:hover .range-volume { display: block; }', '.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; }', '.global-media-controls-content .item:hover button.close-button { display: block; }', '.global-media-controls-content .item.dragstart { opacity: 0.4; }', '.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; }', '.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; }', 'button[name="' + webPanelId + '"] > img { display:none; }', 'button[name="' + webPanelId + '"]:before { width: 16px; height: 16px; content: ""; background-color: var(--colorFg); -webkit-mask-box-image: url(' + JSON.stringify(icons.dataURLs.playlistMusic) + '); }', '.color-behind-tabs-off .toolbar-mainbar button[name="' + webPanelId + '"]:before { background-color: var(--colorAccentFg); }', '.button-toolbar:active button[name="' + webPanelId + '"]:before { transform: scale(0.9); }', ] : [ '.button-toolbar:has(button[name="' + webPanelId + '"]) { display:none !important; }', '.draggable-button:has(button[name="' + webPanelId + '"]) { display:none !important; }', ]; gnoh.addStyle(style, nameAttribute); function updateIconAndTitle() { const webviewButtons = Array.from(document.querySelectorAll('.toolbar > .button-toolbar > .ToolbarButton-Button[name*="' + webPanelId + '"]')); const panel = document.querySelector('.panel.webpanel:has(webview[tab_id*="' + webPanelId + '"])'); if (panel && webviewButtons.length) { createPanelCustom(panel, webviewButtons[0]); } webviewButtons.forEach((wvb) => { if (!chrome.extension.inIncognitoContext) { if (wvb.dataset.globalMediaControls) { return; } wvb.dataset.globalMediaControls = true; const buttonBadge = gnoh.createElement('span', { class: 'button-badge', style: { display: 'none', }, }); buttonBadges.push(buttonBadge); wvb.append(buttonBadge); } }); } function createWebPanel() { vivaldi.prefs.get('vivaldi.panels.web.elements', (elements) => { let element = elements.find((e) => e.id === webPanelId); if (!element) { element = { activeUrl: code, faviconUrl: icons.dataURLs.playlistMusic, faviconUrlValid: true, id: webPanelId, mobileMode: true, origin: 'user', resizable: false, title: name, url: 'vivaldi://' + nameAttribute, width: -1, zoom: 1, }; elements.unshift(element); vivaldi.prefs.set({ path: 'vivaldi.panels.web.elements', value: elements, }); } Promise.all( [ 'vivaldi.toolbars.panel', 'vivaldi.toolbars.navigation', 'vivaldi.toolbars.status', 'vivaldi.toolbars.mail', 'vivaldi.toolbars.mail_message', 'vivaldi.toolbars.mail_composer', ].map((path) => vivaldi.prefs.get(path)) ).then((toolbars) => { const hasGlobalMediaControl = toolbars.some((toolbar) => toolbar.some((p) => p === webPanelId)); if (!hasGlobalMediaControl) { const panels = toolbars[0]; const panelIndex = panels.findIndex(panel => panel.startsWith('WEBPANEL_')); panels.splice(panelIndex, 0, webPanelId); vivaldi.prefs.set({ path: 'vivaldi.toolbars.panel', value: panels, }); } }); }); } vivaldi.windowPrivate.onActivated.addListener((windowId, active) => { if (active) { chrome.tabs.query({ active: true, windowId: windowId }, (tabs) => { const tab = tabs[0]; if (tab) { activeItem(tab.id); } }); } }); if (!chrome.extension.inIncognitoContext) { chrome.tabs.onActivated.addListener((activeInfo) => { activeItem(activeInfo.tabId); }); chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.status === 'loading') { deleteItem(tabId); } }); chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => { replaceItem(addedTabId, removedTabId); }); chrome.tabs.onRemoved.addListener((tabId, removeInfo) => { deleteItem(tabId); }); chrome.windows.getAll({ windowTypes: ['normal'] }, (windows) => { const windowNotIncognitos = windows.filter((w) => !w.incognito); if (windowNotIncognitos.length < 2) { chrome.storage.local.remove('GLOBAL_MEDIA_CONTROLS'); } else { syncData('GLOBAL_MEDIA_CONTROLS'); } }); chrome.runtime.onMessage.addListener(async (info, sender, sendResponse) => { if (info.type === messageType && (!sender.tab || (sender.tab && !sender.tab.incognito))) { switch (info.action) { case 'open-webpanel': if (info.windowId === vivaldiWindowId) { simulateWebviewButtonClick({ webPanelId: info.webPanelId, openOnly: true }); } break; default: info.frameId = sender.frameId; await updateItem(sender.tab, info); chrome.storage.local.set({ GLOBAL_MEDIA_CONTROLS: tabs, }); break; } } }); gnoh.timeOut(() => { chrome.tabs.query({ windowId: window.vivaldiWindowId, windowType: 'normal' }, (tabs) => { tabs.forEach((tab) => { if (!tab.incognito) { chrome.scripting.executeScript({ target: { tabId: tab.id, allFrames: true, }, func: injectMain, world: 'MAIN', args: [messageType, nameAttribute], }); chrome.scripting.executeScript({ target: { tabId: tab.id, allFrames: true, }, func: inject, args: [messageType], }); } }); }); chrome.webNavigation.onCommitted.addListener((details) => { chrome.scripting.executeScript({ target: { tabId: details.tabId, frameIds: [details.frameId], }, func: injectMain, world: 'MAIN', args: [messageType, nameAttribute], }); chrome.scripting.executeScript({ target: { tabId: details.tabId, frameIds: [details.frameId], }, func: inject, args: [messageType], }); }); }, () => window.vivaldiWindowId != null); function injectToggleLucidModeVideo(enable) { let style = document.querySelector('style[lucid-mode-video]'); if (style && !enable) { style.remove(); } else if (!style && enable) { style = document.createElement('style'); style.setAttribute('lucid-mode-video', ''); style.innerHTML = 'video { filter: url(\'data:image/svg+xml, #sharpen\'); }'; document.head.append(style); } } function toggleLucidModeVideo() { updateButtonToolbar(); chrome.tabs.query({ windowType: 'normal' }, (tabs) => { tabs.forEach((tab) => { chrome.scripting.executeScript({ target: { tabId: tab.id, allFrames: true, }, func: injectToggleLucidModeVideo, args: [lucidModeVideo], }); }); }); } chrome.storage.local.get({ LUCID_MODE_VIDEO: false }, (result) => { lucidModeVideo = result.LUCID_MODE_VIDEO; toggleLucidModeVideo(); chrome.webNavigation.onCommitted.addListener((details) => { chrome.scripting.executeScript({ target: { tabId: details.tabId, frameIds: [details.frameId], }, func: injectToggleLucidModeVideo, args: [lucidModeVideo], }); }); }); chrome.storage.local.onChanged.addListener((changes, namespace) => { if (changes.LUCID_MODE_VIDEO) { lucidModeVideo = changes.LUCID_MODE_VIDEO.newValue; toggleLucidModeVideo(); } }); } gnoh.timeOut(() => { const webviewButtons = Array.from(document.querySelectorAll('.toolbar > .button-toolbar > .ToolbarButton-Button[name*="' + webPanelId + '"]')); if (webviewButtons.length) { updateIconAndTitle(); } else { createWebPanel(); } }, '#browser'); gnoh.observeDOM(document, () => { updateIconAndTitle(); }); })();