I am working on developing an extension (debugger) for a Javascript library. Initial results are fine and I have come across one bug (not sure if its a bug). Problem is "When a code is running multiple tabs open (for each, dev-tools is also open) and The dev-tools receive messages from all the open tabs, which should not be the case. How to prevent it from happening? any suggestions would be great help. Let me know if I need to put code related to any other files.
//Part of Manifest.json
{
"manifest_version": 2,
"name": "debugger",
"version": "1.0",
"description": "some",
"author": "some",
"devtools_page": "devtools.html",
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"tabs",
"storage",
"<all_urls>"
]
}
// content-script.js
chrome.extension.sendMessage(message, function (message) {
console.log("message sent");
});
chrome.extension.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action == 'debug') {
console.log("Message: " + msg.content);
}
});
// Background.js
chrome.extension.onConnect.addListener(function (port) {
var extensionListener = function (message, sender, sendResponse) {
if (message.destination == "panel") {
port.postMessage(message);
// chrome.tabs.sendMessage(sender.tab.id, message, sendResponse);
}
});
// panel.js
var port = chrome.extension.connect({
name: "my-extension"
});
port.onMessage.addListener(function (message) {
// do something with message
});
In your case object sender from the background message listener contains property tab (with tab.id), so you can recognise which tab sent this message.
Then you can get current tab using:
chrome.tabs.query({active: true, currentWindow: true}, function(foundTabs){
//foundTabs[0].id //current tab id
});
And that means you can filter messages from the current tab only.
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 trying to use chrome extension to get some data from web of science. In one step, I want to create a new tab and wait until it loaded. So I add a webNavigation Listener after creating the tab. I found the listener works well
only for some websites. If I use the target url (web of science) as the code below, I won't get the alert window. But if I use the target "https://stackoverflow.com/questions/ask", it gives the alert successfully. Why this happens? Could anyone advice the reason to me? Really thankful to it.
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var activeTab = tabs[0];
tabId = activeTab.id;
chrome.tabs.sendMessage(tabId, {"message": "clicked_browser_action"});
});
});
var link = 'https://apps.webofknowledge.com/OneClickSearch.do?product=UA&search_mode=OneClickSearch&excludeEventConfig=ExcludeIfFromFullRecPage&SID=7ENVgUT3nRKp41VVlhe&field=AU&value=Leroux,%20E.'; // failed in this url
//var link = 'https://stackoverflow.com/questions/ask'; //success in this url
function listener1(){
chrome.webNavigation.onCompleted.removeListener(listener1);
chrome.tabs.sendMessage(tabId, {"message": "to content"});
alert('listener succeed');
}
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.joke == 'content initial'){
chrome.tabs.create({ url: link });
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var activeTab = tabs[0];
tabId = activeTab.id;
});
//alert(link);
chrome.webNavigation.onCompleted.addListener(listener1, {url: [{urlMatches : link}]});
}
}
)
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "clicked_browser_action" ) {
console.log('content initial');
chrome.runtime.sendMessage({joke: 'content initial'}, function(response) {
});
}
}
)
manifest.json
{
"manifest_version": 2,
"name": "citation",
"version": "1",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {},
"content_scripts": [{
"matches": ["<all_urls>"],
"run_at": "document_idle",
"js": ["content.js"]
}],
"permissions": [
"downloads",
"webNavigation",
"tabs",
"<all_urls>"
]
}
The main problem is that urlMatches is a regular expression in RE2 syntax as you can see in the documentation so various special symbols in the URL like ? are interpreted differently. Solution: use urlEquals or other literal string comparisons.
There are other problems:
The API is asynchronous so the tabs are created and queried later in the future in no predictable sequence. Solution: use the callback of create().
All tabs are reported in webNavigation listener, not just the active one, so theoretically there's a problem of two identical URLs being reported in different tabs. Also the API filtering parameter cannot handle URLs with #hash part Solution: remember the tab id you want to monitor in a variable and compare it in the listener, and explicitly strip #hash part in the filter.
The site may redirect the final URL of the page so it may not get reported due to your filter. Solution: specify only the host name in the filter.
The tab that sends you messages or performs navigation may be inactive. Solution: use the tab id in the listener's parameters.
chrome.browserAction.onClicked.addListener(tab => {
chrome.tabs.sendMessage(tab.id, {message: 'clicked_browser_action'});
});
var link = '...............';
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.joke === 'content initial') {
chrome.tabs.create({url: link}, tab => {
chrome.webNavigation.onCompleted.addListener(function onCompleted(info) {
if (info.tabId === tab.id && info.frameId === 0) {
chrome.webNavigation.onCompleted.removeListener(onCompleted);
chrome.tabs.sendMessage(tab.id, {message: 'to content'});
console.log('listener succeeded');
}
}, {
url: [{urlPrefix: new URL(link).origin + '/'}],
});
});
}
});
Notes:
Avoid declaring content_scripts in manifest.json for all URLs if you only need processing on demand. Use programmatic injection in such cases.
Instead of alert() use the proper debugging in devtools like breakpoints or console.log() of the background page (more info).
I'm working on a Google Chrome Extension that you're supposed to be able to turn on and off using the symbol in the extension toolbar. This toggle should be universal, meaning that no matter where you turn it on or off, the current state is preserved everywhere. Doesn't matter in what tab or window you were, the status is shared everywhere.
Let's just say for our example, it's supposed to write "I'm on!" in the console when it's turned on and you press the A key. If it's turned off and you press A, it will say "I'm off!".
manifest.json:
{
"name": "Test Extension",
"version": "1.0",
"manifest_version": 2,
"description": "Just a test for Stack Overflow.",
"browser_action": {
"default_icon": "images/icon.png"
},
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
}
background.js:
var extensionMode = true;
chrome.browserAction.onClicked.addListener(function(tab) {
extensionMode = !extensionMode;
if(extensionMode) {
chrome.browserAction.setIcon({
path : "images/icon.png"
});
} else {
chrome.browserAction.setIcon({
path : "images/icon_disabled.png"
});
}
let msg = {
extensionMode: extensionMode
}
chrome.tabs.sendMessage(tab.id, msg);
});
content.js:
var extensionMode = true;
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message, sender, sendResponse) {
extensionMode = message.extensionMode;
}
onkeydown = onkeyup = function(e){
if(extensionMode) {
if(event.keyCode == 65) { // A
console.log("I'm on!");
}
} else {
if(event.keyCode == 65) { // A
console.log("I'm off!");
}
}
}
The above code works when you stay in the tab you are, but not when you switch... the icon will stay disabled, but extensionMode actually reverses back to true.
What am I doing wrong here? Is this the wrong approach for what I'm trying to do?
chrome.tabs.sendMessage targets just one tab so you would need to get a list of all tabs using chrome.tabs.query and send the message to each one:
const ignoreRuntimeError = () => chrome.runtime.lastError;
chrome.tabs.query({}, tabs => {
tabs.forEach(tab => chrome.tabs.sendMessage(tab.id, msg, ignoreRuntimeError));
});
You would also need to query the state in content scripts on tabs navigated/opened later:
chrome.runtime.sendMessage('getState', state => extensionMode = state);
For which the background script should have a listener:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getState') {
sendResponse(extensionMode);
}
});
A more efficient approach for you to consider:
use a nonpersistent event page and store the state in chrome.storage.local
run the content scripts only when enabled using chrome.tabs.executeScript and register/unregister the DOM listeners when toggled
I want to write a chrome extension which records the current active tab URL every time a new site is loaded and send it to a server for further use. So far I have managed to write the following code:
manifest.json
{
"manifest_version": 2,
"name": "Currenturl",
"description": "Fetches current tab url.",
"version": "0.1",
"author": "Tarun Khare",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Just observing your current url."
},
"permissions": ["tabs", "activeTab"],
"background": {
"scripts": ["content.js"],
"persistent": false
}
}
content.js
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
var url = tabs[0].url;
console.log("hello: "+url);
});
I am using background scripts since chrome.tabs doesn't work in content scripts. But this extension is not printing anything in chrome console. What is the issue?
Rename content.js to background.js since this is a background script
Use chrome.tabs.onUpdated listener
Look at the correct console: Where to read console messages from background.js?
chrome.tabs.onUpdated.addListener((tabId, change, tab) => {
if (change.url) {
console.log(change.url);
}
});
It'll report the URL changes in all tabs.
You can also limit the processing to only the active tab by adding a check for tab.active property.
i try to write code based on the information you provide.
const tabUpdatelistenerFun = (tabid, changeInfo, tab) => {
const url = changeInfo.url;
if (!url || ['chrome://', 'about://'].some(p => url.startsWith(p))) return false;
const { index, active, highlighted, windowId } = tab;
if (!active) return false;
chrome.tabs.query({ index, highlighted, windowId, lastFocusedWindow: true }, () => {
console.log(url);
})
}
chrome.tabs.onUpdated.addListener(tabUpdatelistenerFun);
i think this is what you want.
You can count and extract the URL with full detail in background.js
here is the main code from below GihHub repository:
chrome.windows.getAll({ populate: true }, function (windows) {
windows.forEach(function (window) {
window.tabs.forEach(function (tab) {
//i++
collect all of the urls here, I will just log them instead
console.log("tab.ur[![enter image description here][1]][1]l aaaaakahari 2");
console.log(tab.url);
});
});
});
There is a GitHub repository of chrome Extension below here, you can find in the background.js there are methods to count and extract the open URL, and there is and even section that console it imminently when the user open or close any tab.
https://github.com/Farbod29/extract-and-find-the-new-tab-frome-the-browser-with-chrome-extention
I'm building a chrome extension which communicates with a nodejs server through websockets. The point of it is to track browsing history with content. It all seems to work, but occasionally (30% of the time) the callback in a function passed to onMessage.addListener doesn't fire correctly. Let me show you the code:
background.js
var socket = io('http://localhost:3000/');
var tabLoad = function (tab) {
socket.emit('page load', tab);
};
var tabUpdate = function (tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined && changeinfo.status == "complete") {
tab.user_agent = navigator.userAgent;
tab.description = '';
tab.content = '';
socket.emit('insert', tab);
}
};
socket.on('inserted', function(page){
socket.emit('event', 'Requesting page content\n');
//page = {tab: page, id: docs._id};
chrome.tabs.sendMessage(page.tab_id, {requested: "content", page: page}, function(data) {
socket.emit('content', data);
});
});
try {
chrome.tabs.onCreated.addListener(tabLoad);
chrome.tabs.onUpdated.addListener(tabUpdate);
} catch(e) {
alert('Error in background.js: ' + e.message);
}
content script - public.js
var messageHandler = function(request, sender, sendContent) {
if (request.requested == "content") {
var html = document.getElementsByTagName('html')[0].innerHTML;
var data = {
content: html,
page: request.page
};
sendContent(data);
return true;
}
};
chrome.extension.onMessage.addListener(messageHandler);
The problem is that sometimes data in sendContent is undefined, while sometimes it is alright. Any ideas how to debug this or what i'm doing wrong?
I've tried replacing document.getElementsByTagName('html')[0].innerHTML with a hardcoded 'test' string, but that didn't help.
Pages like youtube/wikipedia seem to never work, while facebook/google works.
Edit: The sendContent callback does fire 100% of the time it's just that the data passed to it is undefined.
Edit: Here's the manifest file
{
"manifest_version": 2,
"name": "Socket test",
"description": "sockets are cool",
"version": "1.0",
"permissions": [
"http://st-api.localhost/",
"http://localhost:3000/",
"tabs",
"background",
"history",
"idle",
"notifications"
],
"content_scripts": [{
"matches": ["*://*/"],
"js": ["public/public.js"]
//"run_at": "document_start"
}],
//"browser_action": {
// "default_icon": "logo.png",
// "default_popup": "index.html"
//},
"background": {
//"page" : "background.html",
"scripts": ["socket-io.js", "background.js"],
"persistent": true
}
}
First off, your understanding that sendContent is executed 100% of the time is wrong.
As established in the comments, the sendMessage callback also gets executed when there was an error; and this error is, in your case, "Receiving end does not exist"
The error lies in your manifest declaration of the content script. A match pattern "*://*/" will only match top-level pages on http and https URIs. I.e. http://example.com/ will match, while http://example.com/test will not.
The easiest fix is "*://*/*", but I would recommend the universal match pattern "<all_urls>".
With that fixed, there are still a couple of improvements to your code.
Replace chrome.extension.onMessage (which is deprecated) and use chrome.runtime.onMessage
Modify the sendMessage part to be more resilient, by checking for chrome.runtime.lastError. Despite the wide permission, Chrome still won't inject any content scripts into some pages (e.g. chrome:// pages, Chrome Web Store)
Make sure you use "run_at" : "document_start" in your content script, to make sure onUpdated with "complete" is not fired before your script is ready.