Chrome Extension: Can't send message to tab with id of -1 - javascript

I've checked numerous questions regarding message passing in a Chrome extension but I haven't found much specifically relating to this.
I'm using the chrome.devtools* APIs and I'm having trouble sending messages between content scripts when the developer toolbar is docked. Everything works fine when it is not docked i.e. floating.
Here's a brief example of what I'm doing.
devtools.js
chrome.devtools.panels.create("myExtension", "img/icon.png",
"/panel.html", function(extensionPanel) {
var myData; //this variable gets manipulated before a user
//clicks on the panel
extensionPanel.onShown.addListener(function(panelWindow) {
chrome.extension.sendMessage({greeting: "update my data", data: myData}, function(response) {});
});
});
Then in my background script (eventPage.js) I listen for this message and pass it on to panel.js
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.greeting == "update my data"){
chrome.tabs.sendMessage(sender.tab.id, {greeting: "show data", showResource: request.data}, function(response) {});
}
});
And then finally I listen for the 'show data' call in my panel.js (which is loaded from panel.html)
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.greeting == "show data") {
//do stuff with my data here
});
Essentially, I need to pass messages between devtools.js to panel.js but the only way to do it is by using the background script as an intermediary.
devtools.js -> background.js -> panel.js.
This actually works fine as long as the dev tools panel is not docked to the window. When it is docked I get an error because sendMessage() won't except a tab id of -1, which is what sender.tab.id equals when the dev tools are docked to the window. I also tried using chrome.tabs.connect - long lasting connections - but ran into the same problem.

I also just found out recently how to do this.
A technique used by "Google Pagespeed" is to get the tab id of the inspected window and pass it back and forth between the extension and background using a port.
Whenever you want to send the extension a message, you look for the tab id and get its port.
panel.js
// get the inspected windows tab id from the panel
var tabId = chrome.devtools.inspectedWindow.tabId;
// create the port
var port = chrome.extension.connect({name: 'connection'});
// keep track of the port and tab id on the background by
// sending the inspected windows tab id
port.postMessage(['connect', tabId])
eventPage.js
var activeListeners = {};
chrome.extension.onConnect.addListener(function(port) {
port.onMessage.addListener(function(message) {
if (message[0] === 'connect') {
// get the tab id
var tabId = message[1];
// save the reference to the port
activeListeners[tabId] = port;
// make sure we clean up after disconnecting (closing the panel)
activeListeners[tabId].onDisconnect.addListener(function() {
delete activeListeners[tabId];
});
}
});
});
This is not a very thorough explanation but I hope you get the point.

Related

Check whether an URL is already open or not in Background script

I send message using chrome.runtime.sendMessage({}); from my content.js and it is received by background script which opens a HTML file:
background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
chrome.tabs.create({url: 'popup.html'});
});
If the popup.html is already open, I don't want to open it again, so there should be a ifcondition to check whether it is already open.
But what do I put in side the if condition before chrome.tabs.create({url: 'popup.html'}); in the background script?
Please note that I am looking the solution inside banckground script.
Please provide the solution according to the scripts given in this answer.
You need to check every tab if your extension popup page opened
background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
var flag = false;
chrome.tabs.query({}, function (tabs) {
for (let index = 0; index < tabs.length; index++) {
const tab = tabs[index];
if (tab.url.includes("chrome-extension://")) { //You can filter by extension id or popup.html if you want
flag = true;
}
}
if(flag){
chrome.tabs.create({ url: 'err.html' });
}
else{
chrome.tabs.create({ url: 'popup.html' });
}
});
});
You can filter by extension id or popup.html if you want in if (tab.url.includes("chrome-extension://")) {. For better results you can filter by your extension id
err.html
<html>
<script src="err.js"></script>
</html>
err.js
alert("Popup already opened");
window.close();
if popup tab already opened then open err.html and show alert then close. But you can't alert in background.js because background.js does not have a page to show alert.
You can see your extension id from extension details in chrome and popup url. "Kimlik" means ID btw.
And I mean when you open a new tab with using your extension it will always includes your extension id in url so you can filter your url by it.
example code:
if (tab.url.includes("chrome-extension://elbpcoenaghkeppoliiaakgggojafnkl/popup.html")) {
flag = true;
}
or you can use chrome.runtime.id to get extension id
It seems a lot simpler to store the id of the opened tab, set that id to null if the tab is closed, and only open another new tab if no open tab id is set.
You can use the second argument of chrome.tabs.create for this. Watch for when the tab is removed using chrome.tabs.onRemoved.addListener.
I didn't find time to do the setup to run this code. I'll update this later but perhaps it already helps out.
let popupTabId = null;
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (popupTabId !== null) {
// Optionally do stuff with the existing tab here.
return;
}
chrome.tabs.create(
{url: 'popup.html'},
tab => {
popupTabId = tab.id;
}
);
});
chrome.tabs.onRemoved.addListener(
removedTabId => {
if (removedTabId === popupTabId) {
popupTabId = null;
}
}
)
You can also focus the tab using its id in the listener. I only added a return statement in the code as strictly the question is only for detecting the tab.
Handling open tabs on extension startup
If the popup tab is always closed when/before Chrome exits, you can ignore the following and the above solution should be sufficient.
My solution doesn't handle tabs that are already opened yet, when the extension initializes. For this you could use the slower solution of the other answer to initialize the value of popupTabId to something else than null.
Another option is to write the tab id to storage when it changes, then read it on initialization.

