Chrome Extension: Unable to pass message to content.js - javascript

I am trying to send a message from background.js to content.js.
addListener in content.js is not working.
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab)
{
console.log(tab.url);
if(changeInfo.status=="complete"){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
if(tabs.length!=0){
chrome.tabs.sendMessage(tabs[0].id, {message: "sendurl"}, function(response) {
console.log(response.url);
});
}
});
console.log("load complete");
}
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "sendurl" ) {
var firstHref = $("a[href^='http']").eq(0).attr("href");
console.log(firstHref);
sendResponse({url: firstHref});
}
}
);
manifest.json
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["https://*/","http://*/"],
"js": ["jquery-2.1.4.js","enc-base64-min.js","hmac-sha256.js","content.js"]
}
],
After some tme, it gives error: TypeError: Cannot read property 'url' of undefined

Your "matches": ["https://*/","http://*/"], only specifies the domain wildcard which means that content scripts are injected for the main page only. The error message you see occurs because sendMessage timeouts after some time as there was no content script to receive the message.
As the match pattern documentation says:
http://*/* Matches any URL that uses the http scheme
The correct code would be "matches": ["https://*/*","http://*/*"],
P.S. Make use of the debugger, it's immensely helpful to catch such errors.

Related

chrome.tabs.sendMessage doesn't send message to content script

I'm trying to send some data from runtime (background.js) back to my content script. For some reason, it doesn't work for me. Here's my code:
manifest.json
"permissions": [
"activeTab",
"tabs",
"*://*.mysite.com/*"
],
"background": {
"scripts": ["background.js"],
"persistent": true
},
"content_scripts":[
{
"matches": ["*://*.mysite.com/*"],
"js": ["script.js"]
}
],
background.js
function sendToActiveTab(type, message) {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { type: type, message: message });
})
}
chrome.runtime.onMessage.addListener(
(request, sender, sendResponse) => {
if (request) {
//do some stuff here
sendToActiveTab('type', 'message');
}
}
)
script.js
document.write('hello');
var params = new URLSearchParams(window.location.search);
chrome.runtime.onMessage.addListener(
(request, sender, sendResponse) => {
if (request) {
//do some stuff here
}
}
)
if (params.get('someParam')) {
chrome.runtime.sendMessage(
{
example: 'data'
}
)
} else {
//do some stuff here
}
Here's what I tried:
return true in background.js addListener & scripts.js addListener
return Promise in background.js addListener & scripts.js addListener
I checked - chrome.tabs.query returns the right tab and everything looks good there (although status of this tab is 'loading')
I've looked through many links online and still can't find a solution that works for me. I'd appreciate any help!

javascript - Chrome extension: Communication between content.js and background.js on load

Edit: Modified code using https://developer.chrome.com/extensions/devtools#evaluated-scripts-to-devtools as reference. Still no luck.
I'm trying to code a chrome-extension which uses chrome.* API call and save portions of the result in a file. I want to automate everything from the loading of the page to the text file download and hence, I don't want to use the browser.onclick() event.
My current attempt has no effect.
What changes would I need to make?
https://stackoverflow.com/a/16720024
Using the above answer as reference, I attempted the following:
manifest.json
{
"name":"Test Extension",
"version":"0.0.1",
"manifest_version": 2,
"description":"Description",
"permissions":["tabs"],
"background": {
"scripts": ["background.js"]
},
"devtools_page": "devtools.html"
}
background.js
// Background page -- background.js
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
// assign the listener function to a variable so we can remove it later
var devToolsListener = function(message, sender, sendResponse) {
// Inject a content script into the identified tab
chrome.tabs.executeScript(message.tabId,
{ file: message.scriptToInject });
}
// add the listener
devToolsConnection.onMessage.addListener(devToolsListener);
devToolsConnection.onDisconnect.addListener(function() {
devToolsConnection.onMessage.removeListener(devToolsListener);
});
}
devtools.js
var backgroundPageConnection = chrome.runtime.connect({
name: "devtools-page"
});
backgroundPageConnection.onMessage.addListener(function (message) {
// Handle responses from the background page, if any
});
chrome.devtools.network.onRequestFinished.addListener(
function(request) {
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
}
);
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
content.js
alert("Hello");

