I'm trying to write my first browser extension for Chrome/Vivaldi but for some reason I can't seem to get it to write to the console log to assist in debugging another issue, which may be related.
Its supposed trigger a notification when you highlight a game and use the content menu button but its like it doesnt even run and so console log were added but none of them seem to get trigger according to the console.
For this post I needed to remove the apiKey
background.js
console.log("Creating context menu item...");
chrome.contextMenus.create({
id: "isthereanydeal",
title: "Look up on IsThereAnyDeal",
contexts: ["selection"]
});
console.log("Context menu item created.");
chrome.contextMenus.onClicked.addListener(function(info, tab) {
console.log("Context menu clicked");
if (info.menuItemId === "isthereanydeal") {
const gameName = decodeURIComponent(info.selectionText.trim().replace(/\s/g, '%20'));
const apiKey = '~~enter api key here~~';
const url = `https://api.isthereanydeal.com/v01/game/prices/?key=${apiKey}&plains=${gameName}®ion=us`;
console.log(`URL: ${url}`);
fetch(url)
.then(response => response.json())
.then(data => {
console.log("Data received from Isthereanydeal");
const bestPrice = data.data[gameName].list[0].price_new;
const discount = data.data[gameName].list[0].price_cut;
const notificationOptions = {
type: "basic",
title: "Best Price on Isthereanydeal",
message: `Game: ${gameName}\nBest Price: $${bestPrice}\nDiscount: ${discount}% off`
};
chrome.notifications.create(notificationOptions);
})
.catch(error => console.log(error));
}
});
manifest.json
{
"name": "Isthereanydeal Lookup",
"version": "1.0",
"description": "Lookup video game names on Isthereanydeal.com",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": [
"contextMenus",
"tabs"
],
"host_permissions": [
"https://api.isthereanydeal.com/*"
]
}
Does anyone have suggestions on why I cant even write to the console with it?
Related
I'm implementing a small extension for Copy as cURL feature (as done by the Network tab of DevTools) and I would like to use Manifest v3. According to the documentation and to the contribution of the community, Service Worker at a certain time stops to live so some variables cannot retrieve the needed information from the active tab.
For managing this, I'm using chrome.storage.local.set and .get functions in order to keep the needed information also after the Service Worker stops to live. When I run the extension test, I don't receive any error, but, despite I retrieve the stored variables by the chrome.storage API, sometimes I continue to not retrieve the values anymore also when the Service Worker should be alive. For example:
when I connect to a website, I can retrieve and copy the correct data also in 1 min, then, if I continue to Copy (without refreshing the page), I don't get the parameters (i.e., GET headers).
sometimes, if I open a new tab, insert an address and quickly press Copy as cURL, of my extension, headers are not copied, and I need to refresh the page (not by clicking refresh button of browser but click on URL then ENTER) for getting them.
Maybe the issue is not related to the Time-to-live of the Service Worker because I can keep a page opened for a lot of minutes and it gives me the right parameters. I don't know where my approach is failing. The code of this small implementation is the following:
background.js
"use strict";
/*
Called when the item has been created, or when creation failed due to an error.
We'll just log success/failure here.
*/
function onCreated() {
if (chrome.runtime.lastError) {
console.log(`Error: ${chrome.runtime.lastError}`);
} else {
console.log("Item created successfully");
}
}
/*
Called when the item has been removed.
We'll just log success here.
*/
function onRemoved() {
console.log("Item removed successfully");
}
/*
Called when there was an error.
We'll just log the error here.
*/
function onError(error) {
console.log(`Error: ${error}`);
}
/*
Create all the context menu items.
*/
chrome.contextMenus.create({
id: "tools-copy",
//title: chrome.i18n.getMessage("menuItemToolsCopy"),
title: "Copy",
contexts: ["all"],
}, onCreated);
chrome.contextMenus.create({
id: "tools-copy-curl",
parentId: "tools-copy",
//title: chrome.i18n.getMessage("menuItemToolsCopyAsFFUF"),
title: "Copy as cURL",
contexts: ["all"],
}, onCreated);
const tabData = {};
const getProp = (obj, key) => (obj[key] || (obj[key] = {}));
const encodeBody = body => {
var data = '';
// Read key
for (var key in body.formData) { //body is a JSON object
data += `${key}=${body.formData[key]}&`;
}
data = data.replace(/.$/,"");
var body_data = `'${data}'`;
return body_data;
}
const FILTER = {
types: ['main_frame', 'sub_frame'],
urls: ['<all_urls>'],
};
const TOOLS = {
CURL: 'tools-copy-curl',
};
chrome.webRequest.onBeforeRequest.addListener(e => {
getProp(getProp(tabData, e.tabId), e.frameId).body = e.requestBody;
chrome.storage.local.set({tabData: tabData}, function() {
console.log('HTTP request saved');
});
}, FILTER, ['requestBody']);
chrome.webRequest.onBeforeSendHeaders.addListener(e => {
getProp(getProp(tabData, e.tabId), e.frameId).headers = e.requestHeaders;
chrome.storage.local.set({tabData: tabData}, function() {
console.log('HTTP request saved');
});
}, FILTER, ['requestHeaders']);
chrome.tabs.onRemoved.addListener(tabId => delete tabData[tabId]);
chrome.tabs.onReplaced.addListener((addId, delId) => delete tabData[delId]);
chrome.contextMenus.onClicked.addListener((info, tab) => {
chrome.storage.local.get(["tabData"], function(items) {
const data = items.tabData[tab.id]?.[info.frameId || 0] || {};
if (info.menuItemId === TOOLS.CURL) {
var txt_clip = `curl -u '${info.frameUrl || tab.url}'` +
(data.headers?.map(h => ` -H '${h.name}: ${h.value}'`).join('') || '') +
(data.body? ' --data_raw ' + encodeBody(data.body) : '');
}
chrome.tabs.sendMessage(tab.id,
{
message: "copyText",
textToCopy: txt_clip
}, function(response) {})
});
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.message === "copyText") {
navigator.clipboard.writeText(request.textToCopy);
sendResponse({status: true});
}
}
);
manifest.json
{
"manifest_version": 3,
"name": "CopyAsCURL",
"description": "Copy as cURL test example.",
"version": "1.0",
"default_locale": "en",
"background": {
"service_worker": "background.js"
},
"permissions": [
"contextMenus",
"activeTab",
"cookies",
"webRequest",
"tabs",
"clipboardWrite",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"icons": {
"16": "icons/menu-16.png",
"32": "icons/menu-32.png",
"48": "icons/menu-48.png"
}
}
I want also to thank #wOxxOm for the support on similar topic.
I am struggling to get this simple f-ty working... My scenario is:
get current URL
modify it
navigate/redirect to it
execute custom JS code there
The most problems I have is with 4)
manifest.json
{
"name": "Hello, World!",
"description": "Navigate and execute custom js script",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"tabs",
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {}
}
background.js
function myCustomScript() {
alert('myCustomScript test ok!');
console.log('myCustomScript test ok!');
}
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.update({url: "https://example.com"}, myCustomScript);
});
The page got redirected but my js function is not executed! Do you know why and how to fix it?
P.S: this is my first time I am creating my chrome extension, maybe I am doing something wrong...
To execute custom code, use chrome.scripting API. For this scenario you'll need:
"scripting" added to "permissions", which you already have,
"https://example.com/" added to "host_permissions" in manifest.json.
Note that activeTab permission won't apply to the tab after it's navigated to a URL with a different origin because this permission only applies to the currently shown origin.
Due to a bug in Chrome, you need to wait for the URL to be set before executing the script.
The bug is fixed in Chrome 100.
chrome.action.onClicked.addListener(async tab => {
await chrome.tabs.update(tab.id, {url: "https://example.com"});
// Creating a tab needs the same workaround
// tab = await chrome.tabs.create({url: "https://example.com"});
await onTabUrlUpdated(tab.id);
const results = await chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content.js'],
});
// do something with results
});
function onTabUrlUpdated(tabId) {
return new Promise((resolve, reject) => {
const onUpdated = (id, info) => id === tabId && info.url && done(true);
const onRemoved = id => id === tabId && done(false);
chrome.tabs.onUpdated.addListener(onUpdated);
chrome.tabs.onRemoved.addListener(onRemoved);
function done(ok) {
chrome.tabs.onUpdated.removeListener(onUpdated);
chrome.tabs.onRemoved.removeListener(onRemoved);
(ok ? resolve : reject)();
}
});
}
P.S. alert can't be used in a service worker. Instead, you should look at devtools console of the background script or use chrome.notifications API.
I know there have been many questions related to this topic but none of them thus far have allowed me to figure this out in V3. When I run the following background.js, I only get undefined.
The goal of my extension, at least for this stage, is to scrape the active tab's DOM and extract the text content of all of the div elements.
My background.js page:
function getDOM() {
let htmlarr = [];
const pageDOM = document.getElementsByTagName('div');
for (i = 0; i < pageDOM.length; i++) {
htmlarr += pageDOM.innerHTML;
}
return Object.assign({}, htmlarr)
}
chrome.tabs.onActivated.addListener(activeInfo => {
let domRes = chrome.scripting.executeScript({
target: { tabId: activeInfo.tabId },
func: getDOM
})
console.log(domRes);
});
my manifest.json:
{
"name": "HTML Sourcer",
"description": "Extract HTML source code",
"version": "1.0",
"minimum_chrome_version": "10.0",
"manifest_version": 3,
"permissions": ["scripting", "tabs", "activeTab"],
"host_permissions": [
"*://*/*"
],
"background": {
"service_worker": "background.js"
}
}
Any help would be much appreciated. Thank you!
Problem 1
Per the documentation this API method returns a Promise when there's no callback parameter.
To get the value of the Promise, add await and mark the function as async:
chrome.tabs.onActivated.addListener(async info => {
let domRes = await chrome.scripting.executeScript({
target: {tabId: info.tabId},
func: getDOM,
}).catch(console.error);
if (!domRes) return;
console.log(domRes);
});
There's a catch because some tabs don't support injection e.g. the start tab or chrome:// tabs.
Problem 2
In JavaScript += doesn't work with arrays, but only with numbers/strings. Use htmlarr.push() instead. There's also no need to convert the array to an object via Object.assign.
Actually, let's rewrite getDOM using Array.from:
function getDOM() {
return Array.from(
document.getElementsByTagName('div'),
el => el.innerHTML
);
}
I am building an extension that records my screen and microphone audio as well.
Overview:
I have content.js which tries to get the access of navigator.mediaDevices.getUserMedia({... in the succession I send message to the background.js which again tries to access navigator.mediaDevices.getUserMedia({... over here I am recording the audio stream. In addition to this, I have popup.html which have a start button on clicking the button I am recording the screen. But in the full process I am getting the error DOMException: Failed due to shutdown.
Also, I am aware there have been questions on the above error (the famous one Chrome Extension - getUserMedia throws "NotAllowedError: Failed due to shutdown") but didn’t help me much.
content.js
navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true }
})
.then((e) => { chrome.runtime.sendMessage({ from: 'success' })})
.catch((e) => {console.log(e);});
Background.js
var recorder = null;
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message) {
if(message.from === 'success') {
navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true }
})
.then((e) => {
var chunks = [];
var options = {
mimeType: 'audio/webm',
};
recorder = new MediaRecorder(e, options);
recorder.ondataavailable = function (event) {
if (event.data.size > 0) {
chunks.push(event.data);
}
};
recorder.onstop = function () {
var superBuffer = new Blob(chunks, {
type: 'audio/webm',
});
var url = URL.createObjectURL(superBuffer);
};
recorder.start();
})
.catch((e) => {console.log(e);});
}
popup.js:
let btnStartTab = document.getElementById('start');
let btnStartTab1 = document.getElementById('stop');
btnStartTab.addEventListener('click', function (event) {
chrome.runtime.sendMessage({ from: 'start' });
});
btnStartTab1.addEventListener('click', function (event) {
chrome.runtime.sendMessage({ from: 'stop' });
});
manifest.json
{
"name": "My Recorer",
"version": "1.0",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": true
},
"browser_action": {
"default_icon": "test.png"
},
"commands": {
"run-foo": {
"suggested_key": {
"default": "Ctrl + Shift + Y",
"mac": "Command+Shift+Y"
},
"description": "Run \"foo\" on the current page."
},
"_execute_action": {
"suggested_key": {
"default": "Ctrl + Shift + Y",
"mac": "Command+Shift+Y"
}
}
},
"permissions": [
"activeTab",
"clipboardWrite",
"declarativeContent",
"storage",
"tabs",
"tabCapture",
"desktopCapture",
"alarms",
"activeTab",
"downloads",
"<all_urls>"
]
}
After deep searching and help from SO foundation found a way to reach the solution thought of sharing.
So, we need to understand that background doesn’t have control over DOM that's why we need to take help from contentscript.js and below architecture is important :
In my case, I am injecting contentscript.js from manifest.json but one can use executescript() to inject the script.
contentscript.js will create an iframe with display none with iframe.src = chrome.extension.getURL('audiosources.html'); above is important!!
Also, audiosources.html is under web_accessible_resources in manifest.json
The body of above HTML will have <script src="audiosources.js"></script> only.
audiosources.js is the oe which will help to get rid of the error here I am getting access to navigator.mediaDevices.getUserMedia(...) and all the mic devices. here on success it will send message to background.js.
Now, background.js will do its magic of tabCapture with navigator.mediaDevices.getUserMedia and here we need to apply logic for merging.
I have a very strange situation. I have an extension which copies stuff from the webpage based on the user's selection. But, when ever there are multiple frames its fails. For example on Gmail. If I select anything from Gmail and try to find the selection it will end up with an error:
Error: window.getSelection(...) is null
Here is my code (This is a working example. I didn't include the icon.):
manifest.json
{
"description": "Adds a solid red border to all webpages matching mozilla.org. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#borderify",
"manifest_version": 2,
"name": "Borderify",
"version": "1.0",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/borderify",
"icons": {
"48": "icons/border-48.png"
},
"background": {
"scripts": ["myaddone.js"]
},
"browser_action": {
"default_icon": "icons/trash.svg",
"default_title": "Forget it!"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["callHere.js"],
"all_frames": true
}]
}
callHere.js
function logger(msg) {
console.log("=============");
console.log(msg);
console.log("=============");
}
var getSelectedDataFromPage = function () {
logger("fooo");
selec = window.getSelection().toString().trim();
return selec;
}
browser.runtime.onMessage.addListener(request => {
var messageToCopy = request.greeting;
if (messageToCopy == "findCopy") {
var selectedText = getSelectedDataFromPage();
return Promise.resolve({
response: selectedText
});
}
logger(messageToCopy);
return Promise.resolve({
response: "Fail"
});
});
myaddone.js
function logger(msg) {
console.log(msg);
}
function onError(error) {
console.error(`Error: ${error}`);
}
function findSelectionTExt(tabs) {
for (let tab of tabs) {
browser.tabs.sendMessage(tab.id, {
greeting: "findCopy"
}).then(response => {
logger(response.response);
}).catch(onError);
}
}
browser.browserAction.onClicked.addListener(() => {
browser.tabs.query({
currentWindow: true,
active: true
}).then(findSelectionTExt).catch(onError);
});
It is using a message system to content script to copy stuff from selection. It works perfectly fine with Stack Overflow and other sites, but not sites which use more frames etc., like Gmail.
Loop Image, as you can see it able to grab the text first time and then its keep sending the message I think.
What I am really missing?
I did solved my issue using context menu item and it works very well with every where like iframes. I got a example from firefox repo. https://github.com/mdn/webextensions-examples/tree/master/context-menu-demo.