I've made a chrome extension that downloads some stuff from a certain site. Basically, it goes around through all links I'm interested in, stores them in an array and then downloads it one by one. The thing is, the storing is done in a separate file called download.js. Then, I proceed to send a message to popup.js using chrome.extension.sendRequest. I pick it up in popup.js with chrome.extension.onRequest.addListener. It works perfectly when I don't switch my tab, but I'd like it to work while I'm browsing some other stuff in the meantime. I can see the code reaching the point to send the request to popup.js through a console log, but I can't see what's going on in popup.js because when I switch my tab the popup console immediately closes.
download.js:
// logic behind link gathering and storing that works
...
gatherLinks().then(function() {
// logs is defined, don't worry
chrome.extension.sendRequest(logs);
)};
popup.js:
document.addEventListener('DOMContentLoaded', function() {
var downloadButton = document.getElementById('download');
downloadButton.addEventListener('click', downloadStuff);
});
function downloadStuff() {
chrome.tabs.executeScript({
file: 'jquery-3.1.1.min.js'
});
chrome.tabs.executeScript({
file: 'download.js'
});
chrome.extension.onRequest.addListener(function(message_logs) {
message_logs.forEach(function(log) {
chrome.downloads.download({
url: log.link,
filename: log.filename
});
}
}
manifest.json:
{
...
"permissions": [
"tabs",
"activeTab",
"downloads"
],
"background": {
"scripts": ["download.js"]
}
}
When you switch away, the popup window closes.
It does not hide the popup, the popup is properly closed, as you would close a tab. Therefore, its code is no longer executing and there's nothing to listen for your messages.
This is a job for background (or better, event) pages. They exist, invisibly, independent of what you're doing with the browser. Therefore, such a page should be the one to receive commands when the popup may not exist.
Also,
BIG SCARY WARNING!
If your content script and your background script are the same, there is a 99% probability you're doing something wrong. Do not try to reuse code in both, unless it's some auxilliary library - main logic should never be the same in those very different contexts.
Related
In my background script, or anywhere but a content script, I am trying to listen for the active tab, or a newly opened tab.
I have this, but this is apparently incorrect:
What I am trying to do is to inject a content-script into the active tab. I don't want my content script to run for every tab/window, just for select tabs.
Does anyone know how to inject a content script for certain tabs? I can't figure it out. I assume the best way to inject content scripts is from the background script.
If you want to run a code in already activated tab (chrome.tabs.query):
chrome.tabs.query({active: true, currentWindow: true}, function(foundTabs) {
const activeTabId = foundTabs[0].id;
chrome.tabs.executeScript(activeTabId, {
file: 'inject.js'
});
})
In case you want to inject a file into every newly activated tab (chrome.tabs.onActivated):
chrome.tabs.onActivated.addListener(function(activeInfo) {
chrome.tabs.executeScript(activeInfo.tabId, {
file: 'inject.js'
});
});
Be sure to check if you have injected the file already, in order to prevent multiple injections.
For both cases, permissions should include tabs.
I was missing the:
"webNavigation"
permission in manifest.json. After adding that permission, I now have:
chrome.webNavigation.onDOMContentLoaded.addListener(function (details) {
const tabId = details.tabId;
chrome.tabs.executeScript(tabId, {
file: 'inject.js'
});
});
now it works.
some chrome API we cant access in content script in that case do that code in background.js and do according action from background js. if you want to do some action in content script in that case you need to communicate using send message
I'm trying to build a basic Chrome extension that, from a browser action popup, opens a website in a new tab, and fills in the login credentials. I can get the Chrome extension to open the new page but can't seem to get it to input text into the input fields.
Manifest.json
{
"manifest_version": 2,
"name": "Vena",
"description": "This extension will allow users to login to vena accounts",
"version": "1.0",
"browser_action": {
"default_icon": "images/icon.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab"
]
}
popup.html
<!doctype html>
<html>
<head>
<title>Auto-Login</title>
<script src="popup.js"></script>
</head>
<body>
<h1>Login</h1>
<button id="checkPage">Login!</button>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function() {
var newURL = "https://vena.io/";
chrome.tabs.create({ url: newURL });
var loginField = document.getElementsByClassName('js-email form-control input-lg');
var passwordField = document.getElementsByClassName('js-password form-control input-lg');
loginField.value = 'gsand';
passwordField.value = '123';
}, false);
}, false);
How do I fill in the information in the input areas of the new tab?
Another time, you may want to use something other than a popup (e.g. just a plain browser action button, or a panel) to test out your functionality. It is easier to debug things other than a popup due to the fact that the popup will disappear under so many conditions. Once you have the basic functionality debugged, you can move it into a popup and deal with the issues specific to using a popup.
Issues
You need to use a content script to interact with web pages:
The primary issue is that you have to use a content script to interact with a web page, such as manipulating the DOM, as you desire to do. Content scripts have to be injected into the web page. This can be done with a content_scripts entry in your manifest.json, or with chrome.tabs.executeScript() from JavaScript that is in the background context (background scripts, event scripts, popups, panels, tabs containing pages from your add-on, etc.). For what you are doing, chrome.tabs.executeScript() is the way to go.
Additional issues:
chrome.tabs.create() is asynchronous. You need to wait for the callback to execute so the tab exists in order to inject a content script. You can not inject scripts into a tab that does not yet exist. Note: You could use other, more complex, methods of determining when to inject the content script, but the callback for chrome.tabs.create() is a good way to do it in this case.
Once you create the new tab, you want to inject a script. This is not the "active tab", so you need to add "https://vena.io/*" to your permissions in your manifest.json.
The elements you desire to interact with are not immediately available on the page when the content script is run. You need to wait until they are available. I just used a setTimeout loop to poll until the elements are available. I chose to poll on 250ms intervals a maximum of 100 times (25 seconds). The elements were there each time after the first delay.
document.getElementsByClassName() returns an HTMLCollection, not a single element.
Popups close when you activate a different tab. Once the popup is closed/destroyed, you can not do any more processing within the code for the popup. In order to get around that:
In your chrome.tabs.create(), include active:false to prevent the new tab from becoming active immediately.
Call chrome.tabs.update() in the callback for chrome.tabs.executeScript() to active the tab once the content script has been injected (i.e. when you are done with all the processing you are going to do in the popup).
Code
Changes were only needed in your manifest.json and popup.js.
manifest.json
{
"manifest_version": 2,
"name": "Vena",
"description": "This extension will allow users to login to vena accounts",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab", //This is not needed as you never interact with the active tab
"https://vena.io/*"
]
}
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function() {
var newURL = "https://vena.io/";
//Must not make the tab active, or the popup will be destroyed preventing any
// further processing.
chrome.tabs.create({ url: newURL,active:false }, function(tab){
console.log('Attempting to inject script into tab:',tab);
chrome.tabs.executeScript(tab.id,{code:`
(function(){
var count = 100; //Only try 100 times
function changeLoginWhenExists(){
var loginField = document.getElementsByClassName('js-email form-control input-lg')[0];
var passwordField = document.getElementsByClassName('js-password form-control input-lg')[0];
//loginField and passwordField are each an HTMLCollection
if( loginField && passwordField ){
loginField.value = 'gsand';
passwordField.value = '123';
} else {
if(count-- > 0 ){
//The elements we need don't exist yet, wait a bit to try again.
//This could more appropriately be a MutationObserver
setTimeout(changeLoginWhenExists,250);
}
}
}
changeLoginWhenExists();
})();
`},function(results){
//Now that we are done with processing, make the tab active. This will
// close/destroy the popup.
chrome.tabs.update(tab.id,{active:true});
});
});
}, false);
}, false);
May need use document.execCommand('insertText', false, text);
Depending on the page, you may need/want to use:
document.execCommand('insertText', false, textValue);
If you do so, you will need to first select/focus the desired element. This would be instead of setting the .value property. Which you use will depend on what you are actually doing and the composition of the page you are altering. For the specific example in the question, setting the element's .value property works. For inserting text, using `document.execCommand('insertText') is more generally applicable.
May need a MutationObserver
In the above code I use a setTimeout() loop to delay until the desired elements exist. While that works in the above case, depending on your use case, it may be more appropriate for you to use a MutationObserver. Largely, which to use will depend on how immediately you need to respond to the desired elements being added and what type of load you are putting on the page by looking for the elements. For more information about watching for DOM changes see: Is there a JavaScript/jQuery DOM change listener?
UI comment
Currently you have a popup that has a single button: "Login". From a user interaction point of view, it would probably be better to just use a plain browser action button. If you are intending to add functionality to your popup, then go ahead and keep the popup. If you are not going to add functionality, it does not make a lot of sense to force your user to click twice (click: open popup, then click: login) when they could have just clicked once.
Use an actual Password Manager
If this is functionality that you desire, rather than just something you are putting together just to learn, you should use an actual Password Manager. The functionality of securely storing passwords and inserting them appropriately in websites is non-trivial. You can easily make mistakes that result in compromising your security. I strongly recommend that you investigate the various ones available and choose one which fits your needs. Basically, all the ones that I have seen would easily provide you with the functionality you have at this time: open a popup; select the site; go to the site and fill in the user name and password. A password manager is a very significant project. It is not a project to be taken on lightly, or for someone who is not experienced in security issues.
This is the background.js of my chrome extension. I am a newbie on programming chrome extensions.
How can I close the tab that is opened in this example regardless of if the tab is the current one or not?
chrome.tabs.onCreated.addListener(function() {
chrome.tabs.query({'active': true, 'lastFocusedWindow': true},
function (tabs) {
url = tabs[0].url;
if (url=="http://www.example.com"){
setTimeout(function(){
//would be nice to have this executed after EVERYTHING on the page is loaded
chrome.tabs.executeScript(null, {file:"jquery-1.11.1.min.js"});
chrome.tabs.executeScript(null, {file:"contentscript.js"});
},17000);
setTimeout(function(){
tabs[0].remove();
alert('tab removed');
},25000);
}
}
);
});
The comment from gui47 is correct - just use chrome.tabs.remove(... the tab id you want to remove ...)
A lot of this code could use improvement, assuming I understood your intent.
Imagine that you middle-click a link in a tab. It creates a tab in the background, and calls onCreated listener for it.
Yet you query for the active tab, and get the initial tab, not the new one.
You should use the fact that the tab information is passed into onCreated.
chrome.tabs.onCreated.addListener(function(tab) { // Can have a parameter!
/* use tab.url and tab.id */
});
When you call executeScript, it still obeys the run_at parameter, which is by default at document_idle. That means it will not execute until the static DOM is ready at least.
So no need for the timeout. To be extra safe, you can wrap your content script code in $(document).ready()
If you want to wait until the resources like images are loaded, you can use load event of the body.
If you need to wait until a specific event happens / some scripts execute in the page, you can use timeout and/or something like MutationObserver. However, it would make more sense then to add this waiting to contentscript.js and not the injecting code.
chrome.tabs.onCreated.addListener(function(tab) { // Can have a parameter!
// Assuming you want to check the address contains that and not exactly it
if(~tab.url.indexOf("http://www.example.com")) {
chrome.tabs.executeScript(tab.id, {file:"jquery-1.11.1.min.js"});
chrome.tabs.executeScript(tab.id, {file:"contentscript.js"});
}
});
Finally, if you want to close the page, you could call chrome.tabs.remove(tab.id).
But it might be better to simply close it from the content script - it might know better when to do it.
From the content script, you can simply use window.close() if your extension opened the tab.
You should use chrome.tabs.remove(tab.id); to close the tab.
See chrome.tabs.remove( ).
Use chrome.tabs.remove(tabId);
Whereas tabId is the id of your tab. If you don't know where to get the tabId you can use the below code in your background.js.
chrome.tabs.query(
{
active:true,
windowType:"normal",
currentWindow: true
},
function(d)
{
tabId = d[0].id;
});
I'm using programmatic injection to inject my extension's code into a page only when the browser action is clicked.
This is what I have on my extension's event page (per the example in the documentation):
chrome.browserAction.onClicked.addListener(function callback(tab){
chrome.tabs.executeScript(null, {file: "content-script.js"});
});
However, the way this works, the script is injected every time the button is clicked.
How can I change it so that the script is not injected on subsequent button presses - so that it is inserted only the first time the button is clicked on that page?
Put a global variable in your contentscript to judge if the contentscript has been executed.
if (something) { return; }
One way I can think of right now (easy and simple) is to use html5webstorage. Since you are running this code from your background or popup page it will be ok.
if(!localStorage.getItem("isAlreadyInjected")){
localStorage['isAlreadyInjected'] = "true";
chrome.browserAction.onClicked.addListener(function callback(tab){chrome.tabs.executeScript(null, {file: "content-script.js"});});}
So, the very first time when storage value "isAlreadyInjected" does not exist, the listener will be added. Afterwards, even when the browser closes and opens again this value will remain stored and so the listener will not be added to your extension.
UPDATE
As your background page loads only once at the beginning, it can keep variable that is not re-initialized with the browser action click. So you can use that variable to do your job!
background.js
var isAlreadyInjected =false;
function isInjected(){
if(!isAlreadyInjected ){
isAlreadyInjected=true;
return false;
}
else
return true;
}
popup.js
var bgpage=chrome.extension.getBackgroundPage();
if(!bgpage.isInjected()){
chrome.browserAction.onClicked.addListener(function callback(tab) {chrome.tabs.executeScript(null, {file: "content-script.js"});});
}
or
var bgpage=chrome.extension.getBackgroundPage();
chrome.browserAction.onClicked.addListener(function callback(tab) {
if(!bgpage.isInjected()){
chrome.tabs.executeScript(null, {file: "content-script.js"});
}});
I know this is an older question but I encountered the issue now that Manifest V3 is out and persistent background pages have been replaced with service workers. I figured I'd give what I used as a solution in case anyone else needs it. Code must be executed within the global context of the content script. Anytime it tries to inject it again, the relevant code will only be executed if the global variable is not defined.
if (typeof hasBeenExecuted === 'undefined') {
// Code that needs to execute only once goes here
}
var hasBeenExecuted = true;
Hopefully this is helpful for someone else who comes across the question.
I did a simple auto form filler, by sending info from a created html to the background and then the content script, so the injected script can change the info on the form.
I know the content script run once the page is load. I want to know if I can run the content script again, without the need of reloading the page.
I got sendRequest function in the content script, that I use to make sure it gets the info, only when the page is ready. It then add the info to the form, and wait for me to send it.
In the content script, I added a onRequest and it works (it get the info). but, I don't see the changes on the form, unless I am realoding the page.
I want to know if it is possible to do and if it does what subjects should I learn to implent this.
I am new to chrome extentions and I am still learning :)
in 1 of the pages, I use jQuery, so an answer with jQuery would be good too.
i found out that if we create a chrome.tabs.sendRequest from background we can use chrome.extestion.onRequest from content script and it will execute every time becuse they both run allmost in the same time.
so i did from background:
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.sendRequest(tabs[i].id, {...requests u want to send }, function (response) {
});
}
});
from content script:
chrome.extension.onRequest.addListener(function (request, sender, sendRespons) {
//get requested info here
//call functions.
sendResponse({}); //send info back to background page.
});
form's target could be an iframe which would avoid page reload. not sure how useful it'd be.
The correct way to execute a content script again is by using the chrome.tabs.executeScript method. It receives two arguments. The first argument is the tabId, which can be obtained in many ways, such as one of the chrome.tabs events. Use null to execute the content script in the currently selected tab (caution: this may also be an active dev tools window!).
Examples:
// Reloads the current tab
chrome.tabs.executeScript(null, {code:'location.reload();'});
// Executes contentscript.js in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js'});
// Executes contentscript.js in all frames in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js', allFrames: true});
// Receives message from content script, and execute a content script:
chrome.extension.onMessage.addListener(function(details) {
if (details.message === 'load a content script') {
chrome.tabs.executeScript(details.sender.tab.id, {file: 'a_script.js'});
}
});
// The previous one is activated from a content script, as follows:
chrome.extension.sendMessage('load a content script');
(onMessage and sendMessage have to be used instead of onRequest and sendRequest, since Chrome 20)