Tab Listener not Getting Activated by Message from Background Script

I am sending a message to the tab were I have a content script (getTradingData.js) from the background.js with the following code:
alert("Automated TradingView Extension is running");
chrome.tabs.query({
url: 'https://www.tradingview.com/*'
}, function(tabs) {
if (tabs.length == 1) {
chrome.tabs.sendMessage(tabs[0].id, {subject: "testConnection"}, function(response) {
alert(response); //THIS RETURNS UNDEFINED
if (response.msg == "getTradingDataScriptHere") {
alert("Script Already Injected. Do not reinject"); //THIS IS NOT RUNNING
} else {
chrome.tabs.executeScript(tabs[0].id, {file: "jquery-2.2.3.min.js"});
chrome.tabs.executeScript(tabs[0].id, {file: "jquery.waituntilexists.min.js"});
chrome.tabs.executeScript(tabs[0].id, {file: "getTradingData.js"});
alert("Injected all Nessessary Scripts for Auto Trading View to work"); //THIS IS NOT RUNNING
}
});
} else {
alert("Please have one and only one tradingview chart page opened.");
}
});
var price = "Waiting For Price"
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.subject == "getPrice") {
sendResponse({
price: price
});
} else if (request.from == "getTradingData" && request.subject == "scriptLoaded") {
//getTradingData.js Script has Fully Loaded onto Website
} else if (request.from == "getTradingData" && request.subject == "updatePrice") {
price = request.price
}
});
However the response return as undefined. So basically I am not getting a response back.
Here is what I have in my getTradingData.js that should respond to the message:
alert("getTradingData.js is Running");
//Send message to let the extension know the script has been injected on site
chrome.runtime.sendMessage({
from: 'getTradingData',
subject: 'scriptLoaded'
});
chrome.runtime.onConnect.addListener(function(port) { //THIS DOESN'T WORK EITHER
console.assert(port.name == "tradingdata");
port.onMessage.addListener(function(request) {
if (request.msg == "Knock knock")
port.postMessage({subject: "price"});
else if (msg.answer == "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer == "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});
//to check if script already injected
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
alert("got message"); //THIS IS NOT RUNNING
if (request.subject == "testConnection") {
sendResponse({msg: "getTradingDataScriptHere"});
}
});
//wait till item has loaded
$(".dl-header-figures").waitUntilExists(function(){
alert($(".dl-header-figures").text());
updatePrice();
});
function updatePrice(){
alert("updating price");
chrome.runtime.sendMessage({
from: 'getTradingData',
subject: 'updatePrice',
price: $(".dl-header-figures").text()
});
}
//TODO: Use long lived connections for this to work: https://developer.chrome.com/extensions/messaging
// setInterval(updatePrice(), 3000);
However this never gets activated, I never get the alert "got message".
Here is what my manifest.json looks like:
{
"manifest_version": 2,
"name": "Automated TradingView Strategy",
"description": "This extension shows a Google Image search result for the current page",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["jquery-2.2.3.min.js", "background.js"]
},
"content_scripts": [
{
"matches": ["https://www.tradingview.com/chart/*", "http://www.tradingview.com/*"],
"js": ["jquery-2.2.3.min.js", "jquery.waituntilexists.min.js", "getTradingData.js"]
}
],
"permissions": [
"activeTab",
"tabs",
"*://*.tradingview.com/*",
"https://ajax.googleapis.com/"
]
}
What am I doing wrong? How can I make it send a response back. Even when I refresh extension which should reload background.js without reloading tabs which already has the content script injected in it I get no response because the Listener is not activated.
What are you trying to do exactly in your background script?
chrome.tabs.query runs only once when you load the extension, also, the scripts you are injecting with chrome.tabs.executeScript should be injected already because of the manifest.
I don't know exactly what you're trying to do, but, you can listen to an event every time a tab is updated (tabs are updated after being created) - chrome.tabs.onUpdated.addListener
Updated background.js:
alert("Automated TradingView Extension is running");
chrome.tabs.query({
url: 'https://www.tradingview.com/*'
}, function(tabs) {
console.log(tabs);
if (tabs.length == 1) {
chrome.tabs.sendMessage(tabs[0].id, {subject: "testConnection"}, function(response) {
if (response) {
alert("Script Already Injected. Do not reinject");
} else {
chrome.tabs.executeScript(tabs[0].id, {file: "jquery-2.2.3.min.js"});
chrome.tabs.executeScript(tabs[0].id, {file: "jquery.waituntilexists.min.js"});
chrome.tabs.executeScript(tabs[0].id, {file: "getTradingData.js"});
alert("Injected all Nessessary Scripts for Auto Trading View to work");
}
});
} else {
alert("Please have one and only one tradingview chart page opened.");
}
});
var price = "Waiting For Price"
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.subject == "getPrice") {
sendResponse({
price: price
});
} else if (request.from == "getTradingData" && request.subject == "scriptLoaded") {
//getTradingData.js Script has Fully Loaded onto Website
} else if (request.from == "getTradingData" && request.subject == "updatePrice") {
price = request.price
}
});

