chrome.tabs.create not working - javascript

Because my content script cannot use all the chrome API tools, I am sending a message from my content script to my background script. When received, the background script is supposed to open a new tab containing an html file I had made.
This is sending the message from the content script...
chrome.runtime.sendMessage({permission: true}, function(response) {
console.log(response.access);
});
This is the code to receive the message in my background script...
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.permission == true) {
chrome.tabs.create({'url': chrome.extension.getURL('./src/permission.html')}, function(tab) {
sendResponse({access: "yes"});
});
}
});
The message is received, I have already tested that. But when I add the following code...
chrome.tabs.create({'url': chrome.extension.getURL('./src/permission.html')}, function(tab) {
...etc
I get an error saying response not received. Meaning something must have broke inside my chrome.tabs.create. Why is it breaking?
The permission.html path is relative to the background script.
What I want is for a new tab to be created when the message is received.

I'm not sure if this has any affect, but the content scripts and the background scripts communicate asyncronously so for you to use the sendResponse callback, you'll need to return true; at the end of your onMessage anonymous function.
Chrome onMessage return true
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.permission == true) {
chrome.tabs.create({'url': chrome.extension.getURL('./src/permission.html')}, function(tab) {
sendResponse({access: "yes"});
});
}
return true; //to tell the content script to look out for sendResponse
});
Again, I'm not sure if this will solve your problem, but regardless, your response using sendResponse will not work without returning true at the end of the listener
Also, BTW, chome.extension.getURL() does not need a dot-slash so chrome.extension.getURL('src/permission.html') should be enough.
Have you tried just running
chrome.tabs.create({'url': chrome.extension.getURL('src/permission.html')});
(with or without the dot-slash) to see if the tab opens up?

Related

Why is my message being sent from Chrome page action popup not being received by content script (Chrome Extension)?

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

Chrome extension browser action only works once

When a user clicks the browser icon, spiderSimulator() should run.
I can get it to work – once.
Like, I’ll click the icon and it does what it’s supposed to. But if I refresh the page and click it a second time, it doesn’t work. if I go to another site and try again, it doesn’t work.
If I update the extension, refresh the page, and clear the cache, it works again – sometimes. it’s super inconsistent.
Any idea what’s going wrong here?
content.js
// Adds an event listener to the browser icon to sends a message to background.js.
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "clicked_browser_action") {
console.log( 'send message' );
chrome.runtime.sendMessage({"message": "spider_simulator"});
}
}
);
background.js
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function (tab) {
// Send a message to the active tab
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
var activeTab = tabs[0];
console.log('browser action');
chrome.tabs.sendMessage(activeTab.id, {"message": "clicked_browser_action"});
});
});
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "spider_simulator") {
console.log('run the spider');
spiderSimulator()
}
}
);
// removes the site's stylesheet and replaces it with my own;
// turns all page elements into nested list items.
function spiderSimulator() {
console.log( 'inject stylesheet' );
chrome.tabs.insertCSS({
file: 'search-spider-simulator.css'
});
console.log( 'execute script' );
chrome.tabs.executeScript({
file: 'search-spider-simulator.js'
});
window.close();
}
As mentioned by wOxxOm, window.close() executed in the background page acts on itself - essentially terminating the background portion of your extension.
Not sure what was meant there; if you need to close the tab you're talking to, then this also needs to be executeScript'ed.

Passing message from one listener to another

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.

Chrome Extension: Message Passing (Sending the DOM to popup.js) returns 'null'

