I've been building my first Chrome extension. It has been fun however I've run into a problem I've yet to solve.
I get the error in the devtools extentions Developer mode:
Uncaught Error: Extension context invalidated.
I pretty sure that upon update of my extension and hard refresh on my test https:// page that the contentScript.js gets injected multiple times. The older injected scripts are still trying to the dom injections but the ports are not open so it throws the error?
I have tried the solutions in both of these threads as well going through google groups:
- Recursive "Extension context invalidated" error in console
- Extension context invalidated. Chrome Extension
I am using manifest v3.
Can you please suggest a way that I can update my code to protect against this error?
Here is my manifest:
{
"name": "Focuser",
"description": "Focuses on a page during a specified time frame",
"version": "0.1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": [
"storage",
"scripting",
"alarms"
],
"host_permissions": [
"<all_urls>"
]
}
My background script:
try {
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if ('undefined' !== typeof tab.url) {
// skip urls like "chrome://" to avoid extension error
if (tab.url?.startsWith("chrome://")) return undefined;
if(changeInfo.status == 'complete') {
chrome.scripting.executeScript({
files: ['contentScript.js'],
target: {tabId: tab.id}
})
}
}
})
} catch(e) {
console.log(e)
}
My conentScript (with classes removed)
/**
* Foozle Focuser - a class that injects a temporary DOM element
* into the site and then focus and click on it.
*
* The idea is that by maintaining the focus while running in the background a
* Chrome tab can stream radio stations and other media content. This could be used
* for anything a user wants to click.
*/
class FoozleFocuser {
/**
* private {string} target
* the dom element used as the target for the inject DOM
*/
#target = null;
/**
* private {string} injectElement
* the DOM element used as the target for the injected DOM
*/
#injectElement = '';
/**
* private {string} injectElementClassName
* the CSS class to be added to the injected DOM element
*/
#injectElementClassName = '';
/**
* Constructor - set the target and injectElementClass - optionally no params passed
* will set a target as document.body and the injectClassName will be foozle-focuser
* @param {string} target - the passed target DOM element that will be used as the target for the injected DOM
* @param {string} injectElementClassName - the CSS class to be added to the injected DOM element
* @return Void
*/
constructor(target=null, injectElementClassName = 'foozle-focuser') {
this.#target = this.#getTarget(target);
this.#injectElementClassName = injectElementClassName;
}
/**
* private SetInjectedElement
* Creates the injected DOM element with a class that will be used as a target for the focus
* @param {string} domElement
* @return Void
*/
#setInjectedElement(domElement = 'div') {
this.#injectElement = document.createElement(domElement);
this.#injectElement.className = this.#injectElementClassName;
}
/**
* private getTarget - queries the passed dom string. If null set the document.body as target
* @param {string || null} target - The dom target element where the injection will be done
* @return string - the target
*/
#getTarget(target=null) {
if ( target == null ) {
target = document.body;
} else {
target = document.querySelector(target);
if ( target == null || 'undefined' == typeof target ) {
target = document.body;
}
}
return target;
}
/**
* private focus - appends, focuses on, and clicks the injected DOM Element
* @return Void
*/
#focus() {
if (this.#target) {
this.#target.appendChild(this.#injectElement);
this.#target.focus();
this.#target.click();
let newDiv = document.querySelector('.' + this.#injectElementClassName)
newDiv.parentNode.removeChild(newDiv);
}
}
/**
* private run - runs the setup for the target and injected element and then focuses on the target
* @return Void
*/
run() {
this.#setInjectedElement();
this.#focus();
}
}
class FoozleTypes {
typeOf(value) {
var s = typeof value;
if (s === 'object') {
if (value) {
if (Object.prototype.toString.call(value) == '[object Array]') {
s = 'array';
}
} else {
s = 'null';
}
}
return s;
}
checkTypes(argList, typeList) {
for (var i = 0; i < typeList.length; i++) {
if (typeOf(argList[i]) !== typeList[i]) {
throw 'wrong type: expecting ' + typeList[i] + ", found " + typeOf(argList[i]);
}
}
}
}
class FoozleCounter {
getStoredCount(key='focuserCount') {
this.item = localStorage.getItem(key);
if ( null == this.item || 'undefined' == this.item ) {
localStorage.setItem(key, 0);
}
this.item = parseInt(this.item)
let count = this.item;
count++;
localStorage.setItem(key, count);
return count;
}
}
let FC = new FoozleCounter();
let FF = new FoozleFocuser();
if ('undefined' == typeof intervalId) {
var intervalId = setInterval(() => {
if (!chrome.runtime?.id) {
// The extension was reloaded and this script is orphaned
clearInterval(intervalId);
return;
}
FF.run();
// Get the updated count
let count = FC.getStoredCount();
// Store and report the count
// @TODO - change the console log to the popup page
chrome.storage.local.set({key: count}, function() {
console.log('Count is set to ' + count);
});
}, 45000);
}
if('undefined' == typeof init ) {
var init = () => {
if (!chrome.runtime?.id) {
// The extension was reloaded and this script is orphaned
clearInterval(init);
return;
}
FF.run();
console.log('count', FC.getStoredCount())
}
init();
}