messaging between content script and background page in a chrome extension is not working as it is supposed to be

I post the code below:
manifest.json
{
"manifest_version": 2,
"name": "Demo",
"description": "all_frames test",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["*://*/*"],
"js": ["content.js"],
"all_frames": true
}],
"permissions": [
"tabs",
"*://*/*"
]
}
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var tabStatus = changeInfo.status;
if (tabStatus == 'complete') {
function return_msg_callback() {
console.log('Got a msg from cs...')
}
chrome.tabs.sendMessage(tabId, {
text: 'hey_cs'
}, return_msg_callback);
}
});
content.js
/* Listen for messages */
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
/* If the received message has the expected format... */
if (msg.text && (msg.text == 'hey_cs')) {
console.log('Received a msg from bp...')
sendResponse('hey_bp');
}
});
Then, if I go to a site that includes multiples cross-origin iFrames, e.g., http://www.sport.es/ you would see that all the iFrames within the page receive the message from the background page but only one of them is able to response back. Is this a normal behavior?
Thanks in advance for your answer.
You send just one message with a direct callback so naturally Chrome can use this response callback just one time (it's a one-time connection to one entity, be it a page or an iframe).
Solution 1: send multiple messages to each iframe explicitly:
manifest.json, additional permissions:
"permissions": [
"webNavigation"
],
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
.............
// before Chrome 49 it was chrome.webNavigation.getAllFrames(tabId, .....
// starting with Chrome 49 tabId is passed inside an object
chrome.webNavigation.getAllFrames({tabId: tabId}, function(details) {
details.forEach(function(frame) {
chrome.tabs.sendMessage(
tabId,
{text: 'hey_cs'},
{frameId: frame.frameId},
function(response) { console.log(response) }
);
});
});
});
Solution 2: rework your background script logic so that the content script is the lead in communication and let it send the message once it's loaded.
content.js
chrome.runtime.sendMessage({text: "hey"}, function(response) {
console.log("Response: ", response);
});
background.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
console.log("Received %o from %o, frame", msg, sender.tab, sender.frameId);
sendResponse("Gotcha!");
});
Communicating between a content script and the background page in a Chrome extension
Content script to background page
Send info to background page
chrome.extension.sendRequest({message: contentScriptMessage});
Receive info from content script
chrome.extension.onRequest.addListener(function(request, sender) {
console.log(request.message);
});
Background page to content script
Send info to content script
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { message: "TEST" });
});
Receive info from background page
chrome.runtime.onMessage.addListener(function(request, sender) {
console.log(request.message);
});
Instead of messaging, you can use executeScript for your purposes. While the callback's argument is rarely used (and I don't think many know how it works), it's perfect here:
chrome.tabs.executeScript(tabId, {file: "script.js"}, function(results) {
// Whichever is returned by the last executed statement of script.js
// is considered a result.
// "results" is an Array of all results - collected from all frames
})
You can make sure, for instance, that the last executed statement is something like
// script.js
/* ... */
result = { someFrameIdentifier: ..., data: ...};
// Note: you shouldn't do a "return" statement - it'll be an error,
// since it's not a function call. It just needs to evaluate to what you want.
Make sure you make script.js able to execute more than once on the same context.
For a frame identifier, you can devise your own algorithm. Perhaps a URL is enough, perhaps you can use the frame's position in the hierarchy.

