I am attempting to display an alarm that pops up in my browser from background.js in Manifest v3. However, using the code implementation that is described in the Manifest v3 page does not produce an alarm.
Manifest.js:
{
"name": "Focus-Bot",
"description": "A bot meant to help you focus",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
},
"options_page": "options.html"
}
Background.js:
chrome.runtime.onInstalled.addListener(() => {
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
This version of background.js returns the following error
TypeError: Error in invocation of scripting.executeScript(scripting.ScriptInjection injection, optional function callback): Error at parameter 'injection': Missing required property 'target'.
The example code of a working Chrome Extension (the green background button) uses chrome.tabs in a popup.js file to get a target and inject javascript, but when background.js runs the same code like this:
Background.js (tabs):
chrome.runtime.onInstalled.addListener(() => {
let [tab] = await chrome.tabs.query(queryOptions);
console.log(tab)
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
Background.js seems to crash with "Service worker registration failed", with no error logs.
How do I display an alarm for the current active page from background.js?
As the error message says you need to add target to executeScript's parameters. Always look up the exact usage of API methods in the documentation.
Your code uses await but the function isn't declared with async which is a syntax error that causes the service worker to fail the registration. Currently ManifestV3 is riddled with bugs so it doesn't even show the cause of the failure so you'll have to use try/catch manually.
try {
chrome.runtime.onInstalled.addListener(async () => {
const [tab] = await chrome.tabs.query(queryOptions);
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: showAlert,
});
});
} catch (e) {
console.error(e);
}
An arguably better/cleaner approach would be to use two files: the main code in bg.js and the try-catch wrapper in bg-loader.js that imports bg.js, see this example.
Note that the active tab may be un-injectable e.g. a default start page or a chrome:// page (settings, bookmarks, etc.) or a chrome-extension:// page. Instead you can open a small new window:
alert({html: 'Foo <b>bar</b><ul><li>bla<li>bla</ul>'})
.then(() => console.log('alert closed'));
async function alert({
html,
title = chrome.runtime.getManifest().name,
width = 300,
height = 150,
left,
top,
}) {
const w = left == null && top == null && await chrome.windows.getCurrent();
const w2 = await chrome.windows.create({
url: `data:text/html,<title>${title}</title>${html}`.replace(/#/g, '%23'),
type: 'popup',
left: left ?? Math.floor(w.left + (w.width - width) / 2),
top: top ?? Math.floor(w.top + (w.height - height) / 2),
height,
width,
});
return new Promise(resolve => {
chrome.windows.onRemoved.addListener(onRemoved, {windowTypes: ['popup']});
function onRemoved(id) {
if (id === w2.id) {
chrome.windows.onRemoved.removeListener(onRemoved);
resolve();
}
}
});
}
Related
I am attempting to display an alarm that pops up in my browser from background.js in Manifest v3. However, using the code implementation that is described in the Manifest v3 page does not produce an alarm.
Manifest.js:
{
"name": "Focus-Bot",
"description": "A bot meant to help you focus",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
},
"options_page": "options.html"
}
Background.js:
chrome.runtime.onInstalled.addListener(() => {
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
This version of background.js returns the following error
TypeError: Error in invocation of scripting.executeScript(scripting.ScriptInjection injection, optional function callback): Error at parameter 'injection': Missing required property 'target'.
The example code of a working Chrome Extension (the green background button) uses chrome.tabs in a popup.js file to get a target and inject javascript, but when background.js runs the same code like this:
Background.js (tabs):
chrome.runtime.onInstalled.addListener(() => {
let [tab] = await chrome.tabs.query(queryOptions);
console.log(tab)
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
Background.js seems to crash with "Service worker registration failed", with no error logs.
How do I display an alarm for the current active page from background.js?
As the error message says you need to add target to executeScript's parameters. Always look up the exact usage of API methods in the documentation.
Your code uses await but the function isn't declared with async which is a syntax error that causes the service worker to fail the registration. Currently ManifestV3 is riddled with bugs so it doesn't even show the cause of the failure so you'll have to use try/catch manually.
try {
chrome.runtime.onInstalled.addListener(async () => {
const [tab] = await chrome.tabs.query(queryOptions);
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: showAlert,
});
});
} catch (e) {
console.error(e);
}
An arguably better/cleaner approach would be to use two files: the main code in bg.js and the try-catch wrapper in bg-loader.js that imports bg.js, see this example.
Note that the active tab may be un-injectable e.g. a default start page or a chrome:// page (settings, bookmarks, etc.) or a chrome-extension:// page. Instead you can open a small new window:
alert({html: 'Foo <b>bar</b><ul><li>bla<li>bla</ul>'})
.then(() => console.log('alert closed'));
async function alert({
html,
title = chrome.runtime.getManifest().name,
width = 300,
height = 150,
left,
top,
}) {
const w = left == null && top == null && await chrome.windows.getCurrent();
const w2 = await chrome.windows.create({
url: `data:text/html,<title>${title}</title>${html}`.replace(/#/g, '%23'),
type: 'popup',
left: left ?? Math.floor(w.left + (w.width - width) / 2),
top: top ?? Math.floor(w.top + (w.height - height) / 2),
height,
width,
});
return new Promise(resolve => {
chrome.windows.onRemoved.addListener(onRemoved, {windowTypes: ['popup']});
function onRemoved(id) {
if (id === w2.id) {
chrome.windows.onRemoved.removeListener(onRemoved);
resolve();
}
}
});
}
So I was tinkering with a firefox extension and came across something I can't explain. This extension downloads images from a certain site when a browser action (button) is clicked. Can confirm that the rest of the extension works perfectly and the code below has proper access to the response object.
const downloading = browser.downloads.download({
filename:response.fileName + '.jpg',
url:response.src,
headers:[{name:"Content-Type", value:"image/jpeg"}],
saveAs:true,
conflictAction:'uniquify'
});
const onStart = (id) => {console.log('started: '+id)};
const onError = (error) => {console.log(error)};
downloading.then(onStart, onError);
So the saveAs dialog pops up (filename with file extension populated), I click save, and then it downloads. As soon as the file finishes downloading it disappears from the folder it was saved in. I have no idea how this is happening.
Is this something wrong with my code, Firefox, or maybe a OS security action? Any help would be greatly appreciated.
Extra Information:
Firefox - 95.0.2 (64-bit)
macOS - 11.4 (20F71)
I had the same issue. You have to put download in background, background.js.
Attached sample of Thunderbird addon creates new menu entry in the message list and save raw message to the file on click.
If you look to the manifest.json, "background.js" script is defined in the "background" section. The background.js script is automatically loaded when the add-on is enabled during Thunderbird start or after the add-on has been manually enabled or installed.
See: onClicked event from the browserAction (John Bieling)
manifest.json:
{
"description": "buttons",
"manifest_version": 2,
"name": "button",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"permissions": [
"menus","messagesRead","downloads"
],
"browser_action": {
"default_icon": {
"16": "icons/page-16.png",
"32": "icons/page-32.png"
}
}
}
background.js:
async function main() {
// create a new context menu entry in the message list
// the function defined in onclick will get passed a OnClickData obj
// https://thunderbird-webextensions.readthedocs.io/en/latest/menus.html#menus-onclickdata
await messenger.menus.create({
contexts: ["all"],
id: "edit_email_subject_entry",
onclick: (onClickData) => {
saveMsg(onClickData.selectedMessages?.messages);
},
title: "iktatEml"
});
messenger.browserAction.onClicked.addListener(async (tab) => {
let msgs = await messenger.messageDisplay.getDisplayedMessages(tab.id);
saveMsg(msgs);
})
}
async function saveMsg(MessageHeaders) {
if (MessageHeaders && MessageHeaders.length > 0) {
// get MessageHeader of first selected messages
// https://thunderbird-webextensions.readthedocs.io/en/latest/messages.html#messageheader
let MessageHeader = MessageHeaders[0];
let raw = await messenger.messages.getRaw(MessageHeader.id);
let blob = new Blob([raw], { type: "text;charset=utf-8" })
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/downloads
await browser.downloads.download({
'url': URL.createObjectURL(blob),
'filename': "xiktatx.eml",
'conflictAction': "overwrite",
'saveAs': false
});
} else {
console.log("No message selected");
}
}
main();
Do NOT delete my question. I have scoured the web, the documentations, I don't know why this is happening and deleting the question does NOT help. There no other similar questions to mine.
Here goes. Below is how I beleive it should happen. But even with all the async away promis syntax I place there, it doesn't happen. When the async function is called, no one waits for it to finish. Idk what else to do except daisychain everything under the async function which I am trying to avoid. Help please.
I have tried using tabs= await getcurrenttab(), but that causes an error saying it only works on async functions. Which the listener is not.
manifest.json
{
"manifest_version": 3,
"name": "DHL Helper",
"description": "This helper helps DHL's quality of life, improves mental health.",
"version": "1.0",
"action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"content_scripts": [{
"matches":["*://servicenow.*.com/*",
"http://*/*",
"https://*/*"],
"js": ["content_helper.js"]
}],
"permissions": [
"activeTab"
],
"host_permissions": [
"http://www.blogger.com/",
"*://*/*"
]
}
popup.js
document.addEventListener('DOMContentLoaded', function() {
console.log('got tabs id');
tab = getCurrentTab();
console.log('Active tab ' + tab.id);
});
async function getCurrentTab(){
console.log('trying to get tabs id');
let queryOptions = {active: true, currentWindow: true};
return await chrome.tabs.query(queryOptions)
.then((tabs) => {
console.log('Obtained tab ID');
console.log(tabs);
return tabs[0];
})
.catch((Error) => {
console.log('it failed');
console.error;
return;
})
}
document.addEventListener('DOMContentLoaded', async function() {
console.log('got tabs id');
tab = await getCurrentTab();
console.log('Active tab ' + tab.id);
});
Your approach was already correct, but if you want to use await in the function await, you also have to make the executing function asyncronous.
user mentions attempting to use =await getCurrentTab() but encounters error that await is only for async functions...
adding async to event listener function fixed the problem
change this:
document.addEventListener('DOMContentLoaded', function() {
to this:
document.addEventListener('DOMContentLoaded', async function() {
I have this function that runs when a certain button in my popup.html is clicked
popup.js
async function execute () {
console.log("Executing...")
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
let res;
try {
res = await chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ["/src/MainScript.js"],
}, (a) => {
console.log("Done callback?", a)
});
} catch (e) {
return;
}
console.log("Result of script?", res)
}
"Done callback?" and "Result of script?" are printed inmediately after I run execute() so it's not waiting for it's execution to finish but to load the file, I think 🤔
I want to execute the MainScript.js file and wait for it's execution to finish. That file looks something like this:
MainScript.js
(async function () {
function Main () {
this.run = async function () {
// ...
}
}
const main = new Main();
await main.run();
})()
What I want to achieve is, when MainScript.js finishes it's execution I want to change some styling and elements in my popup.html.
I was wondering how to actually wait for the execution to finish or instead send a message from MainScript.js to my popup.js somehow or any alternative way, I'm opened to everything
EDIT
I tried the approach of messaging since I cannot wait for the execution to finish in manifest v3 (ty wOxxOm):
At the end of MainScript.js I placed this
const main = new Main();
await main.run();
chrome.runtime.sendMessage(myExtensionId, {type: 'processDone', value: true});
And then in background.js I listen for that message
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
console.log("Hello background", request);
// Set loading to false in localStorage...
}
);
And the listener is never triggered BUT when I manually type the sendMessage through the console it works as expected, the service worker console prints "Hello background"
How can I send a message from the Content Script (MainScript.js) to the service worker background.js??
Just in case, this is my manifest.json
{
"name": "Extension name",
"description": "Build an Extension!",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": "/images/favicon-32x32.png",
"default_title": "Title"
},
"permissions": [
"activeTab",
"tabs",
"webNavigation",
"scripting",
"storage"
],
"externally_connectable": {
"ids": [
"*"
],
"matches": [
"https://*.google.com/*"
]
},
"host_permissions": [
"http://*/*", "https://*/*"
]
}
I have been trying to make a chrome extension that gives the meaning of the selected text using urban dictionary API. I have tried different approaches but unable to connect all the files for proper execution.
manifest.json
{
"manifest_version": 2,
"name": "Urban Dictionary",
"version": "0.1",
"description": "Dictionary based on Urban Dict.",
"browser_action": {
"default_popup": "popup.html"
},
"icons": {
"16": "images/images.jpg",
"32": "images/images.jpg",
"48": "images/images.jpg",
"128":"images/images.jpg"
},
"permissions": [
"tabs",
"activeTab"
]
}
popup.html
<!doctype html>
<html>
<head>
<title>meaning</title>
</head>
<body>
<h1>meaning</h1>
<button id="test"></button>
<script src="popup.js"></script>
<script src="getword.js"></script>
</body>
</html>
popup.js
chrome.tabs.executeScript(null, {file: "getword.js"},(results)=>{ console.log(results); } );
getword.js
var something;
var term = window.getSelection().toString()
fetch("https://mashape-community-urban-dictionary.p.rapidapi.com/define?term="+term, {
"method": "GET",
"headers": {
"x-rapidapi-key": "My_API_KEY",
"x-rapidapi-host": "mashape-community-urban-dictionary.p.rapidapi.com"
}
})
.then(response => response.json())
.then(result => {
console.log(result)
something=result.list[0].definition
}
)
.catch(err => {
console.error(err);
});
console.log(something)
document.getElementById("test").innerHTML = something;
When trying to manipulate HTML using getword.js. The result comes out to be undefined.
I would be highly obliged if anyone can help me in any way to refactor this code.
In chrome extensions you always define your background scripts in manifest file otherwise it wont work.
like this :
"background": {
"scripts": [
"back.js"
],
"persistent": true
},
Secondly Popup.js needs to be included in your pop.html like we normally do <script src="popup.js"></script>
and lastly there is another type of script that is called content script which also needs to be included in manifest to work at all.
like this:
"content_scripts": [
{
"js": ["jquery-3.5.0.min.js","content.js"]
}
],
According to your need you should probably study content scripts i think.
There are several problems:
Injected code can't make cross-origin network requests.
getword.js's purpose is to be injected as a content script so it runs in the web page and thus shouldn't be listed in popup.html as the popup is a separate extension page in a separate window not related to the web page.
The solution is straightforward:
get the selected text from the web page,
transfer it to the popup script,
make the network request and show the result.
manifest.json (MV2) should list the API site in permissions:
"manifest_version": 2,
"permissions": [
"activeTab",
"https://mashape-community-urban-dictionary.p.rapidapi.com/"
]
popup.html: remove getword.js from html and delete getword.js file.
popup.js:
const API_HOST = 'mashape-community-urban-dictionary.p.rapidapi.com';
const API_OPTS = {
headers: {
'x-rapidapi-key': 'My_API_KEY',
'x-rapidapi-host': API_HOST,
},
};
chrome.tabs.executeScript({
code: 'getSelection().toString()',
}, async pageData => {
try {
const term = pageData[0].trim();
if (!term) throw new Error('No selection!');
const apiUrl = `https://${API_HOST}/define?term=${encodeURIComponent(term)}`;
const apiRes = await (await fetch(apiUrl, API_OPTS)).json();
const def = apiRes.list[0].definition;
document.getElementById('test').textContent = def;
} catch (e) {
document.getElementById('test').textContent =
chrome.runtime.lastError ? 'Cannot access this page' : e.message;
}
});