I am using scalable SVG icons in my Chrome extension.
chrome.browserAction.setIcon({ tabId: tabId, path: '../icons/' + icon + '/scalable.svg' });
I want to switch icons based on some parameters, so I have visual feedback.
What I have realized is that when I am switching icons very quickly, Chrome is messing up and often I end up seeing the wrong icon. I added console.log
prints to code to ensure I am switching icons properly and I see that my code has no errors.
It looks like Chrome executes such change of icon requests asynchronously and conversion of SVG to pixels takes sometimes longer than usual. That leads to an execution in the wrong order.
So for example if I switch icons from A to B, then to C; then to D, … at the end I may see C, although the last request for change was to switch it to D.
Any ideas on how to fix this annoying problem?
Advertisement
Answer
- Chain the calls to the API using
Promise
- If you call
setIcon
often, create a cache of imageData yourself and use it instead ofpath
because the API re-reads the source icon each time and re-creates imageData.
Here’s a generic example, not tested:
const queue = {}; const cache = {}; // auto-clean the queue so it doesn't grow infinitely chrome.tabs.onRemoved.addListener(tabId => delete queue[tabId]); async function setIcon(tabId, icon) { const url = '../icons/' + icon + '/scalable.svg'; const imageData = await (cache[url] || (cache[url] = loadImageData(url))); queue[tabId] = (queue[tabId] || Promise.resolve()).then(() => new Promise(resolve => chrome.browserAction.setIcon({tabId, imageData}, resolve))); } function loadImageData(url) { return new Promise((resolve, reject) => { const data = {}; const img = new Image(); img.src = url; img.onload = () => { for (const size of [16, 32]) { const canvas = document.createElement('canvas'); document.documentElement.appendChild(canvas); canvas.width = canvas.height = size; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); data[size] = ctx.getImageData(0, 0, size, size); canvas.remove(); } resolve(data); }; img.onerror = reject; }); }