I'm writing an Edge extension and struggling with communication between content script and background script.
I'm sending a message from content script to background one:
browser.runtime.sendMessage({ name: "get_card_for_website", url: document.URL }, function(response) {
console.log("Got card for the website:");
console.log(response);
if (response != undefined) {
if (response.card) {
g_card = response.card;
callback(response.card);
}
}
});
Listener in background script is implemented like this:
browser.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.name == 'get_card_for_website') {
get_card_for_website(request.url)
.done(function(element) {
if (element.cards && element.cards.length != 0) {
if (element.cards.length == 1) {
sendResponse({'card': element.cards[0]});
}
else {
get_one_card_for_site(element);
sendResponse({'card': ""});
}
}
});
}
}
Debugger shows message is sent to background script and corresponding code is executed up to sendResponse. But back in content script this callback function is never executed. Console shows no errors.
What could I miss?
Update: I've found out that some tabs receive responses and some don't. I don't really understand difference between first and second ones.
Ok, I figured out the reason of the problem. sendResponse() was called in .done() function, which is called asynchronously. And according to manual:
The sendResponse callback is only valid if used synchronously, or if
the event handler returns true to indicate that it will respond
asynchronously.
So I've modified my background script's function this way:
if (request.name == 'get_card_for_website') {
get_card_for_website(request.url)
.done(function(element) {
if (element.cards && element.cards.length != 0) {
if (element.cards.length == 1) {
sendResponse({'card': element.cards[0]});
}
else {
get_one_card_for_site(element);
sendResponse({'card': ""});
}
}
});
return true; // <----- this part I've added
}
And now it's working like a charm.
Related
I am finding the 'Uncaught (in promise) Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received' error in the background.js. My chrome extension works but the dev tools displays a lot of these errors on background.js
I recently migrated my chrome extension from Manifest V2 to Manifest V3 and since then I am finding the error.
This is the place from where the error seems to be arising :
const browser = chrome || browser;
// Listen for messages from content script
browser.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
var frameId = sender.frameId || 0;
if (request.type == "connect") {
connect(sender.tab, frameId);
sendResponse({ success: true });
} else if (request.type == "disconnect") {
disconnect(sender.tab, frameId);
sendResponse({ success: true });
} else if (request.type == "send") {
sendNativeMessage(request.data, sender.tab);
document.addEventListener('nativeMessage',
function handleNativeMessage(e) {
sendResponse(e.detail);
document.removeEventListener(e.type, handleNativeMessage);
}, false);
sendResponse({});
return true;
}
});
The sendresponse is processed in ContentScript :
const browser = chrome || browser;
self.addEventListener("message", function(event){
var request = event.data;
if(request != null && request.type == "SendMessage")
{
ProcessNativeMessage(request.data);
}
});
function ProcessNativeMessage(nativeMessageData) {
var request = new Object();
request.data = nativeMessageData;
browser.runtime.sendMessage(request,handleExtensionResponse);
}
function handleExtensionResponse(value)
{
//alert(value);
};
I played around by returning true, returning false, returning undefined, sending empty response and even disabling the other extensions but none of these changes worked.
I even tried changing the code with async/await as suggested here but it makes no difference.
Does anything change if you rewrite the code like this?
Anyway I can't reproduce an extension with native messaging, sorry.
P.S. Before any scientist decides to downvote me badly, I want to ensure that if the solution doesn't work I'm willing to cancel the answer.
const browser = chrome || browser;
var globSendResp, globReqTypeSend; //(*)
// Listen for messages from content script
browser.runtime.onMessage.addListener(function(request, sender, sendResponse) {
var frameId = sender.frameId || 0;
globReqTypeSend = false; //(*)
if (request.type == "connect") {
connect(sender.tab, frameId);
sendResponse({ success: true });
} else if (request.type == "disconnect") {
disconnect(sender.tab, frameId);
sendResponse({ success: true });
} else if (request.type == "send") {
globReqTypeSend = true; //(*)
globSendResp = sendResponse; //(*)
sendNativeMessage(request.data, sender.tab);
//sendResponse({}); //(*)
return true;
}
});
document.addEventListener('nativeMessage', function handleNativeMessage(e) {
if (globReqTypeSend)
globSendResp(e.detail)
}, false);
I'm having a lot of trouble trying to pass a variable from a content script to the popup.js using the sendMessage method.
This is the content script where I'm sending a message to the background script that contains the number of children of a DOM node:
let incomingChatsNumber = incomingChatsContainer.children().length;
chrome.runtime.sendMessage({ incomingChatsNumber: incomingChatsNumber });
Then the background.js listen to the message and send a messag itself with the same variable to the popup.js:
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
let incomingChatsNumber = message.incomingChatsNumber;
chrome.runtime.sendMessage({ incomingChatsNumber: incomingChatsNumber });
});
In the popup.js I have a button that will trigger some code if the "incomingChatsNumber" is more than 0 (the dom container has children):
$("#js-toggleSorting").on("click", function () {
chrome.runtime.onMessage.addListener(function (message) {
let incomingChatsNumber = message.incomingChatsNumber;
if (incomingChatsNumber <= 0) {
$(".message").html("<p>No Chats To Sort, You Joker</p>");
} else {
$(".message").html("<p>Sorting Chats</p>");
//This code never gets executed
if ($("#js-toggleSorting").attr("data-click-state") == 1) {
$("#js-toggleSorting").attr("data-click-state", 0);
$("#js-toggleSorting").html("SORT INCOMING CHATS");
sortFunction(false);
} else {
$("#js-toggleSorting").attr("data-click-state", 1);
$("#js-toggleSorting").html("STOP SORTING INCOMING CHATS");
sortFunction(true);
}
save_button_state();
}
});
});
The strange thing for me is that the popup.js gets the value right I can even console.log it, but as soon as I put more code on that if conditional the code never gets executed even when the condition is ok to go.
UPDATE:
After doing the modifications suggested by ehab I realized that the variable is "undefined" all the time because of the async nature of chrome.runtime.onMessage.addListener:
Content Script:
let incomingChatsNumber = incomingChatsContainer.children().length;
chrome.runtime.sendMessage({ incomingChatsNumber: incomingChatsNumber });
Background.js:
chrome.runtime.onMessage.addListener(function (message) {
let incomingChatsNumber = message.incomingChatsNumber;
chrome.runtime.sendMessage({ incomingChatsNumber: incomingChatsNumber });
});
Popup.js (This is where I need to use the value of the incomingChatsNumber variable):
let latestIncomingChatsNumber;
chrome.runtime.onMessage.addListener(function (message) {
let incomingChatsNumber = message.incomingChatsNumber;
latestIncomingChatsNumber = incomingChatsNumber;
//This Works
console.log(latestIncomingChatsNumber);
});
//This will give me an undefined value
console.log(latestIncomingChatsNumber);
I get that my problem is related to the fact that the chrome.* APIs are asynchronous so The chrome.runtime... and the console.log(...) will be executed at the same time, hence the error. So how to use the latestIncomingChatsNumber variable inside an on click event then ? Do I have to to save "latestIncomingChatsNumber" to the local storage inside the chrome.runtime.onMessage.addListener first ?
You should not put an event listener inside another event handler, this is an extremely bad practice even if it do what you think it should be doing, this will cause memory leaks for you down the road, and makes the code hard to read and semantically incorrect.
It seems based on looking at your code the problem is that the handler for the message gets called multiple times because of previous event listeners added in the click evet
let latestIncomingChatsNumber
chrome.runtime.onMessage.addListener(function (message) {
let incomingChatsNumber = message.incomingChatsNumber;
// save this into the application state, i will use a parent scope variable you could use something else
latestIncomingChatsNumber = incomingChatsNumber
});
$("#js-toggleSorting").on("click", function () {
if (latestIncomingChatsNumber <= 0) {
$(".message").html("<p>No Chats To Sort, You Joker</p>");
} else {
$(".message").html("<p>Sorting Chats</p>");
//This code never gets executed
if ($("#js-toggleSorting").attr("data-click-state") == 1) {
$("#js-toggleSorting").attr("data-click-state", 0);
$("#js-toggleSorting").html("SORT INCOMING CHATS");
sortFunction(false);
} else {
$("#js-toggleSorting").attr("data-click-state", 1);
$("#js-toggleSorting").html("STOP SORTING INCOMING CHATS");
sortFunction(true);
}
save_button_state(); // i believe this function saves the attributes to the dom elements
}
});
if save_button_state does a good job the code i shared should work
I want to run a callback function from content script after tab loading new page .
Here is my code :
content_script.js
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
if (request.id == "content_script") {
// open google.com
chrome.runtime.sendMessage({
"id" : "openUrl",
"url" : "https://google.com"
}, function(response) {
});
// call background script
// go to the claim code page
chrome.runtime.sendMessage({
"id" : "background"
}, function() {
alert("test");
});
}
});
background.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.id == "openUrl") {
var tabId = sender.tab.id;
var url = msg.url;
openUrl(tabId, url);
} else if (msg.id == "background") {
setTimeout(function() {
sendResponse();
}, 5000);
}
});
function openUrl(tabId, url) {
chrome.tabs.update(tabId, {
url : url,
active : false
}, function() {
console.log("open tab url callback");
});
};
I also uploaded the source code to google drive, you can download it using the bellow link :
https://drive.google.com/open?id=15zSn40z4zYkvCZ8B-gclzixvy6b0C8Zr
as you can see the alert test don't show !
However if I remove the code which open new url , then alert ("test") appear !
I am not sure why ! but it looks like javascript lost the reference to the call back function when I open new url .
How can I solve the problem ? what's the correct way ?
Thank you
The sendResponse function becomes invalid after the message callback returns, unless you return true from the event listener to indicate you wish to send a response asynchronously. (https://developer.chrome.com/extensions/runtime#event-onMessage)
Add return true; in background.js to make this handler asynchronous.
Now you get an error Attempting to use a disconnected port object in the sendResponse(); call of background.js, and yes, that's a result of the page navigating away and losing the context that the content script was running in.
There's no way to make this work: The context in which you wanted to call alert() simply doesn't exist anymore.
Try using chrome.tabs.sendMessage instead. But this means you have to set up the listener at the top level, and not inside of any callback. Message passing is hard.
This issue is literally driving me mad and I've already spent hours researching possible solutions :)
My problem is: I've got a script that, upon loading, makes some AJAX calls to the server. This script is a widget and is already configured for cross-domain , etc.
Everything was working fine until now when 1 request has stopped working. The crazy thing is that is only that one, the others work just fine.
You can see in the screenshot below:
This is the code I use to send AJAX requests:
ajax: {
xhr: null,
request: function (url, method, data, success, failure){
if (!this.xhr){
this.xhr = window.ActiveX ? new ActiveXObject("Microsoft.XMLHTTP"): new XMLHttpRequest();
}
var self = this.xhr;
self.onreadystatechange = function () {
if (self.readyState === 4 && self.status === 200){
success(JSON.parse(self.responseText));
} else if (self.readyState === 4) { // something went wrong but complete
if (failure) {
failure();
} else { console.error("Ajax calls has failed."); }
}
};
self.onerror = function() {
if (failure) {
failure();
} else { console.error("Ajax calls has failed."); }
};
this.xhr.open(method,url,true);
this.xhr.setRequestHeader('Content-Type', 'application/json');
this.xhr.send(JSON.stringify(data));
}
}
And this is the call that causes the problem:
this.ajax.request(
this.getWidgetUrl() +"/check_referral_code",
"POST",
{uuid: SL.uuid, ref_code: ref},
function(data) {
if (data.response == "ok") {
// Do something
} else {
console.error(data.message);
}
},
function(data) {
console.error(data.message);
}
);
Can anybody help here?
UPDATE:
The problem seems to be intermittent. If I reload the page it will literally happen 50% of the times
i need following function to be execute in Firefox.., but it is working fine in chrome. the problem was when i do 'Inspect Element With Firebug' it is working fine. the method 'EditEncounterBillStatus' is also hitting correctly. but when i don't use 'Inspect Element With Firebug' the method EditEncounterBillStatus is not hitting.. i tried a lot to sort out this. but still i can't can any one help me to find solution thanks in advance.
else if (element.trim() == "Approved") {
var TestPin = prompt("Please Enter your PIN");
if (TestPin != null) {
if (isNaN(TestPin)) {
alert("Please Enter a Valid Pin");
return;
}
else if (TestPin == pin) {
var postVisitData = { VisitId: vid};
$.post("/Emr/WaitingRoom/EditEncounterBillStatus", { VisitId: vid }, function (data) {
});
window.location = "/Emr/Patients/Show?PID=" + pid;
}
else {
alert("Your Entered PIN Is Incorrect");
}
}
else {
return;
}
}
I would recommend doing it like this
else if (TestPin == pin) {
$.post("/Emr/WaitingRoom/EditEncounterBillStatus", { VisitId: vid }, function (data) {
window.location = "/Emr/Patients/Show?PID=" + pid;
});
return; // in case of side effects in unseen code
}
i.e. wait until the $.post has finished before changing the window.location
As the rest of your code is unseen there could be side effects of performing this in this way - hence the return where it is - but even then, not knowing the full call stack there could still be side effects - you have been warned
You should change location upon the success of the post call, so put that in your callback function body:
$.post("/Emr/WaitingRoom/EditEncounterBillStatus", { VisitId: vid },
function (data) {
window.location = "/Emr/Patients/Show?PID=" + pid;
});
This way you are sure you only change location when the post action was executed. Otherwise you risk that you change location before the post happens. In debug mode, and certainly when you step through the code, there is enough time for the post to finish in time, and so your original code then works.