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;
}
Related
I'm creating a Chrome Extension, with a content-script.js, a background.js, and a popup.html. It runs a sentiment analysis on editable areas like #textarea.
To communicate between the content-script.js and the background.js, I'm using chrome.runtime.sendMessage(message), and running the analyses on background.js and saving the results to window.text. If there is a negative sentiment, I send a message back to the page to show an image of a stop sign.
Then on the popup.html (which is really a .vue file), so
mounted(){
this.bgpage = chrome.extension.getBackgroundPage()
this.text = this.bgpage.text
}
and displaying the {{text}} in the template.
It worked perfectly. But I didn't love that users would have to click the button on the browser_action to engage with the popup.
So I added this listener and function to the content-script.js:
document.addEventListener("click", clickStop)
function clickStop(event) {
let target = event.target.getAttribute('id');
if (target == "stop"){
let message = {
stop: true
}
chrome.runtime.sendMessage(message)
}
and this to the background.js
function receiver(request) {
if (request.stop == true){
window.open("popup.html", "default_title", "width=375,height=600,titlebar=no,scrollbars=yes,resizable=no,top=0,left=0");
}
and this works as well. Now I have a popup when a user clicks on the stop sign.
The problem is that it seems that the new popup window doesn't access the chrome.extension.getBackgroundPage(), which means that the {{text}} doesn't show up, like it would if the user would click on the actual browser_action button. (chrome.runtime.getBackgroundPage() crashes everything for some reason.)
I've added the permissions for background to to the manifest.json:
"permissions": [
"activeTab",
"<all_urls>",
"*://*/*",
"tabs",
"background"
],
"background": {
"scripts": ["js/background.js"],
"persistent" : true
},
"browser_action": {
"default_popup": "popup.html",
"default_title": "default_popup",
"default_icon": {
"19": "icons/stopsign.png",
"38": "icons/stopsign.png"
}
Basically I'm trying to figure out how to get the literal new popup window to work just like the browser_action.
The problem lay in a part of the code that I didn't share, because I hadn't thought it was relevant, and I was wrong.
Basically, I had two different chrome.runtime.sendMessage(message); on my content-script.js: one had an if statement on the receiver and the other did not. So every time I sent the request.stop message, the request.text was undefined and therefore caused window.text to become undefined.
Therefore, it worked on my browser-action popup, because there was no request.stop message sent, so window.text was still defined by the last message.
Once I added the second conditional statement, that would not run when the request.stop message was received.
window.text = "there is no text";
function receiver(request) {
if (request.stop == true){
window.open("popup.html", "default_title", "width=375,height=600,titlebar=no,scrollbars=yes,resizable=no,top=0,left=0");
}
//removed this
//window.text = request.text;
//added this - if statement
if (request.text){
window.text = request.text;
}
}
I'm trying to automate the task of taking customers data from an ebay page and inserting it into a form in another page. I used Imacros but i don't quite like it.
Are chrome extensions good for this kind of work?
And if yes, where the dom logic should go, on the background page or in the content script?
Can anyone provide me a simple example of code?
NOTE: since January 2021, use Manifest V3 with chrome.scripting.executeScript() and the scripting permission and move <all_urls> to host_permissions instead of using the deprecated chrome.tabs.executeScript() with the tabs permission.
Task
What you need here is a Chrome extension with the ability to read DOM content of the customer page inside a tab with a content script, and then store the information and send it to another tab.
Basically, you'll need to:
Inject a content script in the customer page
Retrieve the data and send it to the background
Elaborate the data and send it to another content script, that will:
Insert the data in the form on another page
Implementation:
So, first of all, your manifest.json will need the permission to access the tabs and the URLs you need, plus the declaration for your background script, something like this:
{
"manifest_version": 2,
"name": "Extension name",
"description": "Your description...",
"version": "1",
"permissions": [
"<all_urls>",
"tabs"
],
"background": { "scripts": ["background.js"] }
}
Now, following the steps:
Add a listener to chrome.tabs.onUpdated to find the customer page and inject the first content script, plus find the tab with the form and inject the second script, all from background.js:
chrome.tabs.onUpdated.addListener(function(tabID, info, tab) {
if (~tab.url.indexOf("someWord")) {
chrome.tabs.executeScript(tabID, {file: "get_data.js"});
// first script to get data
}
if (~tab.url.indexOf("someOtherWord")) {
chrome.tabs.executeScript(tabID, {file: "use_data.js"});
// second script to use the data in the form
}
});
Ok, now the above code will inject your get_data.js script in the page if its URL contains "someWord" and use_data.js if its URL contains "someOtherWord" (you must obviously replace "someWord" and "someOtherWord" with the right words to identify the correct page URLs).
Now, in your get_data.js you'll need to retrieve data and send it to the background.js script, using chrome.runtime.sendMessage, something like this:
var myData = document.getElementById("some-id").textContent;
// just an example
chrome.runtime.sendMessage({messgae: "here is the data", data: myData});
Now you've sent the data, so inside background.js you'll need a listener to catch and elaborate it:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message = "here is the data") {
// elaborate it
chrome.tabs.query({url: "*://some/page/to/match/*"}, function(tabs) {
chrome.tabs.sendMessage(tab[0].id, {message: "here is the data", data: request.data});
});
}
});
Ok, you are almost finished, now you'll need to listen to that message in the second content script, which is use_data.js, and use it in the form:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == "here is the data") {
// use the data to do something in the page:
var myData = request.data;
// for example:
document.getElementById("input-id").textContent = data;
}
});
And you are done!
Documentation
This wast just an example, and I strongly recommend you to check out the documentation to fully understand the functions and methods to use, here are some useful links:
chrome.tabs
.query
.onUpdated
.sendMessage
.executeScript
chrome.runtime
.onMessage
.sendMessage
Chrome extension message passing
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.
I'm working on some code that takes an input from a text box in a browser popup and then relays that input to the background.js in order to filter a webpage using that input.
If I hard code the filtering on background.js at the start then it works (because background.js runs once at the start), but if I put the filtering inside a function that receives the input from the text box in popup.js it doesn't work.
popup.js
$(document).ready(function(){
$('#zim').click(function(){
// Get User Input in Text Box
var author = document.getElementById('author').value;
// Pass author variable to background.js
chrome.extension.getBackgroundPage().StartFiltering(author);
});
});
background.js
function StartFiltering(person){
console.log("Starting Filtering");
console.log("person: " +person);
$( "a:contains('" + person + "')" ).closest('.post-wrapper.js_post-wrapper.wide.postlist-dense').remove();
$( ".text-upper:contains('" + person + "')" ).closest('.post-wrapper.js_post-wrapper.wide.postlist-dense').remove();
$( "a:contains('" + person + "')" ).closest('.post-wrapper.js_post-wrapper.postlist-dense').remove();
};
manifest
{
"name": "Filter",
"description": "Filter out authors on homepage",
"version": "2.0",
"permissions": [
"activeTab"
],
"background": {
"scripts": ["jquery.js","background.js"],
"persistent": false
},
"icons": {
"128": "128.png"
},
"browser_action": {
"default_title": "Filter",
"default_icon": "filter.png",
"default_popup": "popup.html"
},
"manifest_version": 2,
"content_scripts": [
{
"js": ["jquery.js","background.js"],
"matches": [ "http://example.com/*"]
}
]
}
On background.js if I put the 3 lines of jQuery outside the function, and hard code in the "person" variable and reload the extension then it will filter the website correctly. StartFiltering definitely runs, it definitely gets the "author" input from the user, but I think because background.js is only run at the start, it doesn't know to update the file? I'm not sure! Fairly new to JS and coding in general!
Have searched around on here but I can't find anyone with the same problem! Thanks in advance for any help!
EDIT - SOLVED!
So here's how I got this working...
After ExpertSystem pointed out that I was using background.js twice I cleaned up my manifest and file system so that I had 4 files: background.js, content.js, popup.js and the manifest. In my content_scripts section of the manifest I had jquery.js and content.js instead of background.js as before.
I had popup.js send a message to background.js whenever a user entered a value in the popup text box, this was done very simply as:
popup.js
chrome.runtime.sendMessage({type: "new author", author:author});
I had background.js listen in for the message, then if the message type matched the one sent from popup.js then it would store the value of the blocked author in an array (because eventually I planned to keep a list of authors to filter), and send the array as a message:
background.js
chrome.runtime.onMessage.addListener(
function(request,sender,sendResponse) {
if(request.type == "new author") {
blocked.push(request.author)
}
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id,{filter:"author",blocked:blocked})
});
});
Then I had content.js listen in for the message:
content.js
chrome.runtime.onMessage.addListener(function(msg,sender){
if (msg.filter == "author"){
for (var i=0;i<msg.blocked.length;i++){
startFiltering(msg.blocked[i]);
}
}
});
Still needs tweaking but I have all 3 pages communicating now!
The problem
You are using background.js both as your background-page (actually event-page) and as your content-script. So, when you visit http://example.com/* there are in fact two separate instances of background.js: One injected into the webpage and one in your generated background-page. (By the way, you can access your background-page at chrome://extensions/ once you enable the developer mode.)
The solution
Don't use the same file as background-page and content script.
From popup.html pass a message to the content script injected into the webpage.
Have the content script do its magic (a.k.a. filtering).
Example (~ untested code alert ~):
// In popup
var author = ...;
chrome.tabs.getCurrent(function(currentTab) {
chrome.tabs.sendMessage(currentTab.id, {
action: 'filter',
person: author
});
});
.
// In content script
function filter(person) {
$('a:contains(' + person + ')').
closest('.post-wrapper.js_post-wrapper.wide.postlist-dense').
remove();
...
}
chrome.runtime.onMessage.addListener(function(msg) {
if ((msg.action === 'filter') && msg.person) {
filter(msg.person);
}
});
I'm toying around with Chrome trying to create my first extension. Basically, I want to create a script that does some DOM manipulation on a particular domain. Furthermore, I want the user to be able to toggle the script through an icon displayed in the address bar, when visiting that particular domain.
So far, I've got this manifest.json:
{
"manifest_version": 2,
"name": "Ekstrafri",
"description": "Removes annoying boxes for paid articles.",
"version": "1.0",
"page_action": {
"default_title": "Foobar"
},
"content_scripts": [
{
"matches": ["http://ekstrabladet.dk/*"],
"js": ["jquery.min.js", "cleaner.js"],
"run_at": "document_end"
}
]
}
cleaner.js contains a couple of jQuery DOM selectors that removes some stuff.
The current setup works, but the context script is injected all the time. I want the user to be able to toggle, which should trigger a confirmation prompt in which the user accepts or rejects a page reload.
Anyway, page_action doesn't seem to display any icon. According to the documentation, it should display an icon in the address bar.
I have two questions:
How do I display this page_action icon on the matched content?
How do I bind an event to that icon?
One thing you could do here is get and set a variable in a background.js page using the message passing framework. Essentially when the content-script runs you can contact the background script to check the state of a boolean variable. You can then determine whether or not to execute the rest of the script.
chrome.extension.sendMessage({ cmd: "runScript" }, function (response) {
if (response == true) {
//Run the rest of your script inside here...
}
});
You would also use this initial call to bind some UI on the page so you can toggle this state (i.e. switch the content script on/off). In this example I'm using a checkbox.
$("chkOnOff").click(function(){
var scriptOn = ($(this).is(":checked");
chrome.extension.sendMessage({ cmd: "updateRunScriptState", data: { "scriptOn" : scriptOn } }, function (response) {
//Update confirmed
});
});
The background.js page would look something like this:
//Global variable in background.js
var scriptOn = true;
chrome.extension.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.cmd == "runScript") {
sendResponse(scriptOn);
}
if (request.cmd == "updateRunScriptState") {
//here we can update the state
scriptOn = request.data.scriptOn;
}
});
And don't forget to register the background.js page in the manifest.
..
"background": {
"scripts" : ["background.js"]
},
..