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

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!

Related

Sending message to content script when on a different domain

I want to send a message to a chrome tab to remove an mutation observer when I navigate off a certain page. However, I get the following error in the dev tools console.
Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.
Currently I have the below which does work fine when. It will connect and disconnect when the current URL does not match what I want (either on the same domain or difference domain). However, I get the above error when I go to a different domain and navigate.
manifest.json
{
"name": "Name...",
"description": "Description...",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "serviceWorker.js",
"matches": ["https://*.MY_DOMAIN.com/admin*"],
"type": "module"
},
"content_scripts": [
{
"matches": ["https://*.MY_DOMAIN.com/*"],
"run_at": "document_start",
"js": ["contentScript.js"]
}
],
"permissions": ["storage", "scripting", "webNavigation", "tabs"],
"host_permissions": ["https://*.MY_DOMAIN.com/*"]
}
serviceWorker.js
chrome.webNavigation.onHistoryStateUpdated.addListener(({ tabId, url: tabUrl }) => {
if (isSomePage) {
chrome.tabs.sendMessage(tabId, { action: 'addObserver' })
} else {
chrome.tabs.sendMessage(tabId, { action: 'removeObserver' })
}
})
contentScript.js
chrome.runtime.onMessage.addListener(({ action }, _, sendResponse) => {
const appEl = document.getElementById('app')
switch (action) {
case 'addObserver':
if (appEl) {
someObserver.observe(appEl, { childList: true, subtree: true })
}
break
case 'removeObserver':
someObserver.disconnect()
break
}
sendResponse()
})
The only solution I have found that fixes the error and keeps all existing functionality is below, however I am not sure on this. My understanding is the below will inject the content script multiple times on the same page potentially and also inject it into pages that are not part of my domain.
serviceWorker.js
chrome.webNavigation.onHistoryStateUpdated.addListener(({ tabId }) => {
chrome.tabs.query({ url: 'https://*.MY_DOMAIN.com/*' }, (tabs) => {
for (const { id: queryTabId } of tabs) {
if (queryTabId) {
chrome.scripting.executeScript(
{
target: { tabId: queryTabId },
files: ['contentScript.js'],
},
() => {
if (isSomePage) {
chrome.tabs.sendMessage(tabId, { action: 'addObserver' })
} else {
chrome.tabs.sendMessage(tabId, { action: 'removeObserver' })
}
}
)
}
}
})
})
Is there an alternative solution to the above problem?

Wait for injected script execution to finish in Chrome Extension

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://*/*"
]
}

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
}
});

Chrome Extension: Unable to pass message to content.js

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.

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