bacground.js
chrome.tabs.create({url: "http://www.google.com", "active":true}, function(tab) {
console.log(tab.id);// 315
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
contentscript.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
});
logs:
Port: Could not establish connection. Receiving end does not exist.
How to fix it?
This error seems to be happening because the content script has not yet been injected into the page when your background script sends the message. Hence, the "receiving end does not exist."
I'm assuming (because I don't have more than 50 rep to be able to comment on your question and clarify this first, so correct me if I am wrong) that in your manifest.json file, you specify it in the following way:
"content_scripts": [{
"matches": ["*://xyz.com/*"],
"js": ["contentscript.js"]
}]
If this is indeed how you are injecting the content script, then you need to know that the content script only gets injected after the DOM has finished rendering. (Search for 'run_at' on the following link: http://developer.chrome.com/extensions/content_scripts.html) This means that when you send that message from the background script, the content script is still "loading."
The good news is that you can specify when the content script should be loaded by adding a third key-value pair to the content_scripts parameter in the manifest.json file, like so:
"content_scripts": [{
"matches": ["*://xyz.com/*"],
"js": ["contentscript.js"],
"run_at": "document_start"
}]
This tells the extension that you want to inject the contentscript.js before the DOM is constructed or any other script is run (i.e. as early as possible).
If the above technique still gives you the same error, that's an indication that even document_start is not early enough. In that case, let's consider another approach entirely. What you are attempting to do presently is to have the background script connect to the content script. Why not have the content script connect to the background script instead when it has successfully been injected into the page? The background page is always running so it is guaranteed to be able to receive the message from the content script without complaining about "the receiving end does not exist." Here is how you would do it:
In background.js:
chrome.runtime.onConnect.addListener(function(port) {
console.log("background: received connection request from
content script on port " + port);
port.onMessage.addListener(function(msg) {
console.log("background: received message '" + msg.action + "'");
switch (msg.action) {
case 'init':
console.log("background script received init request
from content script");
port.postMessage({action: msg.action});
break;
}
});
});
In contentscript.js:
var port_to_bg = chrome.runtime.connect({name: "content_to_bg"});
port_to_bg.postMessage({action: 'init'});
port_to_bg.onMessage.addListener(function(msg) {
switch (msg.action) {
case 'init':
console.log("connection established with background page!");
break;
}
}
Feel free to ask more questions for clarification! I'd be curious to learn if the first approach worked. If not, the second approach is a sure win.
Related
I am trying to write a Chrome extension that iterates through a list of web pages (the URLs of these pages can be hard coded into some JavaScript array in a .js), perform the following actions...
Take a screenshot
Take the resulting screenshot and either store it locally or send it (the image data) to an API
Move on to the next URL, rinse and repeat
I would like to do this without opening a new tab for each URL.
I tried all manner of things using a script that executes when the extension is called up. It appears that window.open("some URL", "_self") does not work. Using chrome.tab.create, as expected, opens a new tab each time. I was fine with this (for now), but then in the callback function, attempting to call chrome.tab.create again did not work. I then read about background scripts (service workers) and still no luck there. I was hoping in the callback function (newTab) => {...}, I could do something like this newTab.open("https://blah", some_callback) and cause the tab to be updated with the new URL and another callback (perhaps using chrome.tabs.update).
I am using manifest version 3
Thanks,
Matthew
Update: Looks like am lacking some contextual information. Seems that background JS (service workers) appear to be Node.js and, naturally, browser based JS is not. This means they would have two completely different namespaces, so would be curious as to how browser JS interacts with service worker JS (like any other server I assume)
Looks like I figured it out. First here's the manifest (version 3)...
{
"name": "Screenshots from List",
"version": "0.0.1",
"description": "Take screenshots based on a list of URLs",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": [
"desktopCapture",
"downloads",
"tabs",
"activeTab"
],
"host_permissions": ["<all_urls>"],
"action": {
"default_title": "Take Screenshots"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
]
}
The content_script.js file is empty, but appears to be needed so the extensions menu contains a clickable entry (given my current, and no doubt limited, understanding of Chrome extensions)
The background.js script pauses after the URL for the tab is set, since in my use case, JavaScript continues to render the tab after the chrome.tabs.onUpdated event has fired...
var m_Clicked = false;
var m_Index = 0;
var m_UrlList = ["URL1", "URL2"];
chrome.action.onClicked.addListener(function (tab) {
console.log("Clicked");
m_Clicked = true;
chrome.tabs.update({"url": m_UrlList[m_Index]});
});
chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {
console.log("Clicked : " + m_Clicked);
console.log(changeInfo.status);
if (m_Clicked) {
if (changeInfo.status == 'complete') {
await new Promise(r => setTimeout(r, 2000));
chrome.tabs.captureVisibleTab(null, {
format: "png",
quality: 100
}, function (data) {
chrome.downloads.download({
url: data,
filename: "pic_" + m_Index + ".png"
},
() => {
if (m_Index < m_UrlList.length) {
m_Index++;
chrome.tabs.update({"url": m_UrlList[m_Index]});
}
}
);
});
}
}
});
I'm messing around (trying to learn) how to make a chrome extension. Right now I'm just making super simple one where it counts instances of a certain word on a page. I have this part working.
What I want to do is send this information to the pop so I can use it to do some other stuff.
Here is what I have so far:
manifest.json
{
"manifest_version": 2,
"name": "WeedKiller",
"description": "Totally serious $100% legit extension",
"version": "0.1",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"permissions":[
"tabs",
"storage"
],
"browser_action": {
"default_icon": "icon.png",
"default_title": "WeedKiller",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
]
}
content.js
var elements = document.getElementsByTagName('*');
var count = 0;
function tokeCounter(){
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
for (var j = 0; j < element.childNodes.length; j++) {
var node = element.childNodes[j];
if (node.nodeType === 3) {
var text = node.nodeValue;
if(text == '420'){
count++;
}
var replacedText = text.replace(/420/, '+1');
if (replacedText !== text) {
element.replaceChild(document.createTextNode(replacedText), node);
}
}
}
}
}
tokeCounter();
So what I want to happen is to send the count variable to the popup so that I can use it there.
I have looked around and found that I need to do something with chrome.runtime.sendMessage.
I have it so I add this line to the end of content.js:
chrome.runtime.sendMessage(count);
and then in background.js:
chrome.runtime.onMessage.addListener(
function(response, sender, sendResponse){
temp = response;
}
);
I'm sort of stuck here as I'm not sure how to send this information to popup and use it.
As you have properly noticed, you can't send data directly to the popup when it's closed. So, you're sending data to the background page.
Then, when you open the popup, you want the data there. So, what are the options?
Please note: this answer will give bad advice first, and then improve on it. Since OP is learning, it's important to show the thought process and the roadbumps.
First solution that comes to mind is the following: ask the background page, using Messaging again. Early warning: this will not work or work poorly
First off, establish that there can be different types of messages. Modifying your current messaging code:
// content.js
chrome.runtime.sendMessage({type: "setCount", count: count});
// background.js
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse) {
switch(message.type) {
case "setCount":
temp = message.count;
break;
default:
console.error("Unrecognised message: ", message);
}
}
);
And now, you could in theory ask that in the popup:
// popup.js
chrome.runtime.sendMessage({type: "getCount"}, function(count) {
if(typeof count == "undefined") {
// That's kind of bad
} else {
// Use count
}
});
// background.js
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse) {
switch(message.type) {
case "setCount":
temp = message.count;
break;
case "getCount":
sendResponse(temp);
break;
default:
console.error("Unrecognised message: ", message);
}
}
);
Now, what are the problems with this?
What's the lifetime of temp? You have explicitly stated "persistent": false in your manifest. As a result, the background page can be unloaded at any time, wiping state such as temp.
You could fix it with "persistent": true, but keep reading.
Which tab's count do you expect to see? temp will have the last data written to it, which may very well not be the current tab.
You could fix it with keeping tabs (see what I did there?) on which tab sent the data, e.g. by using:
// background.js
/* ... */
case "setCount":
temp[sender.tab.id] = message.count;
break;
case "getCount":
sendResponse(temp[message.id]);
break;
// popup.js
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
// tabs is a single-element array after this filtering
chrome.runtime.sendMessage({type: "getCount", id: tabs[0].id}, function(count) {
/* ... */
});
});
It's a lot of work though, isn't it? This solution works fine though for non-tab-specific data, after fixing 1.
Next improvement to consider: do we need the background page to store the result for us? After all, chrome.storage is a thing; it's a persistent storage that all extension scripts (including content scripts) can access.
This cuts the background (and Messaging) out of the picture:
// content.js
chrome.storage.local.set({count: count});
// popup.js
chrome.storage.local.get("count", function(data) {
if(typeof data.count == "undefined") {
// That's kind of bad
} else {
// Use data.count
}
});
This looks cleaner, and completely bypasses problem 1 from above, but problem 2 gets trickier. You can't directly set/read something like count[id] in the storage, you'll need to read count out, modify it and write it back. It can get slow and messy.
Add to that that content scripts are not really aware of their tab ID; you'll need to message background just to learn it. Ugh. Not pretty. Again, this is a great solution for non-tab-specific data.
Then the next question to ask: why do we even need a central location to store the (tab-specific) result? The content script's lifetime is the page's lifetime. You can ask the content script directly at any point. Including from the popup.
Wait, wait, didn't you say at the very top you can't send data to the popup? Well, yes, kinda: when you don't know if it's there listening. But if the popup asks, then it must be ready to get a response, no?
So, let's reverse the content script logic. Instead of immediately sending the data, wait and listen for requests:
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse) {
switch(message.type) {
case "getCount":
sendResponse(count);
break;
default:
console.error("Unrecognised message: ", message);
}
}
);
Then, in the popup, we need to query the tab that contains the content script. It's a different messaging function, and we have to specify the tab ID.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {type: "getCount"}, function(count) {
/* ... */
});
});
Now that's much cleaner. Problem 2 is solved: we query the tab we want to hear from. Problem 1 seems to be solved: as long as a script counted what we need, it can answer.
Do note, as a final complication, that content scripts are not always injected when you expect them to: they only start to activate on navigation after the extension was (re)loaded. Here's an answer explaining that in great detail. It can be worked around if you wish, but for now just a code path for it:
function(count) {
if(typeof count == "undefined") {
// That's kind of bad
if(chrome.runtime.lastError) {
// We couldn't talk to the content script, probably it's not there
}
} else {
// Use count
}
}
I want to bring the tab the extension is running on to the front of my window as soon as the match is found, even if I am currently working in a second window.
So far I have this code in my content_script.js, but it doesn't seem to work.
The commented lines were my last failed tries.
The alert gave me -1, which seems to be quite the weird tab id.
if(output == 'found') {
sendResponse({findresult: "yes"});
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
//chrome.tabs.update(sender.tab.id, {selected: true});
//chrome.tabs.update(sender.tab.id, {active: true});
alert(sender.tab.id);
});
}
I've tried some things in the background.html too and all kinds of things already posted here, all with no luck.
What do I need to do to make this work?
EDIT
manifest.json script inclusion
"background": {
"scripts": ["background.js"]
},
"content_scripts": [ {
"all_frames": false,
"js": [ "jquery.js", "content_script.js" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_idle"
}
background.js (the alert won't even show up)
alert("here");
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
your_tab_Id = sender.tab.id);
});
chrome.tabs.update(your_tab_Id,{"active":true,"highlighted":true},function (tab){
console.log("Completed updating tab .." + JSON.stringify(tab));
});
content_script.js jQuery change background (sendResponse works, but if I activate the background changing line the script stops working)
if(found === true) {
//$('td:contains('+foundItem+')').css("background", "greenyellow");
sendResponse({findresult: "yes"});
}
jsFiddle I tested the jQuery code in
EDIT 2
extension download
You can not use chrome.tabs API() from content script
References
Content Scripts
tabs API
To get tab id put your code to background.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
your_tab_Id = sender.tab.id);
});
and add this to background.js page
chrome.tabs.update(your_tab_Id,{"active":true,"highlighted":true},function (tab){
console.log("Completed updating tab .." + JSON.stringify(tab));
});
Use only content script to send messages
chrome.extension.sendMessage("Awesome message");
Edit 1:
Your code do not work because of syntax error
Some point to consider:
Use onMessage instead of onRequest ( onRequest is deprecated in favour of onMessage)
All the used API's are asynchronous so make sure they are called synchronously.
Use sendMessage in content script as well ( sendRequest is deprecated in favour of sendMessage)
After applying above changes your code turns as shown here.
alert("here");
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
your_tab_Id = sender.tab.id;
chrome.tabs.update(your_tab_Id,{"active":true,"highlighted":true},function (tab){
console.log("Completed updating tab .." + JSON.stringify(tab));
});
});
Let me know if you need more information.
I work on a little extension on Google Chrome, I want to create a new tab, go on the url "sample"+i+".com", launch a content script on this url, update the current tab to "sample"+(i+1)+".com", and launch the same script. I looked the Q&A available on stackoverflow and I google it but I didn't found a solution who works. This is my actually code of background.js (it works), it creates two tabs (i=21 and i=22) and load my content script for each url, when I tried to do a chrome.tabs.update Chrome launchs directly a tab with i = 22 (and the script works only one time) :
function extraction(tab) {
for (var i =21; i<23;i++)
{
chrome.storage.sync.set({'extraction' : 1}, function() {}); //for my content script
chrome.tabs.create({url: "http://example.com/"+i+".html"}, function() {});
}
}
chrome.browserAction.onClicked.addListener(function(tab) {extraction(tab);});
If anyone can help me, the content script and manifest.json are not the problem. I want to make that 15000 times so I can't do otherwise.
Thank you.
I guess chrome.tabs.create is an async function so you need to create a separate function so that the i variable is copied each time:
try this:
var func = function(i)
{
chrome.storage.sync.set({'extraction' : 1}, function() {}); //for my content script
chrome.tabs.create({url: "http://example.com/"+i+".html"}, function() {});
}
function extraction(tab) {
for (var i =21; i<23;i++)
{
func(i);
}
}
chrome.browserAction.onClicked.addListener(function(tab) {extraction(tab);});
You need to make sure that the tab finished loading, and that your content script finished running, before updating the tab to the next url. One way to achieve that would be by sending a message from the content script to the background page. You can include the following in your content script:
chrome.extension.sendMessage("finished");
In your background script you can do the following:
var current = 21, end = 23;
chrome.extension.onMessage.addListener(
function(request, sender) {
if( request == "finished" && current <= end ) {
chrome.tabs.update( sender.tab.id,
{url: "http://example.com/"+current+".html"});
current++;
}
}
);
chrome.tabs.create({url: "http://example.com/"+current+".html"});
When executing a content script from a popup is there a way for that content script to return a value to the popup where the script was executed.
Referring to Google's Docs, use the following code:
contentscript.js
chrome.runtime.sendMessage({value: "hello"}, null);
popup.html
chrome.runtime.onMessage.addListener(
function myFunc(request, sender, sendResponse) {
doStuffWithValue(request.value);
chrome.runtime.onMessage.removeListener(myFunc); //if you want to stop listening after receiving the message
});