Getting basic message passing to work in Chrome extension - google-chrome-extension

I'm trying to get basic message passing to work in a Chrome extension. There are a number of related questions on SF on this (most notably here: Basic google chrome extension message passing not working), but unfortunately the advice there seems not to work for me.
Here's my manifest.json file:
{
"name": "Steve Screenshot",
"description": "Do something",
"version": "0.1",
"manifest_version": 2,
"content_scripts": [
{
"matches" : ["*://*/*"],
"js": ["simple.js"]
}
]
}
And my simple.js file:
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
console.log('message received');
});
// Invoke it!!!
chrome.runtime.sendMessage({
msg: 'whatever'
}, function(response) {
console.log('message sent');
});
At runtime, the console log shows the "message sent" message but NOT the "message received" message. To the best of my understanding, I am ?correctly? invoking chrome.runtime.onMessage/sendMessage. Can anyone comment?
Thanks,
--Steve

The problem is that you are trying to use chrome.runtime.sendMessage to send a message to a content script. You cannot send messages to content scripts using runtime.sendMessage, you must use tabs.sendMessage. (Read about that here).
A good setup for message passing is to use a background page as sort of a 'router' for your messages. When you need to pass a message to content scripts you do so from the background page using the tabs api. For a simple test, you could add a tabs.query call in the background script to send a message to ALL content scripts like so:
chrome.tabs.query({}, function(tabs) {
for(var i = 0; i < tabs.length; i++) {
chrome.tabs.sendMessage(tabs[i].id, {msg: 'whatever'});
}
}
To send messages to individual content scripts will require some additional logic but is very doable (one way is to maintain some associative queue structure in the background page).
Also, note that you can use chrome.runtime.sendMessage from inside a content script, but this will send a message to event listeners in your extension that are not inside content scripts. (So you can send a message to the background page using this method)

Related

Notification not shown in Chrome extension using Mozilla's webextension-polyfill

I just started with developing a Firefox add-on. It works fine in Firefox so I'd like to make it "compatible" to a Coogle Chrome extension.
For this I inject Mozilla webextension-polyfill and basically the add-on also runs in Chrome. There is one thing however I can't get to work...
In Firefox a notifcation is shown to the user if the content script sends a message which is received by the background script. Running this in Chrome results in the following exception:
Uncaught (in promise)
{message: "The message port closed before a response was received."}
callbackArgs # VM18 browser-polyfill.js:630
sendResponseAndClearCallback # VM29 extensions::messaging:417
disconnectListener # VM29 extensions::messaging:441
EventImpl.dispatchToListener # VM19 extensions::event_bindings:403
publicClassPrototype.(anonymous function) # VM25 extensions::utils:138
EventImpl.dispatch_ # VM19 extensions::event_bindings:387
EventImpl.dispatch # VM19 extensions::event_bindings:409
publicClassPrototype.(anonymous function) # VM25 extensions::utils:138
dispatchOnDisconnect # VM29 extensions::messaging:378
I can tell that this comes from the webextension-polyfill but I cannot find a way so that the notification is also shown in Chrome.
Here are the relevant code snippets...
manifest.json
{
"manifest_version": 2,
// ...
"background": {
"scripts": [
"lib/browser-polyfill.js",
"background-script.js"
],
"persistent": false
},
"options_ui": {
"page": "settings/options.html"
}
}
background-script.js
function notify(message) {
if (message.copied) {
browser.notifications.create({
"type": "basic",
"title": "Notifaction title",
"message": "Hello, world!"
});
}
}
browser.browserAction.onClicked.addListener(() => {
browser.tabs.executeScript({file: "lib/browser-polyfill.js"});
browser.tabs.executeScript({file: "content-script.js"});
});
browser.runtime.onMessage.addListener(notify);
content-script.js
browser.storage.local.get({elementId: ""})
.then(() => {
browser.runtime.sendMessage({copied: true});
});
There are two problems here...
Issue 1
The onMessage handler needs a return true. Only then the polyfill seems to be able to handle the messages correctly.
Issue 2
This seems to look like a bug in the polyfill. In Chrome the iconUrl option is required when creating a notification whereas it is optional in Firefox.
If I apply these two things the notification works in Firefox and Chrome.

Communicating between two chrome extensions

I am trying to communicate between two chrome extensions, but unable to do so.
Any help would be great in resolving this issue.
1st extension sending msg in background.js:
chrome.browserAction.onClicked.addListener(
function(tab)
{
chrome.runtime.onConnect.addListener(function(port)
{
port.postMessage({status:"hello"});
});
2nd extension receiving msg in background.js:
var port = chrome.runtime.connect({name: "lkddmaimhocofkfhngkdhdicmldnfdpn"});
port.onMessage.addListener(function(message,sender)
{
alert('listened bg');
});
It seems you are confused with the sending part and receiving part.
Also, there are some differences between onConnect
which fires when a connection is made from either an extension process or a content script,
and onConnectExternal
which fires when a connection is made from another extension.
Take a look at Message External and you can use the following sample code to communicate between two extensions.
1st extension sending msg in background.js:
chrome.browserAction.onClicked.addListener(function() {
var port = chrome.runtime.connect("lkddmaimhocofkfhngkdhdicmldnfdpn");
port.postMessage(...);
});
2nd extension receiving msg in background.js:
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// Handle your msg
});
});

