I'm working on a small Chrome extension that needs to run in the background. However, I understand that that isn't possible when I'm using a popup. After some reading it seems that the best option is to create popup.js in order run the background.js, using chrome.extension.getBackgroundPage() function.
Can someone please show me an example of how it's done?
here's the manifest:
"browser_action": {
"permissions": ["background"],
"default_popup": "popup.html"},
"options_page": "options.html",
"background": {
"scripts": ["background.js"],
"persistent" : true
}
I've included the popup.js reference in popup.html:
<script src="popup.js"></script>
And created a variable in popup.js
var bkg = chrome.runtime.getBackgroundPage();
so now I need a way to activate the background.js
Do I need to run the relevant function inside background.js from popup.js,
or give a general command for the background.js to run?
Yes, you need to call function from background in your popup. Here's a simple example which demonstrates how it works.
background.js
function backgroundFunction () {
return "hello from the background!"
}
popup.js
(function () {
var otherWindows = chrome.extension.getBackgroundPage();
console.log(otherWindows.backgroundFunction());
})();
When you inspect your popup.js you'll see "hello from the background!" in your console. GetBackgroundPage() simply returns window object for your background page, as you probably know all variables are attached to this window object so in this way you will get access to function defined in background scripts.
There is a sample code demonstrating this in chrome documentation see Idle Simple Example and look at file history.js
The background page is loaded then you extension is loaded into Chrome.
Consider the popup page just as a normal web-page: here, you need to use chrome.runtime to make requests to the background page.
Usually, I do it using an implicit (temporal) or explicit (permanent) channel. With temporal channel:
popup.js
chrome.runtime.onMessage.addListener(function (answer) { /* your code */ });
chrome.runtime.sendMessage({cmd: "shutdown"});
background.js
chrome.runtime.onMessage.addListener(function (request) {
if (request.cmd === "shutdown") {
shutdown();
}
});
With permanent channel:
popup.js
var port = chrome.runtime.connect({name: "myChannel"});
port.onMessage.addListener(function (answer) { /* your code */ });
port.postMessage({cmd: "shutdown"});
background.js
chrome.runtime.onConnect.addListener(function (port) {
port.onMessage.addListener(function (request) {
if (request.cmd === "shutdown") {
shutdown();
}
}
});
UPD. While this way of content-backgrounf communication is fully functional, the current specs advice using chrome.runtime.getBackgroundPage() to call a function in the background script is the requests shouldn't be asynchronous (thus, less coding is needed and easier to read the code). See the manual and other answers to this post to clarify this matter.
Related
I am trying to build a Chrome plugin (76.0.3809.132 version of Chrome) that:
Injects a JavaScript block in a web page (done)
Get the injected block to pass back message to the chrome plugin using the latest Chrome runtime functionality (problem)
I initially referred to the Chrome developer reference at: https://developer.chrome.com/extensions/messaging#external-webpage
Accordingly, my Manifest is:
{
"manifest_version": 2,
"name": "Message Passing",
"description": "Simple messaging plugin",
"version": "1.1",
"externally_connectable": {
"matches": ["*://*.myexample.com/*"]
},
"permissions": [
"activeTab",
"storage",
"tabs",
"*://*.myexample.com/*"
],
"content_scripts": [
{
"matches": ["*://*.myexample.com/*"],
"run_at": "document_start",
"js": ["myContent.js"]
}
],
"web_accessible_resources": ["injected.js"]
}
My content script:
var s = document.createElement('script');
s.src = chrome.extension.getURL('injected.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
//injecting correctly till this point
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
console.log("In plugin");//never gets called
});
JavaScript within the web page that makes the call to plugin:
//the function called by my web page, sending a JSON or text message
async function sendMessageToPlugin(dataToSend){
console.log('Send to plugin');//this is getting printed
var extID = 'abcdefghijklmnoabcdefhijklmnoabc';
chrome.runtime.sendMessage(extID, dataToSend);
}
No error, no result. I have tried different things, trying to create a background script to listen instead of listening in content script, trying to create a port for persistent messages instead, using "onMessage" instead of "onMessageExternal" per various older posts on the forum, but nothing works :(. What am I doing wrong? Can somebody at least point me towards any actually working example of a web page successfully communicating with the Chrome plugin? Would be good to know what Manifest was used and which part of the extension listened to the message successfully (content script or background). Thank you for your help!
Update 1:
I had some success with using the method described at https://developer.chrome.com/extensions/content_scripts#host-page-communication. Is this the only way of doing this? I would prefer to do it using externally connected functionality, but happy to keep testing this method and run with it if it works.
Update 2:
This code finally works for me:
In my content script-
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
//if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: ");
console.log(event.data);
//}
}, false);
And in my injected script:
async function sendMessageToPlugin(dataToSend) {
console.log('Send to plugin');
window.postMessage(dataToSend, "*");
}
I am sure Jafar's solution will work as well (I will try it and update!), but for now I am sticking with this one. Thank you Jafar for taking out time to reply!
I am currently trying to make a chrome extension that lists all of the open tabs in its popup window. With more functionality to be added later, such as closing a specific tab through the popup, opening up a new tab with a specific URL etc.
manifest.json
{
"manifest_version": 2,
"name": "List your tabs!",
"version": "1.0.0",
"description": "This extension only lists all of your tabs, for now.",
"background": {
"persistent": true,
"scripts": [
"js/background.js"
]
},
"permissions": [
"contextMenus",
"activeTab",
"tabs"
],
"browser_action": {
"default_popup": "popup.html"
}
}
background.js
const tabStorage = {};
(function() {
getTabs();
chrome.tabs.onRemoved.addListener((tab) => {
getTabs();
});
chrome.tabs.onUpdated.addListener((tab) => {
getTabs();
});
}());
function getTabs() {
console.clear();
chrome.windows.getAll({populate:true},function(windows){
windows.forEach(function(window){
window.tabs.forEach(function(tab){
//collect all of the urls here, I will just log them instead
tabStorage.tabUrl = tab.url;
console.log(tabStorage.tabUrl);
});
});
});
chrome.runtime.sendMessage({
msg: "current_tabs",
data: {
subject: "Tabs",
content: tabStorage
}
});
}
popup.js
(function() {
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.msg === "current_tabs") {
// To do something
console.log(request.data.subject)
console.log(request.data.content)
}
}
);
}());
From my understanding, since you're supposed to have listeners in background.js for any changes to your tabs. Then when those occur, you can send a message to popup.js
As you can see, for now I'm simply trying to log my tabs in the console to make sure it works, before appending it to a div or something in my popup.html. This does not work, however, because in my popup.html I'm getting the following error in the console:
popup.js:3 Uncaught TypeError: Cannot read property 'sendMessage' of undefined
so I'm... kind of understanding that I can't use onMessage in popup.js due to certain restrictions, but I also have no clue, then, on how to achieve what I'm trying to do.
Any help would be appreciated.
The Google's documentation about the background script is a bit vague. The important thing for your use case is that the popup runs only when it's shown, it doesn't run when hidden, so you don't need background.js at all, just put everything in popup.js which will run every time your popup is shown, here's your popup.html:
<script src="popup.js"></script>
The error message implies you were opening the html file directly from disk as a file:// page, but it should be opened by clicking the extension icon or via its own URL chrome-extension://id/popup.html where id is your extension's id. This happens automatically when you click the extension icon - the popup is a separate page with that URL, with its own DOM, document, window, and so on.
The popup has its own devtools, see this answer that shows how to invoke it (in Chrome it's by right-clicking inside the popup, then clicking "inspect").
Extension API is asynchronous so the callbacks run at a later point in the future, after the outer code has already completed, which is why you can't use tabStorage outside the callback like you do currently. More info: Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference.
There should be no need to enumerate all the tabs in onRemoved and onUpdated because it may be really slow when there's a hundred of tabs open. Instead you can modify your tabStorage using the parameters provided to the listeners of these events, see the documentation for details. That requires tabStorage to hold the id of each tab so it would make sense to simply keep the entire response from the API as is. Here's a simplified example:
let allTabs = [];
chrome.tabs.query({}, tabs => {
allTabs = tabs;
displayTabs();
});
function displayTabs() {
document.body.appendChild(document.createElement('ul'))
.append(...allTabs.map(createTabElement));
}
function createTabElement(tab) {
const el = document.createElement('li');
el.textContent = tab.id + ': ' + tab.url;
return el;
}
I found several approaches, but they seem outdated or simply won't work for some reasons for me. Maybe tunnelvision:
First things first:
I have the correct permissions in my manifest.json, I think:
"permissions": [
"tabs",
"activeTab"
]
I have 2 simple scripts, background.js and content.js (which are recognized correctly, the error can't be here).
In my background.js I tried several approaches:
chrome.browserAction.onClicked.addListener(buttonClicked);
var sharedUrl;
function buttonClicked(tab) {
chrome.tabs.getCurrent(function(tab) {
// please read further, this was my last resort, I tried other stuff as well
sharedUrl = console.log(window.location.href);
});
let msg = {
txt: "Hello",
url: sharedUrl
}
chrome.tabs.sendMessage(tab.id, msg);
}
I tried it with getCurrent() and then tab.url, but that didn't work (neither with tab[0].url
I tried it also with getSelected() as well as with something like this:
chrome.tabs.query({active: true, currentWindow: true}, function(arrayOfTabs) {
var activeTab = arrayOfTabs[0];
});
and my content.js is simply this here:
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message, sender, sendResponse) {
console.log(message.txt);
console.log(message.url);
}
It displays "Hello", but not the URL I'm looking for.
Edit:
It might of importance, that I want to retrieve the url after a button-click in my extension.
Thanks for the feedback and help.
Ok, based on the documentation you are not able to grab the tab object while you are not in the tab context. The tab context includes only content scripts. So you can't access to tab because you are calling it from your backend page. You can only do it, if your extension has generated the tab.
Gets the tab that this script call is being made from. May be
undefined if called from a non-tab context (for example: a background
page or popup view).
So, the only possible way is to change your extension data flow.
I'm developing an extension for Google Chrome that requires a Firebase database. It works fine, "popup.html" is currently open. But when it closes, my background.js stops working.
I need a solution that will allow background.js to work in the background, even when the popup.html is closed, or the browser is minimized.
Here is my background.js:
var db = firebase.database().ref();
var clipText;
setInterval(function() {
document.addEventListener('paste', function(event) {
clipText = event.clipboardData.getData('Text');
if (clipText != null) {
db.child('data').set(clipText);
} else {
console.log('Cliptext is null...');
}
});
document.execCommand('paste');
}, 3000)
As you see, it uses "setInterval" function to do some actions in background every 3 seconds.
Based on your comment you are declaring your background.js in the content script.
You need to declare it inside your manifest.json like this for example:
"background": {
"persistent": true,
"scripts": ["lib/jquery-3.1.1.min.js", "background.js"]
},
You can read about Chrome Extensions architecture here.
I've been googling around extensively trying to remedy this problem but can't seem to find a solution. I'm trying to do the simple task of setting up a listener and sender in my Chrome extension.
My manifest
{
"manifest_version": 2,
"name": "my app",
"description": "text",
"version": "0.1",
"background":{
"scripts":["background.js"]
},
"content_scripts": [
{
// http://developer.chrome.com/extensions/match_patterns.html
"matches": ["http://myurl.com/*"],
"js": ["jquery-1.9.1.min.js", "myapp.js"],
"all_frames": true
}
],
"browser_action": {
"default_icon": "/icons/icon-mini.png",
"default_popup": "popup.html"
}
}
In my background JS
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
In my popup.js (rendered by coffeescript, please forgive the sort of strange syntax)
(function() {
$(function() {});
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (console.log(sender.tab)) {
"from a content script:" + sender.tab.url;
} else {
"from the extension";
}
if (request.greeting === "hello") {
return sendResponse({
farewell: "goodbye"
});
}
});
}).call(this);
In my myapp.js
chrome.extension.sendMessage({
greeting: "hello"
}, function(response) {
return console.log(response.farewell);
});
I've followed the tutorial. Not sure why this isn't working. I'm pretty decent with JS, very unclear as to why this is behaving strangely. Any help would be hugely appreciated!
There is more than one problem with this code so let me break it down.
From what I see you are trying to send a message from your content script to your popup and there is a background page not doing anything.
Problem #1
The code in the popup.js, besides being strangely convoluted, is not a background page. It only runs when the popup is open, so it will not be able to listen for the message.
Problem #2
The code in the background page is using the depreciated getSelected method to send a message to the content script. The content script has no listener.
The result of these two things is this:
Background page -> content script (no listener)
Content Script -> extension pages (no listener)
I suggest making your background page the hub of your communications. If you need to communicate between your popup and content script make it popup -> content script and use sendResponse() to reply.
Edit: Here is an example of the message passing you would want. Just replace with your variables.
Content Script
...
//get all of your info ready here
chrome.extension.onMessage.addListener(function(message,sender,sendResponse){
//this will fire when asked for info by the popup
sendResponse(arrayWithAllTheInfoInIt);
});
Popup
...
chrome.tabs.query({'active': true,'currentWindow':true},function(tab){
//Be aware 'tab' is an array of tabs even though it only has 1 tab in it
chrome.tabs.sendMessage(tab[0].id,"stuff", function(response){
//response will be the arrayWithAllTheInfoInIt that we sent back
//you can do whatever you want with it here
//I will just output it in console
console.log(JSON.stringify(response));
});
});
I had a similar problem in a background page and my solution was to ensure that the tab had completed loading before trying to send it a message.
If the tab has not fully loaded, the content script will not have started and will not be waiting for messages yet.
Here's some code:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status === 'complete') {
// can send message to this tab now as it has finished loading
}
}
So if you want to send a message to the active tab, you can make sure it has completed loading first.