4

I'm developing a Google Chrome Extension which changes its icon depending of your IP localization. I face issues with refresh after an extension's icon change.

The icon is actually changing thanks to this command from my background.js file.

chrome.browserAction.setIcon ( { path: 'france.png' } );

Unfortunately, the setIcon command seems to be asynchronous. Icon change actually appears a few seconds after the change in the code. Is there a way to force chrome to refresh icons ?

Many chrome extensions seem to be able to control this but I couldn't find out how they manage this.


Here are more details :

In order to understand more clearly, I removed all my javascript code from different files, except the setIcon line. Here is Manifest line which declares my javascriptfiles.

"background": { "scripts": [ "jquery.min.js", "popup.js", "background.js"] },

popup.js: Now empty background.js: Only these two following lines :

console.log ("I'm background script");
chrome.browserAction.setIcon({path: 'france.png'});

After extension reload with the extensions manager, I click on its icon. Chrome make the static html popup to appear and load background.jsfile. As a proof of it, the text immediately appears on the console window.

But I have to clic a few more times on the icon extension to see it changed by chrome.

I should do something wrong somewhere, but actually, as I removed everything, I have no clue where this delay could come from.

Ipunchu
  • 41
  • 4
  • That does not sound right to me. Especially the "few seconds" part. Are you sure the problem is not elsewhere? Please add more details about your code. – Xan Nov 05 '14 at 15:16
  • Please edit your question with new information instead of adding it in comments. – Xan Nov 05 '14 at 23:11
  • Done as requested. Thanks – Ipunchu Nov 05 '14 at 23:18
  • Still not convinced you're telling us everything. Why would it react to clicking the icon? You have that script included in the popup? Also, what is the resolution of `france.png`? – Xan Nov 05 '14 at 23:20
  • Also, I've seen a couple of reports here (with others unable to reproduce) that the popup takes a few seconds to open. Does yours render fast? – Xan Nov 05 '14 at 23:21
  • `48x48` and declared in the `manifest.json`. No script included in `popup.html`, and no link to script pages either. I noticed, it's after 4 popup show that the icon is changed. Text from `background.js` appears at first popup disappearance and Icon is changed at 4th one. – Ipunchu Nov 05 '14 at 23:47
  • Yes, after the fourth popup disappearance, the new Icon is shown immediately. I actually have no idea how these events would be connected. Thanks for your questions anyway, it helps to figure out what's happening here. – Ipunchu Nov 05 '14 at 23:56
  • Try using an icon with the native resolution (19x19) and see if that changes anything. – Xan Nov 05 '14 at 23:57

1 Answers1

0

Problem: Chrome temporarily resets the browserAction icon you've specified when the tab navigates, which is a bug.

Workaround: you'll have to update the icon explicitly in every tab using webNavigation events in the background script:

Your manifest.json should have webNavigation permission, of course.

Warning! Chrome doesn't cache the icon's imageData and re-calculates it every time setIcon is called, which takes ~10 milliseconds, so let's cache it ourselves. Note that this will also start the event page (or the service worker in ManifestV3), which is very wasteful (at least 50ms), so you may want to prolong its lifetime artificially.

const iconPath = 'france.png';
let iconData;

chrome.webNavigation.onCommitted.addListener(updateIcon);
chrome.webNavigation.onHistoryStateUpdated.addListener(updateIcon);
chrome.webNavigation.onBeforeNavigate.addListener(updateIcon);

(async () => {
  chrome.browserAction.setIcon({
    imageData: iconData = await loadImage(iconPath),
  });
})();

function updateIcon(details) {
  // only update the icon for main page, not iframe/frame
  if (details.frameId != 0) return;
  chrome.browserAction.setIcon({
    imageData: iconData,
    tabId: details.tabId,
  });
}

async function loadImage(url) {
  const img = await createImageBitmap(await (await fetch(url)).blob());
  const {width: w, height: h} = img;
  const canvas = new OffscreenCanvas(w, h);
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0, w, h);
  return ctx.getImageData(0, 0, w, h);
}
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • It's probably worth explaining the performance implications of this, and why this hackery worked. – EricLaw Jun 02 '23 at 00:01