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) {}
}