How to pass data from content script to popup.html?

I'm learning how to make chrome extensions. I have a content script that will obtain some data and I want to pass them to the popup.html page to display them on the popup DOM. I've read about the message passing in the Chrome documentation but I'm unable to make it work. Can anyone help me?
My code:
content script file: main.js
(function($){
$(document).ready(function(){
console.log('Extension Started!');
var el = $(document).find('#stories_tray');
var child = el.find('._827c');
$.each(child, function(i){
var div = $(child[i])
.find('._7h4p')
.attr('data-onkeypress');
var d = JSON.parse(div);
if( typeof d[0].a != 'undefined' ){
console.log(d[0].a[0].preloadImageURIs[0]);
var l = d[0].a[0].preloadImageURIs[0];
chrome.runtime.sendMessage({imageURIs: l}, function(response) {
console.log(response.farewell);
});
}
});
});
}(jQuery));
popup javascript file: popup.js
// window.onload = function(){
// $('.spinner-grow').delay('300')
// .css({'transition':'ease-out','display':'none'});
// }
(function($){
$(document).ready(function(){
console.log('Extension Started!');
chrome.runtime.onMessage.addListner(function(request, sender, sendResponse){
console.log(sender.tab ? "from a content script:" + sender.tab.url : "from the extension");
console.log(request.imageURIs);
sendResponse({farwell: "ok"});
});
});
}(jQuery));
Maybe I'm doing something wrong with the code.
I get this errors in the console:
// content script console error
Error handling response: TypeError: Cannot read property 'farewell' of undefined
//popup.js console error
jQuery.Deferred exception: chrome.runtime.onMessage.addListner is not a function TypeError: chrome.runtime.onMessage.addListner is not a function:
Uncaught TypeError: chrome.runtime.onMessage.addListner is not a function
UPDATE
I've managed how to pass the message from the content script to the popup.js but I need a way to hold the message until the user click on the extension icon. How can I achieve also this?
In general, it will not work to send a message from a content script to a popup unless you know the popup is open: the popup does not exist until you open it.
Given this, it will likely be most decoupled to have your content script send its message to a persistent background (which is the default btw) and serve as the repository for your messages until the popup requests them.
background.js
const messageQueue = [];
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Arbitrary string allowing the background to distinguish
// message types. You might also be able to determine this
// from the `sender`.
if (message.type === 'from_content_script') {
messageQueue.push(message);
} else if (message.type === 'from_popup') {
sendResponse(messageQueue);
}
});
Now from content script, send chrome.runtime.sendMessage({imageURIs: l, type: 'from_content_script'}... and from the popup send
chrome.runtime.sendMessage({type: 'from_popup'}, (response) => {
// do stuff with response (which will be the value of messageQueue
// sent from background.js).
});
Of course the strings 'from_popup' and 'from_content_script' mean nothing; they are just there for clarity.
If you need the popup to initiate the flow, you will need to:
send a message from the popup
to the background, to send a message to the content scripts
which should respond to the background
which should respond to the popup
Chrome runtime have not onMessage method please see this link,hope this will hep you

Chrome Extension: How to run my content.js in actual webpage instead of popup.html

I have a extension that is doing all kind of stuff with any given website. Everything worked well before i added popup window in purpose to use it as some kind of UI for the extension. After i have found it impossible to get my code run anywhere outside of that popup..
This sample is from my previous unsuccessful try:
HTML is just a start (starts initSession function) and stop button with access to content.js. I'am looking for solution (if there is a one) where i don't have to inject the actual page and could handle this with messages somehow..
content.js:
function initSession() {
window.close(); //popup..
chrome.runtime.sendMessage({
message: 'popup_closed'
}, function(response) {
var response = response.message;
if(response === "ready_to_record")
startRecording();
});
}
function startRecording(){
createListeners();
}
//background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse, tab) {
if (request.message == "popup_closed")
sendResponse({message: "ready_to_record"});
});

chrome extension - passing data from background to custom html page

