You can resolve the inline execution error by changing to scriptTag.innerHTML = scriptText;, no need to fetch the script. Manifest v3 seems to only allow injecting static scripts into the page context.scriptTag.src = chrome.runtime.getURL(filePath);
If you want to run dynamically sourced scripts I think this can be achieved by having the static (already trusted) script fetch a remote script then eval it.
UPDATE: example extension, with manifest v3, that injects a script that operates in the page context.
# myscript.js
window.variableInMainContext = "hi"
# manifest.json
{
"name": "example",
"version": "1.0",
"description": "example extension",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["https://*/*"],
"run_at": "document_start",
"js": ["inject.js"]
}
],
"web_accessible_resources": [
{
"resources": [ "myscript.js" ],
"matches": [ "https://*/*" ]
}
]
}
# inject.js
const nullthrows = (v) => {
if (v == null) throw new Error("it's a null");
return v;
}
function injectCode(src) {
const script = document.createElement('script');
// This is why it works!
script.src = src;
script.onload = function() {
console.log("script injected");
this.remove();
};
// This script runs before the <head> element is created,
// so we add the script to <html> instead.
nullthrows(document.head || document.documentElement).appendChild(script);
}
injectCode(chrome.runtime.getURL('/myscript.js'));
You may have to test it yourself to make sure there are no weird behaviors, but Google has some recommendations regarding this in their migration guide:
In Manifest V3, all of your extension's logic must be included in the extension. You can no longer load and execute a remotely hosted file. A number of alternative approaches are available, depending on your use case and the reason for remote hosting. Here are approaches to consider:
Configuration-driven features and logicIn this approach, your extension loads a remote configuration (for example a JSON file) at runtime and caches the configuration locally. The extension then uses this cached configuration to decide which features to enable.
Externalize logic with a remote serviceConsider migrating application logic from the extension to a remote web service that your extension can call. (Essentially a form of message passing.) This provides you the ability to keep code private and change the code on demand while avoiding the extra overhead of resubmitting to the Chrome Web Store.
Bundle third-party librariesIf you are using a popular framework like React or Bootstrap, you can download the minified files, add them to your project and import them locally.
For your case, option #3 seems like the easiest. Looking at the Google Picker API documentation it only uses two relatively small script files, and https://apis.google.com/js/api.js. You could try to bundle these in your Chrome extension and call the methods locally.https://accounts.google.com/gsi/client
I was able to make it work.
background.js
chrome.action.onClicked.addListener(async function (tab) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
const oldIframe = document.getElementById('cm-frame');
if (oldIframe) {
oldIframe.remove();
return;
}
const iframe = document.createElement('iframe');
iframe.setAttribute('id', 'cm-frame');
iframe.setAttribute(
'style',
'top: 10px;right: 10px;width: 400px;height: calc(100% - 20px);z-index: 2147483650;border: none; position:fixed;'
);
iframe.setAttribute('allow', '');
iframe.src = chrome.runtime.getURL('popup.html');
document.body.appendChild(iframe);
},
});
});
This my manifest.json file in case someone also needs it (I had to add stuff to and permissions to make it work and load correctly):web_accessible_resources
{
"manifest_version": 3,
"name": "Contact Mapping Extension",
"background": { "service_worker": "background.bundle.js" },
"action": {
"default_icon": "icon-34.png"
},
"icons": {
"128": "icon-128.png"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
"js": ["contentScript.bundle.js"],
"css": ["content.styles.css"]
}
],
"web_accessible_resources": [
{
"resources": ["content.styles.css", "icon-128.png", "icon-34.png", "popup.html"],
"matches": ["<all_urls>"]
}
],
"permissions": [
"tabs",
"activeTab",
"scripting"
]
}