Chrome extension: How to remove orphaned script after chrom extension update

Since the orphaned content script can still receive DOM messages, send one from your new working content script to the ghosted content script via window, for example. Upon receiving the message you’ll unregister all listeners (and nullify any global variables) which will also make your old script eligible for automatic garbage collection.

content.js:

var orphanMessageId = chrome.runtime.id + 'orphanCheck';
window.dispatchEvent(new Event(orphanMessageId));
window.addEventListener(orphanMessageId, unregisterOrphan);

// register all listeners with named functions to preserve their object reference
chrome.runtime.onMessage.addListener(onMessage);
document.addEventListener('mousemove', onMouseMove);

// the popup script checks it to see if a usable instance of content script is running
window.running = true;

function unregisterOrphan() {
  if (chrome.i18n) {
    // someone tried to kick us out but we're not orphaned! 
    return;
  }
  window.removeEventListener(orphanMessageId, unregisterOrphan);
  document.removeEventListener('mousemove', onMouseMove);
  try {
    // 'try' is needed to avoid an exception being thrown in some cases 
    chrome.runtime.onMessage.removeListener(onMessage);
  } catch (e) {}
  return true;
});

function onMessage(msg, sender, sendResponse) {
  //...........
}

function onMouseMove(event) {
  // DOM events still fire in the orphaned content script after the extension
  // was disabled/removed and before it's re-enabled or re-installed
  if (unregisterOrphan()) { return }
  //...........
}

I’m assuming popup.html loads WebExtension browser API polyfill because it makes life much easier by allowing us to use async/await syntax instead of the mind-boggling callback hell.

<script src="https://stackoverflow.com/questions/57468219/browser-polyfill.min.js"></script>

popup.js should ensure a content script is injected before sending a message:

async function sendMessage(data) {
  const [tab] = await browser.tabs.query({active: true, currentWindow: true});
  if (await ensureContentScript(tab.id)) {
    return await browser.tabs.sendMessage(tab.id, data);
  }
}

async function ensureContentScript(tabId) {
  try {
    const [running] = await browser.tabs.executeScript(tabId, {
      code: 'window.running === true',
    });
    if (!running) {
      await browser.tabs.executeScript(tabId, {file: 'content.js'});
    }
    return true;
  } catch (e) {}
}

Leave a Comment