Creating browser extension where I have to open new tab from background.js and pass JSON data to this new tab. In new tab I am manipulating/rendering DOM using passed JSON data.
Below is part of my background.js where I create new tab with custom URL and send JSON data object
....
var analyticsUrl = chrome.extension.getURL("analytics.html");
chrome.tabs.create({ url: analyticsUrl, selected: true }, sendDataToAnalytics);
function sendDataToAnalytics(tab)
{
console.log(JSON.stringify(txnDataJSON));
chrome.tabs.sendMessage(tab.id, {"action" : "renderChartsTxns", "txns" : JSON.stringify(txnDataJSON)});
}
....
My custom analytics.html page has
<script src="analytics.js" type="text/javascript"></script>
And analytics.js looks like below
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action == "renderChartsTxns")
{
// JSON parsing and analytics.html DOM processing
});
Problem is, my analytics.js listener is never receiving any messages. I've confirmed that background.js is sending JSON message as expected (using background page debugging)
BTW, analytics.js/html are not registered as part of manifest.json file but these files are part of extension package.
I did this setup today morning and everything was working properly for few hours (was able to receive JSON data in analytics.js), not sure what changed later and I lost message receiving in analytics.js (for debugging I tried clearing browser cache, uninstall and reinstalled chrome and much more but no luck yet!)
The callback of chrome.tabs.create returns as soon as the tab is created, not after it fully loads.
As such, you have a race condition: your message is potentially sent before the listener is initialized. Since it's a race condition, it can sometimes work.
The correct logic here would be to send a message requesting data from the newly opened tab, and use sendResponse to pass that data from the background.
// analytics.js
chrome.runtime.sendMessage({"action" : "getTxns"}, function(txns) {
// Process the data
});
// background.js
// Register this before opening the tab
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action == "getTxns") {
sendResponse(txnDataJSON); // No need to serialize yourself!
}
});

Chrome extension: create tab then inject content script into it

Upon receiving a message from a content script I want to create a new tab and fill the page it opens dynamically (for now I'm just trying to turn the newly created page red).
eventPage.js:
// ... code that injects another content script, works fine
// Problem code...
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse)
{
chrome.tabs.create({url: chrome.extension.getURL("blankpage.html")},
turnTabRed);
});
function turnTabRed(tab)
{
chrome.tabs.executeScript(
tab.id,
{code: 'document.body.style.backgroundColor="red"'}
);
}
This successfully creates a new tab and loads blankpage.html (which is just an HTML page with some text) fine, but fails to paint the background colour red. After inserting console.log() statements in various places and fooling around in the debugger, I have ascertained that turnTabRed is being called, tab.id is indeed the ID of the newly created tab and if I call document.body.style.backgroundColor="red" from the console, the background of the new tab turns red. I noticed that if I added
(*)
chrome.tabs.query(
{}, function (tabArr) { for (var i = 0; tabArr[i]; i++)
console.log(tabArr[i].title); });
into the body of turnTabRed the title of the new tab would not be printed into the console, which suggested that the script was being injected too early, so I tried delaying the injection with setTimeout and when that failed, I tried listening for the status-complete event:
function turnTabRed(tab)
{
chrome.tabs.onUpdated.addListener(
function(tabUpdatedId, changeInfo, tabUpdated)
{
if (tabUpdatedId == tab.id && changeInfo.status &&
changeInfo.status == 'complete')
chrome.tabs.executeScript(
tabUpdatedId,
{code: 'document.body.style.backgroundColor="red"'});
});
}
which also failed. Calling (*) after waiting with setTimeout did print the new tab's title along with all the others though.
What's wrong? How can I create a new tab, load an HTML page and then turn its background red?
The problem is that you cannot inject scripts into chrome-extension://* pages (which your blankpage.html is).
For example, change
{url: chrome.extension.getURL("blankpage.html")}
to
{url: "http://www.google.com"}
in your original codeblock and it will change the background to red. As far as I know there is no way around injecting into chrome-extension://* pages (I would assume the reason for this is that it is a giant security concern). I'm not sure what your extension is trying to do, but injecting into a "live" page should work...maybe you can create some API to generate a new "blankpage" on your server whenever the chrome.runtime.onMessage.addListener fires?
EDIT
So you can't inject stuff into chrome-extension://* pages, but you can pass messages to them and use some subset of chrome API's inside those new pages as mentioned below. So using message passing you will be able to do exactly what you want (modify the new page), albeit in a roundabout way. Here is a really simple proof of concept that works for me:
eventPage.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse)
{
chrome.tabs.create({url: chrome.extension.getURL("blankpage.html")}, turnTabRed);
});
function turnTabRed(tab)
{
chrome.tabs.sendMessage(tab.id, {"action" : "setBackground"});
}
blankpage.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if(request.action == "setBackground") document.body.style.background = "red";
});

Categories