I'm trying to send messages from the background page to a content script, then send a message from that content script to an injected script. I've tried this, but it isn't working.
Here's what my code looks like.
manifest.json
{
"manifest_version": 2,
"name": "NAME",
"description": ":D",
"version": "0.0",
"permissions": [
"tabs","<all_urls>"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
],
"web_accessible_resources": [
"injected.js"
],
"background":{
"scripts":["background.js"]
}
}
background.js
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response){});
});
content_script.js
var s = document.createElement('script');
s.src = chrome.extension.getURL('injected.js');
s.onload = function(){
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
document.dispatchEvent(new CustomEvent('Buffer2Remote', {todo: "LOL"}));
});
injected.js
document.addEventListener('Buffer2Remote', function(e){
alert(e.todo);
});
The message sending doesn't work from the first part, background -> content_script. Is there anything wrong with my code?
Background Script - Provides persistence and handles background events. Content Script - Scripts that run in isolation in the context of the web page. Injected Script - Scripts that are programmatically injected into the web page.
A content script is a part of your extension that runs in the context of a particular web page (as opposed to background scripts which are part of the extension, or scripts which are part of the website itself, such as those loaded using the <script> element).
runtime. onMessage API functions. The chrome.runtime.sendMessage function is used to send one time messages from one part of the extension to another part. The function receives a message object which can be any JSON serializable object and an optional callback to handle the response from the other part.
Your script doesn't work because of how content scripts are injected.
When you (re)load your extension, contrary to what some people expect, Chrome will not inject content scripts into existing tabs that match patterns from the manifest. Only after the extension is loaded, any navigation will check the URL for matching and will inject the code.
So, the timeline:
chrome://extensions/
page and you can't inject there anyway)1 - This also happens if you reload your extension. If there was a content script injected, it continues to handle its events / doesn't get unloaded, but can no longer communicate with the extension. (for details, see addendum at the end)
Solution 1: you can first ask the tab you're sending a message to whether it's ready, and upon silence inject the script programmatically. Consider:
// Background
function ensureSendMessage(tabId, message, callback){
chrome.tabs.sendMessage(tabId, {ping: true}, function(response){
if(response && response.pong) { // Content script ready
chrome.tabs.sendMessage(tabId, message, callback);
} else { // No listener on the other end
chrome.tabs.executeScript(tabId, {file: "content_script.js"}, function(){
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
throw Error("Unable to inject script into tab " + tabId);
}
// OK, now it's injected and ready
chrome.tabs.sendMessage(tabId, message, callback);
});
}
});
}
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
ensureSendMessage(tabs[0].id, {greeting: "hello"});
});
and
// Content script
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.ping) { sendResponse({pong: true}); return; }
/* Content script action */
});
Solution 2: always inject a script, but make sure it only executes once.
// Background
function ensureSendMessage(tabId, message, callback){
chrome.tabs.executeScript(tabId, {file: "content_script.js"}, function(){
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
throw Error("Unable to inject script into tab " + tabId);
}
// OK, now it's injected and ready
chrome.tabs.sendMessage(tabId, message, callback);
});
}
and
// Content script
var injected;
if(!injected){
injected = true;
/* your toplevel code */
}
This is simpler, but has complications on extension reload. After an extension is reloaded, the old script is still there1 but it's not "your" context anymore - so injected
will be undefined. Beware of side effects of potentially executing your script twice.
Solution 3: just indiscriminately inject your content script(s) on initialization. This is only safe to do if it's safe to run the same content script twice, or run it after the page is fully loaded.
chrome.tabs.query({}, function(tabs) {
for(var i in tabs) {
// Filter by url if needed; that would require "tabs" permission
// Note that injection will simply fail for tabs that you don't have permissions for
chrome.tabs.executeScript(tabs[i].id, {file: "content_script.js"}, function() {
// Now you can use normal messaging
});
}
});
I also suspect that you want it to run on some action, and not on extension load. For example, you can employ a Browser Action and wrap your code in a chrome.browserAction.onClicked
listener.
When an extension gets reloaded, one would expect Chrome to clean up all content scripts. But apparently this is not the case; content scripts' listeners are not disabled. However, any messaging with parent extension will fail. This should probably be considered a bug and may at some point be fixed. I'm going to call this state "orphaned"
This is not a problem in either of two cases:
However, if that's not the case, you've got a problem: the content script might be doing something but failing or interfering with another, non-orphaned instance of itself.
A solution to this would be:
Code, content script:
function heartbeat(success, failure) {
chrome.runtime.sendMessage({heartbeat: true}, function(reply){
if(chrome.runtime.lastError){
failure();
} else {
success();
}
});
}
function handler() {
heartbeat(
function(){ // hearbeat success
/* Do stuff */
},
function(){ // hearbeat failure
someEvent.removeListener(handler);
console.log("Goodbye, cruel world!");
}
);
}
someEvent.addListener(handler);
Background script:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.heartbeat) { sendResponse(request); return; }
/* ... */
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With