This question already has answers here:
Chrome Extension Message passing: response not sent
(3 answers)
Chrome Extension Message Passing [duplicate]
(1 answer)
Closed 8 years ago.
I've got a very odd problem. I have an extension that takes a screenshot of webpages. I created it using this extension as a reference. It works perfectly and the world is happy.
The extension communicates back and forth with a content script and it uses deprecated APIs to do so. In an effort to keep the extension from using outdated code, I went ahead and made the following replacements:
chrome.tabs.sendRequest(...) -> chrome.tabs.sendMessage(...)
chrome.extension.onRequest(...) -> chrome.runtime.onMessage(...)
chrome.extension.sendRequest(...) -> chrome.runtime.sendMessage(...)
Doing that breaks the extension and it no longer works. If I use the deprecated code, it all works fine again.
I did some tracking and discovered that the extension breaks at the following code:
CONTENT SCRIPT
chrome.runtime.sendMessage(sender.id, data, function(captured) {
window.clearTimeout(cleanUpTimeout);
console.log("came back from extension...");
if (captured) {
// Move on to capture next arrangement.
processArrangements(); //function defined elsewhere
} else {
// If there's an error in popup.js, the response value can be
// undefined, so cleanup
cleanUp(); // function defined elsewhere
}
});
EXTENSION
chrome.runtime.onMessage.addListener(function(message, sender, cb){
appendLog("received message from content script. callback is " + cb);
if (message.msg === 'capturePage') {
capturePage(message, sender, cb);
} else {
console.error('Unknown message received from content script: ' + message.msg);
}
});
function capturePage(data, sender, cb) {
... // some code omitted for clarity
chrome.tabs.captureVisibleTab(
null, {format: 'png', quality: 100}, function(dataURI) {
if (dataURI) {
var image = new Image();
image.onload = function() {
screenshot.ctx.drawImage(image, data.x, data.y);
//appendLog("calling callback function. callback is: " + cb);
cb(true); // **callback isn't called on content script**
};
image.src = dataURI;
}
});
}
I've omitted some code for clarity and the missing code is irrelevant. Let me explain what you see up there:
On the extension, inside the capturePage function, the line cb(true) executes. I can confirm it does because I'm able to see the "calling callback function" that comes right before it. However, the callback code on the content script does NOT run. Again, I can confirm this because I don't see the "came back from extension" message.
Here's the bizarre part: if I call cb(true) BEFORE I call chrome.tabs.captureVisibleTab(), the callback code on the content script DOES execute normally. That means chrome.tabs.captureVisibleTab() is interfering with the execution of cb(true) on the content script somehow. Maybe a port is closed when I call captureVisibleTab() - I'm not sure. The fact of the matter is that cb(true) will not execute on the content script after calling captureVisibleTab() but it will execute if I call it before.
On the extension, I tried storing the callback function, cb, in a variable outside capturePage() and then calling another function which would then call cb... but that doesn't work either.
I added the "" permission to my extension thinking maybe that would change things, but no. That changes nothing.
Again, if I replace the 4 lines of code where the deprecated APIs are, my extension works as expected. Thus, the problem is a combination of sendMessage() and captureVisibleTab(). There is no syntax error and I'm not leaving anything out. If I omit the call to captureVisibleTab(), the cb call will execute normally on the content script. What on earth???
How bizarre is that? I don't want to leave those deprecated API calls there. But then again, what could possibly be preventing the call back code to execute on the content script? I'm set on thinking the port is closed/replaced when we call captureVisibleTab(), but I'm not really sure and I've investigating it further.
I came across this online but I'm not sure if it's related to my issue. I also couldn't find anything similar here on stackoverflow and hence I created this question. Can anyone shed some light on this?
Thanks
Related
I have been trying to do some changes regarding the code below. At first I discovered that a function that returns a promise and in which a query is sent to db to be executed was being run twice instead of once. I have checked the query and the function itself just to make sure. Then I removed all code from inside io.of() except socket.on() functions which didn't seem to be involved in this matter. I have put a simple console.log() statement inside after removing the code I mentioned and it also produced the 'being executed twice' problem.
io.of('....').on('connection', socket => {
console.log("hello");
//...
//......
// below are socket.on('...')... and nothing more
})
Adding this to html and moving the code to socket.on('load') in io.of() fixed it for me.
$(document).ready(function () {
socket.emit('load');
});
I'm experimenting with the translation service on the Microsoft bot framework. I've written a method to which I pass a callback function which receives my translated text.
I've got an existing bot that calls an HTTP endpoint to create my output in English. I want to translate the output to the different language before returning it to the user. My unaltered code looks like this:
await request.post(ENDPOINT,
{
headers: HEADERS,
json: BODY
},
async function (error, response, body) {
if (response.statusCode == 202) {
var msg = body.mainResponse.text;
context.sendActivity(msg);
}
});
This runs just fine. Data passed in the HTTP response body gets parsed sent back to the user.
Now I want to plug in my translation service. I've got a single function that I call to do this called Translator.translate(text, callback). I've added this call to my existing function to get:
await request.post(ENDPOINT,
{
headers: HEADERS,
json: BODY
},
async function (error, response, body) {
if (response.statusCode == 202) {
var msg = body.mainResponse.text;
await Translator.translate(msg, function (output) {
context.sendActivity(output);
});
}
}
);
My translation process runs and I get the translation in the output variable, but nothing gets sent back to the user. Looking at the terminal, I see the error "Cannot perform 'get' on a proxy that has been revoked" relating to the context.sendActivity line in my callback.
Can anyone suggest how I keep the context object active?
Thanks in advance.
Many thanks for the assistance everyone - I never completely got to the bottom of this, but I finally fixed it with a complete re-write of the code. I think the problem was caused by a large number of nested synchronous and asynchronous calls. My ultimate solution was to completely get rid of all the nesting - first calling the translation service (and waiting for it), then doing the original call.
I think there are a number of other asynchronous threads inside the methods of both pieces of functionality. I don't have a great understanding of how this works in node, but I'm guessing that the response was getting popped off the stack at the wrong point, which is why I wasn't seeing it. The "cannot perform get" error was a bit of a red herring, it turns out. I get the same error from some of Microsoft's working demo code. I'm sure there's a separate issue there that ought to be fixed, but it wasn't actually caused by this issue. The code was running, but the output was getting lost.
My extension (https://chrome.google.com/webstore/detail/asana2go/meaajmlecpkbjcofehfpjngpnpfpjlkd/related?hl=en-US) works fine...until I quit and restart Chrome.
After restarting Chrome, this line in my extension's content script:
chrome.runtime.sendMessage({msg: copyOrPrint, tasksOrSubtasks: tasksOrSubtasks, currentProjectIdOrSearch: currentProjectIdOrSearch, currentProjectName: currentProjectName, tasks: tasks});
is executed, I believe, because the prior line is a console.log() and I see its output in the console.
But because in my background script I have this:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log('background listener function');
...
and I do not see 'background listener function' in the console, I'm concluding that either the sendMessage isn't getting through or the onMessage listener is not getting called.
Can you think of any places where I could look to debug this? I'm using the page's devtools, the Inspect Popup devtools, and the background script page's devtools windows, and I've narrowed down to this but now I'm stumped.
Again, after restarting Chrome and seeing this failure to send or receive, I can remedy simply by reinstalling the extension and all works fine and I know the message is both sent and received and continues to work fine--until I quit and restart Chrome again. (This same behavior occurs if I run locally with Load Unpacked or published.)
Since the extension reinstall clears up the problem, perhaps my chrome.runtime.onInstalled listener is relevant? Here is the start of it (which ends with the snippet from my background script already included above):
'use strict';
chrome.runtime.onInstalled.addListener(function() {
let md;
let outputTabId = null;
let messageObj = null;
let cssRules = null;
let requestMsg = null;
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [new chrome.declarativeContent.PageStateMatcher({
pageUrl: {hostEquals: 'app.asana.com'},
})
],
actions: [new chrome.declarativeContent.ShowPageAction()]
}]);
});
chrome.commands.onCommand.addListener(function(command) { // shortcut keys for copy, print
chrome.tabs.executeScript({code: `ContentScript.sendEls('${command}');`});
});
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log('background listener function');
...
(Background: this is my first extension and it's not trivial, so I may well have something wrong! I'm sorry not to include a minimal and complete verifiable example, but the common message passing examples with the extension documentation don't exhibit this problem, and my large extension does. You should be able to repeat my problem by installing the extension linked above, but you will also need to use asana.com since my extension is a page_action for Asana.)
Thank you very much for any leads/ideas!
Larry
My colleague solved this. The onMessage listener was incorrectly inside the onInstalled listener, as is shown in the last code block in the question.
I should have realized that when Chrome restarts each time (post initial installation of the extension) none of this code is executed/exists so there's no onMessage handler to run anymore. But I never saw it during weeks of development because I rarely close chrome! And since I have been in heavy development, I'm constantly doing re-installs of the extension and thus getting the onMessage listener defined in my unique case--not so for most users!
Thanks...
I'm writing a tiny chrome-extension, which redirects the console.log output to a http server via an ajax call. But I discovered, that I cannot overwrite the console.log function. The content script (for testing):
console.log = function() { return 0 }
it keeps the function untouched.
Anybody knows, what could be the reason? Is there maybe another way to archive this?
Content scripts run in isolated contexts. You can workaround this by creating a script tag and injecting it into the DOM.
var script = document.createElement("script")
script.textContent = "console.log = function() { return 0 }"
document.appendChild(script)
I don't have enough reputation to comment or vote, so I just put comment here and please feel free to remove my answer.
I recommend you refer how can I override console.log() and append a word at the beginning of the output? .
I have a chrome extension which injects an iframe into every open tab. I have a chrome.runtime.onInstalled listener in my background.js which manually injects the required scripts as follows (Details of the API here : http://developer.chrome.com/extensions/runtime.html#event-onInstalled ) :
background.js
var injectIframeInAllTabs = function(){
console.log("reinject content scripts into all tabs");
var manifest = chrome.app.getDetails();
chrome.windows.getAll({},function(windows){
for( var win in windows ){
chrome.tabs.getAllInWindow(win.id, function reloadTabs(tabs) {
for (var i in tabs) {
var scripts = manifest.content_scripts[0].js;
console.log("content scripts ", scripts);
var k = 0, s = scripts.length;
for( ; k < s; k++ ) {
chrome.tabs.executeScript(tabs[i].id, {
file: scripts[k]
});
}
}
});
}
});
};
This works fine when I first install the extension. I want to do the same when my extension is updated. If I run the same script on update as well, I do not see a new iframe injected. Not only that, if I try to send a message to my content script AFTER the update, none of the messages go through to the content script. I have seen other people also running into the same issue on SO (Chrome: message content-script on runtime.onInstalled). What is the correct way of removing old content scripts and injecting new ones after chrome extension update?
When the extension is updated Chrome automatically cuts off all the "old" content scripts from talking to the background page and they also throw an exception if the old content script does try to communicate with the runtime. This was the missing piece for me. All I did was, in chrome.runtime.onInstalled in bg.js, I call the same method as posted in the question. That injects another iframe that talks to the correct runtime. At some point in time, the old content scripts tries to talk to the runtime which fails. I catch that exception and just wipeout the old content script. Also note that, each iframe gets injected into its own "isolated world" (Isolated world is explained here: http://www.youtube.com/watch?v=laLudeUmXHM) hence newly injected iframe cannot clear out the old lingering iframe.
Hope this helps someone in future!
There is no way to "remove" old content scripts (Apart from reloading the page in question using window.location.reload, which would be bad)
If you want to be more flexible about what code you execute in your content script, use the "code" parameter in the executeScript function, that lets you pass in a raw string with javascript code. If your content script is just one big function (i.e. content_script_function) which lives in background.js
in background.js:
function content_script_function(relevant_background_script_info) {
// this function will be serialized as a string using .toString()
// and will be called in the context of the content script page
// do your content script stuff here...
}
function execute_script_in_content_page(info) {
chrome.tabs.executeScript(tabid,
{code: "(" + content_script_function.toString() + ")(" +
JSON.stringify(info) + ");"});
}
chrome.tabs.onUpdated.addListener(
execute_script_in_content_page.bind( { reason: 'onUpdated',
otherinfo: chrome.app.getDetails() });
chrome.runtime.onInstalled.addListener(
execute_script_in_content_page.bind( { reason: 'onInstalled',
otherinfo: chrome.app.getDetails() });
)
Where relevant_background_script_info contains information about the background page, i.e. which version it is, whether there was an upgrade event, and why the function is being called. The content script page still maintains all its relevant state. This way you have full control over how to handle an "upgrade" event.