diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb0a3c..3c0a960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +Changes in [0.7] (2020-03-23) +============================================================================================ + + * Implemented setting per-host colors via direct match and regexp + + * Implemented re-applying colors after main TST extension upgrade + + * Fixed issue with lost settings when saving + + * Replaced active and hover tabs tint to filter saturation and brigtness of original color + + * Some other code refactor for improve speed and stability + Changes in [0.6] (2019-05-27) ============================================================================================ @@ -32,9 +45,9 @@ Changes in [0.2] (2019-01-05) ============================================================================================ * Colorize all tabs on extension initialization. - + * Fixed bug with not changing tab color when return to old domain. - + * Added Options page with default options * Allow customize CSS styles for all tabs, active and hovered tabs. diff --git a/background.js b/background.js index 89c125d..08a8d2a 100644 --- a/background.js +++ b/background.js @@ -6,27 +6,67 @@ const DEFAULT_SETTINGS = { saturation: 60, lightness: 70, colors: 15, - active_saturation: 60, - active_lightness: 60, - active_bold: true, - hover_saturation: 60, - hover_lightness: 65, + activeSaturate: 120, + activeBrightness: 120, + activeBold: true, + hoverSaturate: 110, + hoverBrightness: 110, + hosts: [ + { host: 'example.com', regexp: false, color: '#FF7700', disabled: true }, + { host: '.*\\.google\\..*', regexp: true, color: '#7171FF', disabled: true }, + ], }; var ColoredTabs = { - init() { - browser.storage.sync.get(DEFAULT_SETTINGS).then(function(settings) { -// console.log('init settings'); - ColoredTabs.settings = settings; - ColoredTabs.colorizeAllTabs(); - browser.tabs.onUpdated.addListener(ColoredTabs.checkTabChanges); - browser.tabs.onRemoved.addListener(ColoredTabs.removeTabInfo) -// browser.tabs.onCreated.addListener(ColoredTabs.handleCreated); - ColoredTabs.state.inited = true; -// console.log('init settings fin'); - }); - }, - + state: { + 'inited': false, + }, + + init() { + browser.storage.sync.get().then(function(settingsStored) { + ColoredTabs.settings = {} + Object.assign(ColoredTabs.settings, DEFAULT_SETTINGS, settingsStored); + + ColoredTabs.state = { + 'tabsHost': [], + 'tabsClass': [], + 'inited': false, + }; + // console.log(ColoredTabs.settings); + + if(ColoredTabs.settings.hosts) { + ColoredTabs.settings.hosts.forEach(function(hostsItem) { + if(hostsItem.disabled !== true && hostsItem.color.length > 3) { + if(hostsItem.regexp == true) { + if(ColoredTabs.state.hostsRegexp === undefined) { + ColoredTabs.state.hostsRegexp = []; + ColoredTabs.state.hostsRegexpColor = []; + } + ColoredTabs.state.hostsRegexp.push(hostsItem.host); + ColoredTabs.state.hostsRegexpColor.push(hostsItem.color); + } else { + if(ColoredTabs.state.hostsMatch === undefined) { + ColoredTabs.state.hostsMatch = []; + ColoredTabs.state.hostsMatchColor = []; + } + ColoredTabs.state.hostsMatch.push(hostsItem.host); + ColoredTabs.state.hostsMatchColor.push(hostsItem.color); + } + } + }); + } + + ColoredTabs.colorizeAllTabs(); + browser.tabs.onUpdated.addListener(ColoredTabs.checkTabChanges); + browser.tabs.onRemoved.addListener(ColoredTabs.removeTabInfo) + // browser.tabs.onCreated.addListener(ColoredTabs.handleCreated); + + // console.log(ColoredTabs.state); + + ColoredTabs.state.inited = true; + }); + }, + // handleCreated(tab) { // console.log("handleCreated tab id " + tab.id + " tab url " + tab.url); // if(tab.url.indexOf('about:') === 0) @@ -35,86 +75,104 @@ var ColoredTabs = { // host = host.hostname.toString(); // ColoredTabs.colorizeTab(tab.id, host); // }, - - checkTabChanges(tabId, changeInfo, tab) { + + checkTabChanges(tabId, changeInfo, tab) { // console.log("checkTabChanges tab id " + tabId + " tab url " + tab.url); // console.log(changeInfo); - if(typeof changeInfo.url === 'undefined' || tab.url.indexOf('about:') === 0) - return; - let host = new URL(changeInfo.url); - host = host.hostname.toString(); - if(host != ColoredTabs.state.tabHost[tabId]) { - ColoredTabs.state.tabHost[tabId] = host; - ColoredTabs.colorizeTab(tabId, host); - } - }, - removeTabInfo(tabId, removeInfo) { + if(typeof changeInfo.url === 'undefined' || tab.url.indexOf('about:') === 0) + return; + let host = new URL(changeInfo.url); + host = host.hostname.toString(); + if(host != ColoredTabs.state.tabsHost[tabId]) { + ColoredTabs.state.tabsHost[tabId] = host; + ColoredTabs.colorizeTab(tabId, host); + } + }, + removeTabInfo(tabId, removeInfo) { // console.log("removeTabInfo tab id " + tabId); - delete ColoredTabs.state.tabHost[tabId]; - delete ColoredTabs.state.tabHue[tabId]; - }, - colorizeAllTabs() { + delete ColoredTabs.state.tabsHost[tabId]; + delete ColoredTabs.state.tabsClass[tabId]; + }, + colorizeAllTabs() { // console.log('colorizeAllTabs() start'); - let css = ''; - if(ColoredTabs.settings.active_bold == true) { - css += '.tab.active .label{font-weight:bold}'; + let css = ` +.tab.active {filter: saturate(` + ColoredTabs.settings.activeSaturate + `%) brightness(` + ColoredTabs.settings.activeBrightness + `%);} +.tab:hover {filter: saturate(` + ColoredTabs.settings.hoverSaturate + `%) brightness(` + ColoredTabs.settings.hoverBrightness + `%);}`; + + if(ColoredTabs.settings.activeBold == true) { + css += '.tab.active .label{font-weight:bold}'; + } + + for(let i = 0; i < 360; i += (360 / ColoredTabs.settings.colors)) { + let hue = Math.round(i); + css += `.tab.coloredTabsHue` + hue + ` {background-color: hsl(` + hue + `,` + ColoredTabs.settings.saturation + `%,` + ColoredTabs.settings.lightness + `%);}`; + } + + if(ColoredTabs.state.hostsMatchColor !== undefined) { + ColoredTabs.state.hostsMatchColor.forEach((element, index) => css += `.tab.coloredTabsHostMatch` + index + ` {background-color: ` + element + `;}`); + } + if(ColoredTabs.state.hostsRegexpColor !== undefined) { + ColoredTabs.state.hostsRegexpColor.forEach((element, index) => css += `.tab.coloredTabsHostRegexp` + index + ` {background-color: ` + element + `;}`); + } + // console.log(css); + + browser.runtime.sendMessage(TST_ID, { + type: "register-self", + style: css, + }); + + browser.tabs.query({}).then(function(tabs){ + for (let tab of tabs) { + let host = new URL(tab.url); + host = host.hostname.toString(); +// console.log('colorize tab id ' + tab.id + ' host ' + host); + ColoredTabs.colorizeTab(tab.id, host); } - for(let i = 0; i < 360; i += (360 / ColoredTabs.settings.colors)) { - let hue = Math.round(i); - css += ` -.tab.coloredTabsHue` + hue + ` {background-color: hsl(` + hue + `,` + ColoredTabs.settings.saturation + `%,` + ColoredTabs.settings.lightness + `%);} -.tab.coloredTabsHue` + hue + `.active {background-color: hsl(` + hue + `,` + ColoredTabs.settings.active_saturation + `%,` + ColoredTabs.settings.active_lightness + `%);} -.tab.coloredTabsHue` + hue + `:hover {background-color: hsl(` + hue + `,` + ColoredTabs.settings.hover_saturation + `%,` + ColoredTabs.settings.hover_lightness + `%);}`; + }, onError); + }, + + colorizeTab(tabId, host) { + let tabClass = null; + let index = null; + if ((ColoredTabs.state.hostsMatch !== undefined) && (index = ColoredTabs.state.hostsMatch.indexOf(host) > -1)) { + tabClass = 'coloredTabsHostMatch' + ColoredTabs.state.hostsMatch.indexOf(host); + } else if (ColoredTabs.state.hostsRegexp !== undefined) { + for (let i = 0; i < ColoredTabs.state.hostsRegexp.length; i++) { + if(host.match(ColoredTabs.state.hostsRegexp[i])) { + tabClass = 'coloredTabsHostRegexp' + i; + break; + } } -// console.log(css); - + } + if(tabClass === null) { + tabClass = 'coloredTabsHue' + Math.round((ColoredTabs.hash(host) % ColoredTabs.settings.colors) * (360 / ColoredTabs.settings.colors)); + } + + // console.log("colorizeTab tabId " + tabId + ", host " + host + " hash " + ColoredTabs.hash(host) + " step " + (ColoredTabs.hash(host) % ColoredTabs.settings.colors) + " tabClass " + tabClass); + + if(ColoredTabs.state.tabsClass[tabId] != tabClass) { browser.runtime.sendMessage(TST_ID, { - type: "register-self", - style: css, + type: 'add-tab-state', + tabs: [tabId], + state: tabClass, }); - - browser.tabs.query({}).then(function(tabs){ - for (let tab of tabs) { - let host = new URL(tab.url); - host = host.hostname.toString(); -// console.log('colorize tab id ' + tab.id + ' host ' + host); - ColoredTabs.colorizeTab(tab.id, host); - } - }, onError); - }, - - colorizeTab(tabId, host) { - let hue = Math.round((ColoredTabs.hash(host) % ColoredTabs.settings.colors) * (360 / ColoredTabs.settings.colors)); -// console.log("colorizeTab tabId " + tabId + ", host " + host + " hash " + ColoredTabs.hash(host) + " step " + (ColoredTabs.hash(host) % ColoredTabs.settings.colors) + " hue " + hue); - - if(ColoredTabs.state.tabHue[tabId] != hue) { + if(typeof ColoredTabs.state.tabsClass[tabId] !== undefined) { browser.runtime.sendMessage(TST_ID, { - type: 'add-tab-state', + type: 'remove-tab-state', tabs: [tabId], - state: 'coloredTabsHue' + hue, + state: ColoredTabs.state.tabsClass[tabId], }); - if(typeof ColoredTabs.state.tabHue[tabId] !== 'undefined') { - browser.runtime.sendMessage(TST_ID, { - type: 'remove-tab-state', - tabs: [tabId], - state: 'coloredTabsHue' + ColoredTabs.state.tabHue[tabId], - }); - } - ColoredTabs.state.tabHue[tabId] = hue; } - }, - - hash(s) { - for(var i=0, h=1; i>>17)>>>0; - }, - - state: { - 'tabHost': [], - 'tabHue': [], - 'inited': false, - }, + ColoredTabs.state.tabsClass[tabId] = tabClass; + } + }, + + hash(s) { + for(var i=0, h=1; i>>17)>>>0; + }, + } function onError(error) { @@ -148,6 +206,7 @@ async function handleTSTMessage(message, sender) { switch (message.type) { case "ready": registerToTST(); + ColoredTabs.init(); case "sidebar-show": case "permissions-changed": if(ColoredTabs.state.inited != true) { @@ -161,5 +220,6 @@ async function handleTSTMessage(message, sender) { } } + registerToTST(); browser.runtime.onMessageExternal.addListener(handleTSTMessage); diff --git a/manifest.json b/manifest.json index d17b238..1cab4fb 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "TST Colored Tabs", "short_name": "TSTColoredTabs", "description": "Colorize tabs in Tree Style Tab based on hostname.", - "version": "0.6", + "version": "0.7", "author": "Alexey Murz Korepov", "homepage_url": "https://github.com/MurzNN/tst-colored-tabs", diff --git a/options.html b/options.html index 27c4fa5..cc1c06b 100644 --- a/options.html +++ b/options.html @@ -4,32 +4,65 @@ + +
-
-

Settings for all tabs

-
-
-
-
Number of color hues used, maximum is 360, recommended is 15.
-
- -

Active tab

-
-
-
- -

Hovered tab

-
-
- -
- - - +
+ +

Settings for all tabs

+
+
+
+
Number of color hues used, maximum is 360, recommended is 15.
+ +

Active tab

+
+
+
+ +

Hovered tab

+
+
+ +

Per host colors

+ + + + + + + + + + + + + +
HostRegexpColorDisabledAction
+
Remove "disabled" checkbox for enable per-host custom colors on needed hosts. + Use "Regexp" checkbox when you want to add wildcard for domains via regular expression line.
+ +
+ + + + +
+ diff --git a/options.js b/options.js index a211692..f4f2638 100644 --- a/options.js +++ b/options.js @@ -2,45 +2,103 @@ Store the currently selected settings using browser.storage.sync. */ -function storeSettings() { - browser.storage.sync.set({ +function settingsSave(e) { + + settingsCurrent = { saturation: document.querySelector("#saturation").value, lightness: document.querySelector("#lightness").value, colors: document.querySelector("#colors").value, - active: { - saturation: document.querySelector("#active-saturation").value, - lightness: document.querySelector("#active-lightness").value, - bold: document.querySelector("#active-bold").checked, - }, - hover: { - saturation: document.querySelector("#hover-saturation").value, - lightness: document.querySelector("#hover-lightness").value, - }, + + activeSaturate: document.querySelector("#active-saturate").value, + activeBrightness: document.querySelector("#active-brightness").value, + activeBold: document.querySelector("#active-bold").checked, + + hoverSaturate: document.querySelector("#hover-saturate").value, + hoverBrightness: document.querySelector("#hover-brightness").value, + }; + + settingsCurrent.hosts = []; + document.querySelectorAll("table.hosts tbody tr").forEach(function(hostsItem) { + settingsCurrent.hosts.push({ + host: hostsItem.querySelector("input[name='host[]']").value, + regexp: hostsItem.querySelector("input[name='regexp[]']").checked ? true : false, + color: hostsItem.querySelector("input[name='color[]']").value, + disabled: hostsItem.querySelector("input[name='disabled[]']").checked ? true : false, + }); + }); + browser.storage.sync.clear().then(function() { + browser.storage.sync.set(settingsCurrent).then(function() { + ColoredTabs.init(); + }, onError); }); - ColoredTabs.init(); + e.preventDefault(); } -function resetSettings() { +function settingsReset(e) { let settings = DEFAULT_SETTINGS; - updateUI(settings); - ColoredTabs.init(); + settingsFormFill(settings); + e.preventDefault(); } /* Update the options UI with the settings values retrieved from storage, or the default settings if the stored settings are empty. */ -function updateUI(storedSettings) { - document.querySelector("#saturation").value = storedSettings.saturation || ""; - document.querySelector("#lightness").value = storedSettings.lightness || ""; - document.querySelector("#colors").value = storedSettings.colors || ""; - - document.querySelector("#active-saturation").value = storedSettings.active.saturation || ""; - document.querySelector("#active-lightness").value = storedSettings.active.lightness || ""; - document.querySelector("#active-bold").checked = storedSettings.active.bold || ""; - - document.querySelector("#hover-saturation").value = storedSettings.hover.saturation || ""; - document.querySelector("#hover-lightness").value = storedSettings.hover.lightness || ""; +function settingsFormFill(settings) { + + // console.log(settings); + + document.querySelector("#saturation").value = settings.saturation || ""; + document.querySelector("#lightness").value = settings.lightness || ""; + document.querySelector("#colors").value = settings.colors || ""; + + document.querySelector("#active-saturate").value = settings.activeSaturate || ""; + document.querySelector("#active-brightness").value = settings.activeBrightness || ""; + document.querySelector("#active-bold").checked = settings.activeBold || ""; + + document.querySelector("#hover-saturate").value = settings.hoverSaturate || ""; + document.querySelector("#hover-brightness").value = settings.hoverBrightness || ""; + + + document.querySelector("table.hosts tbody").innerHTML = ''; + + if(settings.hosts) { + settings.hosts.forEach(function(hostsItem) { + hostsAdd(null, hostsItem); + }); + } else { + hostsAdd(null, {}); + } + + // const hostsTemplate = document.querySelector("template.host-item table tbody tr"); +} + +function hostsAdd(hostsItemPrev = null, data = {}) { + const hostsTemplate = Sanitizer.createSafeHTML(document.querySelector("template").innerHTML); + // console.log(hostsTemplate); + +// hostsItem.className = "row"; +// box.setAttribute("draggable", true); + let hostsItem = document.createElement('tr'); + hostsItem.insertAdjacentHTML('beforeend', Sanitizer.unwrapSafeHTML(hostsTemplate)); + hostsItem.querySelector("input[name='host[]']").value = data.host || ''; + hostsItem.querySelector("input[name='regexp[]']").checked = data.regexp || false; + hostsItem.querySelector("input[name='color[]']").value = data.color || '#00ff00'; + hostsItem.querySelector("input[name='disabled[]']").checked = data.disabled ? true : false; + + // hostsItem = hostsTemplate.cloneNode(true); + // console.log(hostsItem) + // // hostsItem = document.createElement('tr'); + // // hostsItem.innerHTML = 'xxxx2 sda'; + // document.querySelector("table.hosts tbody").appendChild(hostsTemplate); + document.querySelector("table.hosts tbody").appendChild(hostsItem); + + if (hostsItemPrev) { + hostsItemPrev.parentNode.insertBefore(hostsItem, hostsItemPrev.nextSibling); + } + else { + document.querySelector("table.hosts tbody").appendChild(hostsItem); + } } function onError(e) { @@ -50,11 +108,30 @@ function onError(e) { /* On opening the options page, fetch stored settings and update the UI with them. */ -const gettingStoredSettings = browser.storage.sync.get(DEFAULT_SETTINGS); -gettingStoredSettings.then(updateUI, onError); +// settingsReset(); +// let settingsStored = ColoredTabs.settingsLoad(); +// settingsFormFill(settingsStored); + +browser.storage.sync.get().then(function(settingsSaved) { + settings = {} + Object.assign(settings, DEFAULT_SETTINGS, settingsSaved); + settingsFormFill(settings); +}); /* On submit, save the currently selected settings. */ -document.querySelector("#save-button").addEventListener("click", storeSettings); -document.querySelector("#reset-button").addEventListener("click", resetSettings); +document.querySelector("#save-button").addEventListener("click", settingsSave); + +document.querySelector("#reset-button").addEventListener("click", settingsReset); + +document.querySelector("table.hosts").addEventListener("click", (e) => { + if (e.target.className == "browser-style add-button") { + hostsAdd(e.target.parentNode.parentNode); + } else if (e.target.className == "browser-style del-button") { + e.target.parentNode.parentNode.remove(); + if (document.querySelectorAll("table.hosts tbody tr").length == 0) { + hostsAdd(); + } + } +}); diff --git a/sanitizer.js b/sanitizer.js new file mode 100644 index 0000000..8ee57b5 --- /dev/null +++ b/sanitizer.js @@ -0,0 +1,87 @@ +/* globals define, module */ + +/** + * A simple library to help you escape HTML using template strings. + * + * It's the counterpart to our eslint "no-unsafe-innerhtml" plugin that helps us + * avoid unsafe coding practices. + * A full write-up of the Hows and Whys are documented + * for developers at + * https://developer.mozilla.org/en-US/Firefox_OS/Security/Security_Automation + * with additional background information and design docs at + * https://wiki.mozilla.org/User:Fbraun/Gaia/SafeinnerHTMLRoadmap + * + */ +(function (root, factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + define(factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.Sanitizer = factory(); + } +}(this, function () { + 'use strict'; + + var Sanitizer = { + _entity: /[&<>"'/]/g, + + _entities: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }, + + getEntity: function (s) { + return Sanitizer._entities[s]; + }, + + /** + * Escapes HTML for all values in a tagged template string. + */ + escapeHTML: function (strings, ...values) { + var result = ''; + + for (var i = 0; i < strings.length; i++) { + result += strings[i]; + if (i < values.length) { + result += String(values[i]).replace(Sanitizer._entity, + Sanitizer.getEntity); + } + } + + return result; + }, + /** + * Escapes HTML and returns a wrapped object to be used during DOM insertion + */ + createSafeHTML: function (strings, ...values) { + var escaped = Sanitizer.escapeHTML(strings, ...values); + return { + __html: escaped, + toString: function () { + return '[object WrappedHTMLObject]'; + }, + info: 'This is a wrapped HTML object. See https://developer.mozilla.or'+ + 'g/en-US/Firefox_OS/Security/Security_Automation for more.' + }; + }, + /** + * Unwrap safe HTML created by createSafeHTML or a custom replacement that + * underwent security review. + */ + unwrapSafeHTML: function (...htmlObjects) { + var markupList = htmlObjects.map(function(obj) { + return obj.__html; + }); + return markupList.join(''); + } + }; + + return Sanitizer; + +}));