Message Passing Example From Chrome Extensions

I'm using the example from the Google tutorial and finding it difficult to pass a simple message to the content script from the popup.
Can you provide some suggestions on how to pass a simple message and view it either in the console log or alert?
manifest.json
{
"manifest_version": 2,
"name": "msg-test",
"description": "message test",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"],
"persistent": true
},
"content_scripts": [{
"matches": ["http://*/*","http://www.site.com/*"],
"js": ["content.js"],
"run_at": "document_end"
}],
"permissions": [
"tabs",
"http://*/*"
]
}
background.js
chrome.runtime.onConnect.addListener(function(port){
port.postMessage({greeting:"hello"});
});
content.js
var port = chrome.runtime.connect({name:"content"});
port.onMessage.addListener(function(message,sender){
if(message.greeting === "hello"){
alert(message.greeting);
}
});
popup.js
window.onload = function() {
document.getElementById('btn2').onclick = function() {
alert("button 2 was clicked");
};
document.getElementById('btn1').onclick = function() {
alert("button 1 was clicked");
};
}
*Note: In this example the content script will fire when the page matches manifest.json and the alert box will show.
First, I wouldn't message pass between your popup and your content script. I would message pass between your Background page and your content scripts. Your popup page should only be used to show some ui to interact with your app.
With that being said, I will show you the way to pass messages between your background and your content script.
In your content script:
//This line opens up a long-lived connection to your background page.
var port = chrome.runtime.connect({name:"mycontentscript"});
port.onMessage.addListener(function(message,sender){
if(message.greeting === "hello"){
alert(message.greeting);
}
});
In your background page(possibly your popup? but I don't recommend it)
chrome.runtime.onConnect.addListener(function(port){
port.postMessage({greeting:"hello"});
});
Here is the sequence of events that will take place:
Your application will inject your content script into the page
Your content script will open up a port to communicate with the background script.
Your background script will be notified that a port was open, allowing it to send a message to it, or attach a message listener to it.
In the background script or the content script, you can listen for messages by using port.onMessage.addListener(). provided that port is in scope. Using ports is much easier to grasp and allows for simple, two way communication!
Edit:
If you would like to pass messages to your background page from your popup script, use the exact same method:
var port = chrome.runtime.connect({name: "popup-port"});
port.postMessage({status:"poppedup"});
Edit 2:
To navigate your user to a new page, do this:
function navigateToPage(url){
chrome.tabs.query({url: url}, function(tabs) {
var tab = tabs[0];
return tab ? chrome.tabs.update(tab.id, {active:true}) : chrome.tabs.create({url: url});
});
}
});
What this function does is, it checks to see if there is a tab with the url you want to go to, if there is, switch to it, else, create a tab with that url and navigate to it.

chrome extension messaging from background to content script gives :Port error: Could not establish connection. Receiving end does not exist [duplicate]

