Add: icon-util
This commit is contained in:
parent
510a886e14
commit
30e494eda9
|
@ -1,6 +1,6 @@
|
|||
/* global detectSloppyRegexps download prefs openURL FIREFOX CHROME VIVALDI
|
||||
openEditor debounce URLS ignoreChromeError queryTabs getTab
|
||||
usercss styleManager db msg navigatorUtil
|
||||
usercss styleManager db msg navigatorUtil iconUtil
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
@ -35,7 +35,10 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
|||
|
||||
detectSloppyRegexps,
|
||||
openEditor,
|
||||
updateIcon,
|
||||
|
||||
updateIconBadge(count) {
|
||||
return updateIconBadge(this.sender.tab.id, count);
|
||||
},
|
||||
|
||||
// exposed for stuff that requires followup sendMessage() like popup::openSettings
|
||||
// that would fail otherwise if another extension forced the tab to open
|
||||
|
@ -128,37 +131,20 @@ if (chrome.commands) {
|
|||
chrome.commands.onCommand.addListener(command => browserCommands[command]());
|
||||
}
|
||||
|
||||
if (!chrome.browserAction ||
|
||||
!['setIcon', 'setBadgeBackgroundColor', 'setBadgeText'].every(name => chrome.browserAction[name])) {
|
||||
window.updateIcon = () => {};
|
||||
}
|
||||
|
||||
const tabIcons = new Map();
|
||||
chrome.tabs.onRemoved.addListener(tabId => tabIcons.delete(tabId));
|
||||
chrome.tabs.onReplaced.addListener((added, removed) => tabIcons.delete(removed));
|
||||
|
||||
// *************************************************************************
|
||||
// set the default icon displayed after a tab is created until webNavigation kicks in
|
||||
prefs.subscribe(['iconset'], () =>
|
||||
updateIcon({
|
||||
tab: {id: undefined},
|
||||
styles: {},
|
||||
}));
|
||||
|
||||
navigatorUtil.onUrlChange(({url, tabId, frameId}) => {
|
||||
if (frameId === 0) {
|
||||
tabIcons.delete(tabId);
|
||||
updateIcon({tab: {id: tabId, url}});
|
||||
}
|
||||
});
|
||||
|
||||
prefs.subscribe([
|
||||
'show-badge',
|
||||
'disableAll',
|
||||
'badgeDisabled',
|
||||
'badgeNormal',
|
||||
], () => debounce(refreshIconBadgeColor));
|
||||
|
||||
prefs.subscribe([
|
||||
'show-badge',
|
||||
'iconset',
|
||||
], () => debounce(updateAllTabsIcon));
|
||||
], () => debounce(refreshAllIcons));
|
||||
|
||||
// *************************************************************************
|
||||
chrome.runtime.onInstalled.addListener(({reason}) => {
|
||||
|
@ -250,19 +236,28 @@ if (chrome.contextMenus) {
|
|||
createContextMenus(keys);
|
||||
}
|
||||
|
||||
// *************************************************************************
|
||||
// [re]inject content scripts
|
||||
window.addEventListener('storageReady', function _() {
|
||||
window.removeEventListener('storageReady', _);
|
||||
if (!FIREFOX) {
|
||||
reinjectContentScripts();
|
||||
}
|
||||
|
||||
updateIcon({
|
||||
tab: {id: undefined},
|
||||
styles: {},
|
||||
// FIXME: implement exposeIframes in apply.js
|
||||
|
||||
// register hotkeys
|
||||
if (FIREFOX && browser.commands && browser.commands.update) {
|
||||
const hotkeyPrefs = Object.keys(prefs.defaults).filter(k => k.startsWith('hotkey.'));
|
||||
prefs.subscribe(hotkeyPrefs, (name, value) => {
|
||||
try {
|
||||
name = name.split('.')[1];
|
||||
if (value.trim()) {
|
||||
browser.commands.update({name, shortcut: value});
|
||||
} else {
|
||||
browser.commands.reset(name);
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
// Firefox injects content script automatically
|
||||
if (FIREFOX) return;
|
||||
|
||||
function reinjectContentScripts() {
|
||||
const NTP = 'chrome://newtab/';
|
||||
const ALL_URLS = '<all_urls>';
|
||||
const contentScripts = chrome.runtime.getManifest().content_scripts;
|
||||
|
@ -309,23 +304,6 @@ window.addEventListener('storageReady', function _() {
|
|||
setTimeout(pingCS, 0, cs, tab));
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// FIXME: implement exposeIframes in apply.js
|
||||
|
||||
// register hotkeys
|
||||
if (FIREFOX && browser.commands && browser.commands.update) {
|
||||
const hotkeyPrefs = Object.keys(prefs.defaults).filter(k => k.startsWith('hotkey.'));
|
||||
prefs.subscribe(hotkeyPrefs, (name, value) => {
|
||||
try {
|
||||
name = name.split('.')[1];
|
||||
if (value.trim()) {
|
||||
browser.commands.update({name, shortcut: value});
|
||||
} else {
|
||||
browser.commands.reset(name);
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
function webNavUsercssInstallerFF(data) {
|
||||
|
@ -362,98 +340,55 @@ function webNavIframeHelperFF({tabId, frameId}) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
function updateIcon({tab, styles}) {
|
||||
if (tab.id < 0) {
|
||||
function updateIconBadge(tabId, count) {
|
||||
let tabIcon = tabIcons.get(tabId);
|
||||
if (!tabIcon) tabIcons.set(tabId, (tabIcon = {}));
|
||||
if (tabIcon.count === count) {
|
||||
return;
|
||||
}
|
||||
if (URLS.chromeProtectsNTP && tab.url === 'chrome://newtab/') {
|
||||
styles = {};
|
||||
tabIcon.count = count;
|
||||
iconUtil.setBadgeText({
|
||||
text: prefs.get('show-badge') && count ? String(count) : '',
|
||||
tabId
|
||||
});
|
||||
if (!count) {
|
||||
refreshIcon(tabId, tabIcon);
|
||||
}
|
||||
if (styles) {
|
||||
stylesReceived(styles);
|
||||
return;
|
||||
}
|
||||
styleManager.countStylesByUrl(tab.url, {enabled: true})
|
||||
.then(count => stylesReceived({length: count}));
|
||||
|
||||
function stylesReceived(styles) {
|
||||
function refreshIcon(tabId, icon) {
|
||||
const disableAll = prefs.get('disableAll');
|
||||
const postfix = disableAll ? 'x' : !styles.length ? 'w' : '';
|
||||
const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal');
|
||||
const text = prefs.get('show-badge') && styles.length ? String(styles.length) : '';
|
||||
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
|
||||
let tabIcon = tabIcons.get(tab.id);
|
||||
if (!tabIcon) tabIcons.set(tab.id, (tabIcon = {}));
|
||||
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
|
||||
const postfix = disableAll ? 'x' : !icon.count ? 'w' : '';
|
||||
const iconType = iconset + postfix;
|
||||
|
||||
if (tabIcon.iconType !== iconset + postfix) {
|
||||
tabIcon.iconType = iconset + postfix;
|
||||
const sizes = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
|
||||
const usePath = tabIcons.get('usePath');
|
||||
Promise.all(sizes.map(size => {
|
||||
const src = `/images/icon/${iconset}${size}${postfix}.png`;
|
||||
return usePath ? src : tabIcons.get(src) || loadIcon(src);
|
||||
})).then(data => {
|
||||
const imageKey = typeof data[0] === 'string' ? 'path' : 'imageData';
|
||||
const imageData = {};
|
||||
sizes.forEach((size, i) => (imageData[size] = data[i]));
|
||||
chrome.browserAction.setIcon({
|
||||
tabId: tab.id,
|
||||
[imageKey]: imageData,
|
||||
}, ignoreChromeError);
|
||||
});
|
||||
}
|
||||
if (tab.id === undefined) return;
|
||||
|
||||
let defaultIcon = tabIcons.get(undefined);
|
||||
if (!defaultIcon) tabIcons.set(undefined, (defaultIcon = {}));
|
||||
if (defaultIcon.color !== color) {
|
||||
defaultIcon.color = color;
|
||||
chrome.browserAction.setBadgeBackgroundColor({color});
|
||||
}
|
||||
|
||||
if (tabIcon.text === text) return;
|
||||
tabIcon.text = text;
|
||||
try {
|
||||
// Chrome supports the callback since 67.0.3381.0, see https://crbug.com/451320
|
||||
chrome.browserAction.setBadgeText({text, tabId: tab.id}, ignoreChromeError);
|
||||
} catch (e) {
|
||||
setTimeout(() => {
|
||||
getTab(tab.id).then(realTab => {
|
||||
// skip pre-rendered tabs
|
||||
if (realTab.index >= 0) {
|
||||
chrome.browserAction.setBadgeText({text, tabId: tab.id});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadIcon(src, resolve) {
|
||||
if (!resolve) return new Promise(resolve => loadIcon(src, resolve));
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
const w = canvas.width = img.width;
|
||||
const h = canvas.height = img.height;
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.drawImage(img, 0, 0, w, h);
|
||||
const data = ctx.getImageData(0, 0, w, h);
|
||||
// Firefox breaks Canvas when privacy.resistFingerprinting=true, https://bugzil.la/1412961
|
||||
let usePath = tabIcons.get('usePath');
|
||||
if (usePath === undefined) {
|
||||
usePath = data.data.every(b => b === 255);
|
||||
tabIcons.set('usePath', usePath);
|
||||
}
|
||||
if (usePath) {
|
||||
resolve(src);
|
||||
if (icon.iconType === iconType) {
|
||||
return;
|
||||
}
|
||||
tabIcons.set(src, data);
|
||||
resolve(data);
|
||||
};
|
||||
icon.iconType = iconset + postfix;
|
||||
const sizes = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
|
||||
iconUtil.setIcon({
|
||||
path: sizes.reduce(
|
||||
(obj, size) => {
|
||||
obj[size] = `/images/icon/${iconset}${size}${postfix}.png`;
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
),
|
||||
tabId
|
||||
});
|
||||
}
|
||||
|
||||
function refreshIconBadgeColor() {
|
||||
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
|
||||
iconUtil.setBadgeBackgroundColor({
|
||||
color
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAllIcons() {
|
||||
for (const [tabId, icon] of tabIcons) {
|
||||
refreshIcon(tabId, icon);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,12 +404,6 @@ function onRuntimeMessage(msg, sender) {
|
|||
return fn.apply(context, msg.args);
|
||||
}
|
||||
|
||||
function updateAllTabsIcon() {
|
||||
return queryTabs().then(tabs =>
|
||||
tabs.map(t => updateIcon({tab: t}))
|
||||
);
|
||||
}
|
||||
|
||||
function openEditor({id}) {
|
||||
let url = '/edit.html';
|
||||
if (id) {
|
||||
|
|
91
background/icon-util.js
Normal file
91
background/icon-util.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/* global ignoreChromeError */
|
||||
/* exported iconUtil */
|
||||
'use strict';
|
||||
|
||||
const iconUtil = (() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
// https://github.com/openstyles/stylus/issues/335
|
||||
let noCanvas;
|
||||
const imageDataCache = new Map();
|
||||
// test if canvas is usable
|
||||
const canvasReady = loadImage('/images/icon/16.png')
|
||||
.then(imageData => {
|
||||
noCanvas = imageData.data.every(b => b === 255);
|
||||
});
|
||||
|
||||
return extendNative({
|
||||
/*
|
||||
Cache imageData for paths
|
||||
*/
|
||||
setIcon,
|
||||
setBadgeText
|
||||
});
|
||||
|
||||
function loadImage(url) {
|
||||
let result = imageDataCache.get(url);
|
||||
if (!result) {
|
||||
result = new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = () => {
|
||||
const w = canvas.width = img.width;
|
||||
const h = canvas.height = img.height;
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.drawImage(img, 0, 0, w, h);
|
||||
resolve(ctx.getImageData(0, 0, w, h));
|
||||
};
|
||||
img.onerror = reject;
|
||||
});
|
||||
imageDataCache.set(url, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setIcon(data) {
|
||||
canvasReady.then(() => {
|
||||
if (noCanvas) {
|
||||
chrome.browserAction.setIcon(data, ignoreChromeError);
|
||||
return;
|
||||
}
|
||||
const pending = [];
|
||||
data.imageData = {};
|
||||
for (const [key, url] of Object.entries(data.path)) {
|
||||
pending.push(loadImage(url)
|
||||
.then(imageData => {
|
||||
data.imageData[key] = imageData;
|
||||
}));
|
||||
}
|
||||
Promise.all(pending).then(() => {
|
||||
delete data.path;
|
||||
chrome.browserAction.setIcon(data, ignoreChromeError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setBadgeText(data) {
|
||||
try {
|
||||
// Chrome supports the callback since 67.0.3381.0, see https://crbug.com/451320
|
||||
chrome.browserAction.setBadgeText(data, ignoreChromeError);
|
||||
} catch (e) {
|
||||
// FIXME: skip pre-rendered tabs?
|
||||
chrome.browserAction.setBadgeText(data);
|
||||
}
|
||||
}
|
||||
|
||||
function extendNative(target) {
|
||||
return new Proxy(target, {
|
||||
get: (target, prop) => {
|
||||
// FIXME: do we really need this?
|
||||
if (!chrome.browserAction ||
|
||||
!['setIcon', 'setBadgeBackgroundColor', 'setBadgeText'].every(name => chrome.browserAction[name])) {
|
||||
return () => {};
|
||||
}
|
||||
if (target[prop]) {
|
||||
return target[prop];
|
||||
}
|
||||
return chrome.browserAction[prop].bind(chrome.browserAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
|
@ -2,11 +2,9 @@
|
|||
/* global msg API prefs */
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
if (typeof window.applyOnMessage === 'function') {
|
||||
// some weird bug in new Chrome: the content script gets injected multiple times
|
||||
return;
|
||||
}
|
||||
// define a constant so it throws when redefined
|
||||
const APPLY = (() => {
|
||||
const CHROME = chrome.app ? parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]) : NaN;
|
||||
var ID_PREFIX = 'stylus-';
|
||||
var ROOT = document.documentElement;
|
||||
|
@ -42,7 +40,6 @@
|
|||
});
|
||||
}
|
||||
msg.onTab(applyOnMessage);
|
||||
window.applyOnMessage = applyOnMessage;
|
||||
|
||||
if (!isOwnPage) {
|
||||
window.dispatchEvent(new CustomEvent(chrome.runtime.id));
|
||||
|
@ -139,10 +136,6 @@
|
|||
}
|
||||
break;
|
||||
|
||||
case 'styleApply':
|
||||
applyStyles(request.styles);
|
||||
break;
|
||||
|
||||
case 'urlChanged':
|
||||
API.getSectionsByUrl(getMatchUrl(), {enabled: true})
|
||||
.then(buildSections)
|
||||
|
@ -187,6 +180,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
function updateCount() {
|
||||
if (window !== parent) {
|
||||
// we don't care about iframes
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
for (const id of styleElements.keys()) {
|
||||
if (!disabledElements.has(id)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
// we have to send the tabId so we can't use `sendBg` that is used by `API`
|
||||
msg.send({
|
||||
method: 'invokeAPI',
|
||||
name: 'updateIconBadge',
|
||||
args: [count]
|
||||
});
|
||||
}
|
||||
|
||||
function applyStyleState({id, enabled}) {
|
||||
const inCache = disabledElements.get(id) || styleElements.get(id);
|
||||
const inDoc = document.getElementById(ID_PREFIX + id);
|
||||
|
@ -197,7 +209,7 @@
|
|||
addStyleElement(inCache);
|
||||
disabledElements.delete(id);
|
||||
} else {
|
||||
API.getSectionsByUrl(getMatchUrl(), {id})
|
||||
return API.getSectionsByUrl(getMatchUrl(), {id})
|
||||
.then(buildSections)
|
||||
.then(applyStyles);
|
||||
}
|
||||
|
@ -207,6 +219,7 @@
|
|||
docRootObserver.evade(() => inDoc.remove());
|
||||
}
|
||||
}
|
||||
updateCount();
|
||||
}
|
||||
|
||||
function removeStyle({id, retire = false}) {
|
||||
|
@ -224,12 +237,18 @@
|
|||
docRootObserver.evade(() => el.remove());
|
||||
}
|
||||
}
|
||||
styleElements.delete(ID_PREFIX + id);
|
||||
disabledElements.delete(id);
|
||||
retiredStyleTimers.delete(id);
|
||||
if (styleElements.delete(ID_PREFIX + id)) {
|
||||
updateCount();
|
||||
}
|
||||
}
|
||||
|
||||
function applyStyles(styles) {
|
||||
if (!styles.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!document.documentElement) {
|
||||
new MutationObserver((mutations, observer) => {
|
||||
if (document.documentElement) {
|
||||
|
@ -240,16 +259,12 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const gotNewStyles = styles.length || styles.needTransitionPatch;
|
||||
if (gotNewStyles) {
|
||||
if (styles.length) {
|
||||
if (docRootObserver) {
|
||||
docRootObserver.stop();
|
||||
} else {
|
||||
initDocRootObserver();
|
||||
}
|
||||
}
|
||||
|
||||
if (gotNewStyles) {
|
||||
for (const section of styles) {
|
||||
applySections(section.id, section.code);
|
||||
}
|
||||
|
@ -275,6 +290,9 @@
|
|||
}
|
||||
|
||||
updateExposeIframes();
|
||||
if (styles.length) {
|
||||
updateCount();
|
||||
}
|
||||
}
|
||||
|
||||
function applySections(styleId, code) {
|
||||
|
|
|
@ -78,12 +78,6 @@ if (!IS_BG) {
|
|||
} else {
|
||||
if (VIVALDI) document.documentElement.classList.add('vivaldi');
|
||||
}
|
||||
// TODO: remove once our manifest's minimum_chrome_version is 50+
|
||||
// Chrome 49 doesn't report own extension pages in webNavigation apparently
|
||||
if (CHROME && CHROME < 2661) {
|
||||
getActiveTab().then(tab =>
|
||||
window.API.updateIcon({tab}));
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_BG) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user