So I'm very new to Chrome's message passing, and I'm trying to use it to have my background page be alerted when the DOM of a page is modified (using some injected js).
I'm only trying to have one way communication, but if I open up console on the tab while the extension is running, it tells me "Uncaught Error: Error connecting to extension [my extension id]."
For simplicity, I'll say my background page's background.js has these lines of code:
chrome.runtime.onMessage.addListener(
function(){
if (modified == "true") {
alert("modified message recieved");
fourth();
}
}
);
And my content script is:
function subtreeModified(){
chrome.runtime.sendMessage({modified: "true"});
alert("modified message sent");
}
document.addEventListener("DOMSubtreeModified", subtreeModified, false);
I've been testing this, and whenever the page DOM is modified, I get a bunch of alerts that say "modified message sent," but none that say "modified message recieved." Thoughts/tips?
You aren't declaring and using the arguments sent to the receiver's event listener. Your modified argument is sent as a property on an argument passed to your callback. That's where you have to get it from when using your code. Also, I added a console.log() at the start of your callback so you can see if it's getting called:
chrome.runtime.onMessage.addListener(function(message) {
console.log("received message");
if (message.modified == "true") {
alert("modified message recieved");
fourth();
}
});
Note: it's a little odd to be passing strings for true and false. Why not just use booleans?
Related
I want to stop script execution in a dynamically appended script (as in include_guard) by throwing an error from the appended file. Then I want to catch this error from the file that did the "appending" (as in "making things clean").
index.html
<!DOCTYPE html>
<html>
<head>
<title>pickle</title>
</head>
<body>
<script type="text/javascript">
var de = document.createElement("script");
de.src = "file.js";
document.head.appendChild(de);
</script>
</body>
</html>
file.js
throw new Error("blabla");
How can I catch the error thrown ?
Something like this doesn't catch anything (probably because the appending is happening in a different thread):
var de = document.createElement("script");
de.src = "file.js";
try {
document.head.appendChild(de);
}
catch(e) {
console.log("got it");
}
I also tried setting an error listener to the window object, which does receive error notification, but the error is not caught.
Reason I want to do that: I wrote a javascript includer that works pretty much like the c/c++ #include directive with support for a #pragma once-like feature.
If a file is included 2+ times and it was tagged as once, I need to stop execution for this file - by throwing or erroring the script from inside the once() function.
Note: it aleady works. My once() function throws a custom include_guard object and script execution is halted. If possible, I want to catch that error and print a warning instead, to make things look cleaner (not a life or death situation...).
It is possible to cancel an error by listening to it in window.onerror and returning true from the handler.
Documentation: https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event
Example:
window.onerror = function(event, source, lineno, colno, error) {
if (error instanceof my_custom_error) {
console.warn(my_custom_error + " was canceled.");
return true;
}
//don't disrupt anything else
return false;
}
In the specific issue of the OP tho, error is null and the error string in event is a generic "Script error" message (tested on Chrome), so this method can't be used.
It seems the reason for error to be null is that the error is thrown from a different file.
I am trying to send a message from my popup to my main content script which will change some values that I have added to a page using the method updateInfo(val). However, I am running into an issue where my message is either not being sent at all or just not being received by content.js. I tried using chrome.runtime.sendMessage(), but that didn't work, so I have went overkill and am sending a message to every tab. My content.js is still not receiving the message. I have confirmed this in the chrome debugger. When I change the select box which lives in the popup, it hits changeYearInfo(), but then the message is never received in content.js.
My content.js:
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "Year Value Changed") {
let newVal = request.value;
updateInfo(newVal);
}
});
My popup.js:
function changeYearInfo() {
let newYearVal = document.getElementById('yearSessions').value;
chrome.tabs.query({}, tabs => {
tabs.forEach(tab => {
chrome.tabs.sendMessage(tab.id, {
"message": "Year Value Changed",
"value": newYearVal
});
});
});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('yearSessions').addEventListener("change", changeYearInfo);
}
);
How would I fix this issue? I've successfully used message passing multiple times from background.js to content.js and vice versa, but this is the first time I've tried sending a message from popup to content.
EDIT: This has been solved. The issue was not in the message passing, but that I was basing my conclusion off of the debugger tool, and I was incorrectly using the popup debugger when trying to check if my message was being received in content.js
After struggling for hours with a weird bug in my actual Chrome extension, I finally managed to nail down the following MCVE:
background.js
chrome.runtime.onMessage.addListener(function(request) {
if (typeof request.hello !== "undefined") {
console.log("I got it!");
}
});
options.js
// the following gives an error
// chrome.runtime.sendMessage({ hello: true }, function(response) {
// if (chrome.runtime.lastError) {
// console.log(chrome.runtime.lastError);
// }
// });
// the following does not
chrome.runtime.sendMessage({ hello: true });
As you can probably tell from the comments above, when I add an extra callback function to check for a runtime last error, it gives an error:
The message port closed before a response was received.
However, when I do not add the callback function, no error is generated!
As as as I can tell from the docs, this is the correct format for my callback function:
If you specify the responseCallback parameter, it should be a function that looks like this: function(any response) {...};
Thus, I am failing to understand this inconsistent behaviour. I checked similar questions, and adding return true to the background.js listener simply delays the occurrence of the error in the first case.
Here's a zip of the above files to test locally. I am using Chrome 75.
Update: I do not intend to send any response from my background js file. It is just a one time message from my options page to the background page.
It doesn't, well at least not in my Chrome 75.
The Error Message you see is because you don't answer from your background script, but it's not because you checked for runtime.lastError.
If you want to avoid this error, you need to answer from you background script;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(sender.tab && // check it's actually a tab that talked to us
typeof request.hello !== "undefined"
) {
sendResponse({received: true}); // or whatever you like
}
});
I'm developing an extension for Chrome, and here's the workflow I'm trying to achieve:
popup sends message -> content script 1 listens -> content script 1 sends message -> content script 2 listens -> content script 2 performs action
In concept it's fine and dandy; what I've done is set up 2 listeners: one in each content script:
Popup:
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
console.log('popup send request');
chrome.tabs.sendMessage(tabs[0].id, obj);
});
Content script 1:
chrome.runtime.onMessage.addListener((function (request, sender) {
this.log('wg got request', 'request', request, 'sender', sender);
if (request.action == 'options-updated') {
this.updateOptions(request, (function() {
var obj = {action: 'refresh', WG: window.WG};
this.log('wg forwarded request');
chrome.runtime.sendMessage(obj); // attempting to forward another request
return true;
}).bind(this));
}
return true;
}).bind(window.WG));
Content script 2:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
console.log('content script got request', 'request', request, 'sender', sender);
if (request.WG) {
request.WG.log('message', request.action);
if (request.action == 'refresh') {
WGRefresh(request.WG, request.options);
}
}
return true;
});
Problem is, content script 2 only receives the first message. So the output I'm getting is:
popup send request
content script got request (first, to ignore)
wg got request (same, first, don't ignore here)
wg forward request
And then nothing. The content script should have fired it again, and in the request I always send "action", which I check for in the listener, but for the logs I don't differentiate (it should ALWAYS log, so this means the request never gets there).
I've tried returning true in all the listeners, according to the documentation it will keep the chain running and not stop after the first hit, but even so it's not working. What am I doing wrong?!
There are 2 sendMessage functions in Chrome API.
chrome.runtime.sendMessage sends a message to all open extension pages (i.e. background, popup, etc.)
chrome.tabs.sendMessage sends a message to all content scripts from the extension in a given tab
So the call to chrome.runtime.sendMessage() in your first content script can't reach any other content script.
What's more, you can't call chrome.tabs directly from a content script.
To do what you want, you need to set up a background script that will act like a proxy between CS1 and CS2. Technically, you could use the popup, but it's unreliable, as the popup may be closed and then nobody would be listening. The background page (or better yet, an event page) is designed specifically for that purpose.
So the scheme becomes: popup -(tabs.sendMessage)-> CS1 -(runtime.sendMessage)-> background -(tabs.sendMessage)-> CS2
Do note that background page will need to know the tab ID to send the message to. If it's the same tab for some reason, e.g. you're trying to message across frames, you can use the sender parameter in the callback.
See Messaging docs for more details.
I'm working on a Chrome Extension, and I'm new to the process. The extension I'm working on injects an HTML sidebar into the page, injects java-script functions to the header, then let's the user press the buttons on the sidebar to create/save my extension's data.
However, when I want to save the information, I use localStorage, however, the localStorage always saves with respect to the current website. How can I use localStorage to save with respect to our extension?
Furthermore, I would like to use some global javascript variables in the chrome-extension. Where do these belong? I know I can't currently access them from the injected Javascript.
I've looked into message passing, and I've had some trouble with it. I'm not sure how it works in the scope of injected javascript into a page's header. I've tried working with this example, but my extension doesn't seem to catch the message.
// This function is called in the injected header javascript.
function sendToExtension() {
setTimeout(function() {
console.log('page javascript sending message');
window.postMessage({ type: 'page_js_type',
text: "Hello from the page's javascript!"},
'*' /* targetOrigin: any */);
}, 10);
}
// This is installed in a background script.
window.addEventListener('message', function(event) {
console.log('content_script.js got message:', event);
});
You have to use Chrome's sendMessage function and onMessage listener. See below:
function sendToExtension() {
console.log('Sending message');
chrome.runtime.sendMessage({ext: "myExtension"}, function(response) {
console.log(response.ack);
});
}
// Listener - Put this in the background script to listen to all the events.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.ext) {
console.log('Message received from ' + request.ext);
sendResponse({ack:'received'}); // This send a response message to the requestor
}
});