For my Chrome extension, I am attempting to post selected text to a PHP webpage. A solved question on this website (Chrome Extension: how to capture selected text and send to a web service) has helped me a lot in achieving this, but I want a different way of posting the text.
Instead of XMLHttpRequest as mentioned there, I want to send a hidden JS form from the content script. This method allows me to view or change the text before importing it to the database.
The problem is to get the trigger from the background to the content script. I already have a message the other way, so using the function(response) is desired. However, outside the “sendMessage”, I can’t listen for the response.cmd. And inside the “sendMessage”, I can’t get the response.cmd to trigger a function. Is there a solution for this, other than sending an all new message from the background script?
The code I am referring to:
Background.js
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if(request.cmd == "createSelectionMenu") {
sendResponse({cmd: "saveText"}); //Do things
}
});
Content_script.js
chrome.extension.sendMessage({ cmd: "createSelectionMenu", data: selectedText },
function(response) {
if(response.cmd == "saveText") {
createForm();
}
}
});
What I do is following:
I keep track of my opened tabs
content script:
// connect to the background script
var port = chrome.extension.connect();
background script
// a tab requests connection to the background script
chrome.extension.onConnect.addListener(function(port) {
var tabId = port.sender.tab.id;
console.log('Received request from content script', port);
// add tab when opened
if (channelTabs.indexOf(tabId) == -1) {
channelTabs.push(tabId);
}
// remove when closed/directed to another url
port.onDisconnect.addListener(function() {
channelTabs.splice(channelTabs.indexOf(tabId), 1);
});
});
Now I can notify all my registered tabs (i.e. content scripts) from my background script when a certain action happened:
var notification = { foo: 'bar' };
for(var i = 0, len = channelTabs.length; i < len; i++) {
chrome.tabs.sendMessage(channelTabs[i], notification, function(responseMessage) {
// message coming back from content script
console.log(responseMessage);
});
}
And again, on the other side in the content script, you can add a listener on these messages:
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.foo == 'bar') {
executeStuff();
// if a callback is given:
sendResponse && sendResponse('success');
}
});
It's a bit of a brainf*ck, because it's redundant at some places. But I like it best that way, because you can wrap it and make it a bit easier.
If you want to see how I am using this, see my repository on GitHub: chrome-extension-communicator.

Change the URL in chrome after the page has loaded

I've written the following script to change the URL of a tab in Chrome, but can't figure out how to get it to automatically run on every page.
var nytimes = /.*nytimes\.com.*/;
var patt = /(&gwh=).*$/;
function updateUrl(tab){
if(tab.url.match(nytimes))
{
var newUrl = tab.url.replace(patt,"");
chrome.tabs.update(tab.id, {url: newurl});
}
}
chrome.tabs.onUpdated.addListener(function(tab) {updateUrl(tab);});
I put that into my background page, but it isn't working. Do I need to put the code somewhere else to get it to run?
I strongly suggest you read about content scripts. They are exactly what you're looking for but you need to understand that they have limited access to the Chrome.* API, so you'll have to use message passing in order to use your current functionality. However, by using content scripts you can probably make this simpler using one of my proposed solutions.
Solution 1
Assuming you want to send the redirect to the same URL every time, you can easily configure your extension to only run your content script on the NY Times site. For example;
Content Script: content.js
location = 'http://example.com';
Solution 2
However, if the redirect URL can vary you many want to abstract that logic in to your background page. For example;
Content Script: content.js
// Or you can pass a more specific section of the URL (e.g. `location.pathname`)
chrome.extension.sendRequest({href: location.href}, function(data) {
location = data.url;
});
Background Page: background.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
sendResponse({
url: getUrl(request.href) // TODO: `getUrl` method containing your logic...
});
});
Important!
Regardless of which approach you go for you will also need to request permission to run the content script on the target site in your manifest file.
Manifest: manifest.json
{
...
"content_scripts": [
{
"js": ["content.js"],
"matches": ["*://*.nytimes.com/*"],
"run_at": "document_start"
}
],
...
}

Resources