fdbfb23547
* parserlib: fast section extraction, tweaks and speedups
* csslint: "simple-not" rule
* csslint: enable and fix "selector-newline" rule
* simplify db: resolve with result
* simplify download()
* remove noCode param as it wastes more time/memory on copying
* styleManager: switch style<->data names to reflect their actual contents
* inline method bodies to avoid indirection and enable better autocomplete/hint/jump support in IDE
* upgrade getEventKeyName to handle mouse clicks
* don't trust location.href as it hides text fragment
* getAllKeys is implemented since Chrome48, FF44
* allow recoverable css errors + async'ify usercss.js
* openManage: unminimize windows
* remove the obsolete Chrome pre-65 workaround
* fix temporal dead zone in apply.js
* ff bug workaround for simple editor window
* consistent window scrolling in scrollToEditor and jumpToPos
* rework waitForSelector and collapsible <details>
* blank paint frame workaround for new Chrome
* extract stuff from edit.js and load on demand
* simplify regexpTester::isShown
* move MozDocMapper to sections-util.js
* extract fitSelectBox()
* initialize router earlier
* use helpPopup.close()
* fix autofocus in popups, follow-up to 5bb1b5ef
* clone objects in prefs.get() + cosmetics
* reuse getAll result for INC
198 lines
5.7 KiB
JavaScript
198 lines
5.7 KiB
JavaScript
/* global API */// msg.js
|
|
/* global addAPI bgReady */// common.js
|
|
/* global prefs */
|
|
/* global tabMan */
|
|
/* global CHROME FIREFOX VIVALDI debounce ignoreChromeError */// toolbox.js
|
|
'use strict';
|
|
|
|
(() => {
|
|
const ICON_SIZES = FIREFOX || CHROME >= 55 && !VIVALDI ? [16, 32] : [19, 38];
|
|
const staleBadges = new Set();
|
|
const imageDataCache = new Map();
|
|
// https://github.com/openstyles/stylus/issues/335
|
|
let hasCanvas = loadImage(`/images/icon/${ICON_SIZES[0]}.png`)
|
|
.then(({data}) => (hasCanvas = data.some(b => b !== 255)));
|
|
|
|
addAPI(/** @namespace API */ {
|
|
/**
|
|
* @param {(number|string)[]} styleIds
|
|
* @param {boolean} [lazyBadge=false] preventing flicker during page load
|
|
*/
|
|
updateIconBadge(styleIds, {lazyBadge} = {}) {
|
|
// FIXME: in some cases, we only have to redraw the badge. is it worth a optimization?
|
|
const {frameId, tab: {id: tabId}} = this.sender;
|
|
const value = styleIds.length ? styleIds.map(Number) : undefined;
|
|
tabMan.set(tabId, 'styleIds', frameId, value);
|
|
debounce(refreshStaleBadges, frameId && lazyBadge ? 250 : 0);
|
|
staleBadges.add(tabId);
|
|
if (!frameId) refreshIcon(tabId, true);
|
|
},
|
|
});
|
|
|
|
chrome.webNavigation.onCommitted.addListener(({tabId, frameId}) => {
|
|
if (!frameId) tabMan.set(tabId, 'styleIds', undefined);
|
|
});
|
|
|
|
chrome.runtime.onConnect.addListener(port => {
|
|
if (port.name === 'iframe') {
|
|
port.onDisconnect.addListener(onPortDisconnected);
|
|
}
|
|
});
|
|
|
|
bgReady.all.then(() => {
|
|
prefs.subscribe([
|
|
'disableAll',
|
|
'badgeDisabled',
|
|
'badgeNormal',
|
|
], () => debounce(refreshIconBadgeColor), {runNow: true});
|
|
prefs.subscribe([
|
|
'show-badge',
|
|
], () => debounce(refreshAllIconsBadgeText), {runNow: true});
|
|
prefs.subscribe([
|
|
'disableAll',
|
|
'iconset',
|
|
], () => debounce(refreshAllIcons), {runNow: true});
|
|
});
|
|
|
|
function onPortDisconnected({sender}) {
|
|
if (tabMan.get(sender.tab.id, 'styleIds')) {
|
|
API.updateIconBadge.call({sender}, [], {lazyBadge: true});
|
|
}
|
|
}
|
|
|
|
function refreshIconBadgeText(tabId) {
|
|
const text = prefs.get('show-badge') ? `${getStyleCount(tabId)}` : '';
|
|
setBadgeText({tabId, text});
|
|
}
|
|
|
|
function getIconName(hasStyles = false) {
|
|
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
|
|
const postfix = prefs.get('disableAll') ? 'x' : !hasStyles ? 'w' : '';
|
|
return `${iconset}$SIZE$${postfix}`;
|
|
}
|
|
|
|
function refreshIcon(tabId, force = false) {
|
|
const oldIcon = tabMan.get(tabId, 'icon');
|
|
const newIcon = getIconName(tabMan.get(tabId, 'styleIds', 0));
|
|
// (changing the icon only for the main page, frameId = 0)
|
|
|
|
if (!force && oldIcon === newIcon) {
|
|
return;
|
|
}
|
|
tabMan.set(tabId, 'icon', newIcon);
|
|
setIcon({
|
|
path: getIconPath(newIcon),
|
|
tabId,
|
|
});
|
|
}
|
|
|
|
function getIconPath(icon) {
|
|
return ICON_SIZES.reduce(
|
|
(obj, size) => {
|
|
obj[size] = `/images/icon/${icon.replace('$SIZE$', size)}.png`;
|
|
return obj;
|
|
},
|
|
{}
|
|
);
|
|
}
|
|
|
|
/** @return {number | ''} */
|
|
function getStyleCount(tabId) {
|
|
const allIds = new Set();
|
|
const data = tabMan.get(tabId, 'styleIds') || {};
|
|
Object.values(data).forEach(frameIds => frameIds.forEach(id => allIds.add(id)));
|
|
return allIds.size || '';
|
|
}
|
|
|
|
// Caches imageData for icon paths
|
|
async function loadImage(url) {
|
|
const {OffscreenCanvas} = self.createImageBitmap && self || {};
|
|
const img = OffscreenCanvas
|
|
? await createImageBitmap(await (await fetch(url)).blob())
|
|
: await new Promise((resolve, reject) =>
|
|
Object.assign(new Image(), {
|
|
src: url,
|
|
onload: e => resolve(e.target),
|
|
onerror: reject,
|
|
}));
|
|
const {width: w, height: h} = img;
|
|
const canvas = OffscreenCanvas
|
|
? new OffscreenCanvas(w, h)
|
|
: Object.assign(document.createElement('canvas'), {width: w, height: h});
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.drawImage(img, 0, 0, w, h);
|
|
const result = ctx.getImageData(0, 0, w, h);
|
|
imageDataCache.set(url, result);
|
|
return result;
|
|
}
|
|
|
|
function refreshGlobalIcon() {
|
|
setIcon({
|
|
path: getIconPath(getIconName()),
|
|
});
|
|
}
|
|
|
|
function refreshIconBadgeColor() {
|
|
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
|
|
setBadgeBackgroundColor({
|
|
color,
|
|
});
|
|
}
|
|
|
|
function refreshAllIcons() {
|
|
for (const tabId of tabMan.list()) {
|
|
refreshIcon(tabId);
|
|
}
|
|
refreshGlobalIcon();
|
|
}
|
|
|
|
function refreshAllIconsBadgeText() {
|
|
for (const tabId of tabMan.list()) {
|
|
refreshIconBadgeText(tabId);
|
|
}
|
|
}
|
|
|
|
function refreshStaleBadges() {
|
|
for (const tabId of staleBadges) {
|
|
refreshIconBadgeText(tabId);
|
|
}
|
|
staleBadges.clear();
|
|
}
|
|
|
|
function safeCall(method, data) {
|
|
const {browserAction = {}} = chrome;
|
|
const fn = browserAction[method];
|
|
if (fn) {
|
|
try {
|
|
// Chrome supports the callback since 67.0.3381.0, see https://crbug.com/451320
|
|
fn.call(browserAction, data, ignoreChromeError);
|
|
} catch (e) {
|
|
// FIXME: skip pre-rendered tabs?
|
|
fn.call(browserAction, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @param {chrome.browserAction.TabIconDetails} data */
|
|
async function setIcon(data) {
|
|
if (hasCanvas === true || await hasCanvas) {
|
|
data.imageData = {};
|
|
for (const [key, url] of Object.entries(data.path)) {
|
|
data.imageData[key] = imageDataCache.get(url) || await loadImage(url);
|
|
}
|
|
delete data.path;
|
|
}
|
|
safeCall('setIcon', data);
|
|
}
|
|
|
|
/** @param {chrome.browserAction.BadgeTextDetails} data */
|
|
function setBadgeText(data) {
|
|
safeCall('setBadgeText', data);
|
|
}
|
|
|
|
/** @param {chrome.browserAction.BadgeBackgroundColorDetails} data */
|
|
function setBadgeBackgroundColor(data) {
|
|
safeCall('setBadgeBackgroundColor', data);
|
|
}
|
|
})();
|