Message Passing receiving end

Work algorithm of extension
For example, I am on site C, I see pageAction, click on it, script parsing needed information, then opens site A, script add all that information in textarea.
backround.js ---> c.js (signal to start parsing)
c.js ----> backround.js (message with information)
backround.js ----> a.js (that message, add in textarea) [Here I have problem]
manifest.json
{
"name": "test",
"version": "0.0.1",
"manifest_version": 2,
"description": "test",
"icons": { "16": "16.png",
"48": "48.png",
"128": "128.png"
},
"page_action" :
{
"default_icon" : "icon19.png",
"default_title" : "TEST"
},
"background": {
"page": "html/background.html"
},
"content_scripts": [
{
"matches": ["http://A_SITE"],
"js": ["js/jquery.js", "js/a.js"]
},
{
"matches": ["http://C_SITE"],
"js": ["js/jquery.js", "js/c.js"]
},
],
"minimum_chrome_version":"31.0",
"offline_enabled": true,
"permissions": ["tabs", "http://C_SITE/*", "http://A_SITE/*"]
}
c.js
$(document).ready(function(){
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.c_go == "go"){
//parsing here
chrome.runtime.sendMessage({ some obj }); // HERE I SEND MESSAGE TO BACKGROUND
}
});
});
a.js
$(document).ready(function(){
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request){
console.log(request); // NOTHING IN CONSOLE
}
});
});
background.js
function checkForValidUrl(tabId, changeInfo, tab) {
if(changeInfo.status === "loading") {
if (tab.url.indexOf('C_SITE') > -1) {
chrome.pageAction.show(tabId);
}
}
};
chrome.tabs.onUpdated.addListener(checkForValidUrl);
chrome.pageAction.onClicked.addListener(function(tab){
if (tab.url.indexOf('C_SITE') > -1){
// HERE I SEND MESSAGE TO c.js TO PARSING
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tab.id, {c_go: "go"});
});
// OPENS SITE_A
chrome.tabs.create({url: "SITE_A", "active":true}, function(tab){
// REQUEST FROM c.js
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
// SEND REQUEST TO TAB WITH SITE_A
chrome.tabs.query({active: true, currentWindow: true}, function(tab) {
chrome.tabs.sendMessage(tab[0].id, request);
});
});
});
}
});
So, in console on SITE_A I see nothing in console. Its very strange, because I use the same code in c.js chrome.runtime.onMessage.addListener.
How to fix it? Thanks.
chrome.runtime.onMessage is an event listener so it needs to be defined before said event occurs in order to listen for it. Right now you have it sending a message background -> c.js then send a new message c.js -> background.js. This turns it into a race condition as your background page opens up the new tab and creates the event handler at the same time that c.js is trying to send a message. Instead, try to make it all one flow.
c.js
chrome.runtime.onMessage.addListener(function(request,sender,sendResponse){
if(request.c_go == "go"){
//parsing here
sendResponse({ some obj }); // HERE I SEND MESSAGE TO BACKGROUND
}
});
This changes the message c -> background to a response to the first message.
background.js
chrome.pageAction.onClicked.addListener(function(tab){
chrome.tabs.sendMessage(tab.id,{c_go:"go"},function(response){
chrome.tabs.create({url: "SITE_A", "active":true}, function(newTab){
chrome.tabs.executeScript(newTab.id, {file:"a.js"},function(){
chrome.tabs.sendMessage(newTab.id, response);
});
});
});
});
This makes is so that Site A isn't opened until you get the info from Site C.
This is what happens on page-action click:
You send message to C_SITE.
You create new tab with A_SITE.
You register an onMessage listener to receive messages from C_SITE.
You forward any messages from C_SITE to A_SITE.
Unfortunately, somewhere between 1 and 3 (before the listener is registered), C_SITE sends its message (which goes unnoticed).
You should always make sure your events are fired after the corresponding listeners are properly set-up.
See my comment above:
* You use chrome.tabs.query() unnecessarily and risk breaking your extension's behaviour (e.g. if another widnow happens to become unexpectedly active).
* You unnecessarily use $(document).ready().

Categories

Resources