I would like to use a Chrome Extension to download the current page's DOM. I'm not sure why, but when my download occurs, the result is just a text file with either 'null' or 'undefined', rather than the DOM. I've tried to assimilate the knowledge from here and here, but I can't seem to get the message from content.js through to popup.js.
Additionally, I'm not sure why this actually works. When I read the docs, it seems like I need to send the message from popup.js to content.js by selecting the active tab:
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {message: 'getMessageFromContent'}, function(response) {
//Code to handle response from content.js
}
});
My current code:
content.js
var page_html = DOMtoString(document);
chrome.runtime.sendMessage({method: 'downloadPageDOM', pageDOM: thisPage});
function DOMtoString(document_root) { ... }
background.js
chrome.tabs.query({currentWindow: true, active: true}, function(tab) {
var page_html;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == 'downloadPageDOM')
page_html = request.pageDOM;
else if (request.message == 'getPageDOM')
sendResponse(page_html);
});
});
popup.js
document.addEventListener('DOMContentLoaded', function() {
var download_button = document.getElementById('download_button');
download_button.addEventListener('click', function() {
chrome.runtime.sendMessage({message:'getPageDOM'}, function(response) {
download(response, "download.html", "text/html");
});
});
});
function download(data, fileName, mimeType) { ... }
I feel like I'm missing a crucial understanding of how message passing works. If anyone could take a second to help me understand why the file that downloads just has 'null', I would sincerely appreciate it.
You're over-complicating this, which leads to a lot of logical errors.
You've set up the background page to act like a message proxy, and the content script itself triggers updating your page_html variable. Then the popup pulls that data with another message.
Note that page_html will not contain the current tab's data in any case: you're overwriting this data with the last loaded tab.
What you can do is completely cut out the middleman (i.e. background.js). I guess you got confused by the fact that sending a message TO a popup is a generally a bad idea (no guarantee it's open), but the other way around is usually safe (and you can make it always safe).
Solution 1 (bad, but here for educational purposes)
The logic of your app is: once the user clicks the button, make the snapshot at that moment. So, instead of making your content script do its work immediately, add a message listener:
// content.js
chrome.runtime.onMessage(function(message, sender, sendResponse) {
else if (request.message == 'getPageDOM')
sendResponse(DOMtoString(document));
});
function DOMtoString(document_root) { ... }
And in your popup, request it:
// popup.js
// (Inside the click listener)
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
// Note that sending a message to a content script is different
chrome.tabs.sendMessage(tabs[0].id, {message:'getPageDOM'}, function(response) {
download(response, "download.html", "text/html");
});
});
However, this solution is not 100% robust. It will fail if the content script is not injected into the page (and this can happen). But it's possible to fix this.
Solution 2
Let's not assume the content script is injected. In fact, most of the time you don't NEED to inject it automatically, only when the user clicks your button.
So, remove the content script from the manifest, make sure you have host permissions ("<all_urls>" works well, though consider activeTab permission), and the use programmatic injection.
There is a little-used form of programmatic injection that collects the value of the last executed statement. We're going to use that.
// content.js
DOMtoString(document); // This will be the last executed statement
function DOMtoString(document_root) { ... }
In the popup, execute script, collect results:
// popup.js
// (Inside the click listener)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function(data) {
// Data is an array of values, in case it was executed in multiple tabs/frames
download(data[0], "download.html", "text/html");
});
});
NOTE: All of the above assumes that your function DOMtoString actually works.

Chrome extension: dealing with multiple message listeners

My extension has chrome.runtime.onMessage.addListener() defined in both the background page and a content script. Some messages need to be caught by the background page, others by the content script. My problem is that for some reason one of my content scripts does not catch messages.
My background page contains:
chrome.runtime.onMessage.addListener(function (msg, sender, respond) {
if (msg["injectCss"]) {
chrome.runtime.sendMessage({ beginInject: msg.injectCss.files.length });
injectCss(sender.tab.id, msg.injectCss.files, 0, function () {
chrome.runtime.sendMessage({ endInject: true });
respond();
});
}
});
My content script contains:
chrome.runtime.onMessage.addListener(function (msg, sender, respond) {
if (msg["beginInject"]) {
//do something
respond();
}
});
When chrome.runtime.sendMessage({ beginInject: msg.injectCss.files.length }); gets called from the background page, the background page itself catches the message rather than the content script.
Question: Is there a way to mark a message as "not handled yet" so that other listeners can have a chance to catch it and handle it?
Turns out the answer was fairly simple:
In order to send a message specifically to a content script, use chrome.tabs.sendMessage(tabId, message, callback). To send a message anywhere else, use chrome.runtime.sendMessage(message, callback).

Categories

Resources