I'm working on Chrome extensions. I try to learn messaging between content and background. I develop simple project for this. But I have issue.
Basic idea is
User click button on extension popup
A function (bot.js) find image from content of tab then extension (background.js) will download it.
The issue is port.onMessage.addListener() in background.js fired twice.
When background.js sends message to contentscript.js there are two same messages in console or when I try to download in background.js (the code line "Do Something") it download the file twice.
How can I solve this problem?
popup.html
<!doctype html>
<html>
<head>
<title>Test Plugin</title>
<script src="background.js"></script>
<script src="popup.js"></script>
</head>
<body>
<h1>Test Plugin</h1>
<button id="btnStart">Button</button>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('btnStart');
checkPageButton.addEventListener('click', function() {
GetImages("Some URL");
}, false);
}, false);
var tab_title = '';
function GetImages(pageURL){
// Tab match for pageURL and return index
chrome.tabs.query({}, function(tabs) {
var tab=null;
for(var i=0;i<tabs.length;i++){
if(tabs[i].url==undefined || tabs[i].url=="" || tabs[i]==null){}
else{
if(tabs[i].url.includes(pageURL)){
tab=tabs[i];
break;
}
}
}
if(tab!=null){
chrome.tabs.executeScript(tab.id, {
file: "bot.js"
}, function(results){
console.log(results);
});
}
});
}
bot.js
var thumbImagesCount = document.querySelectorAll('.classifiedDetailThumbList .thmbImg').length;
var megaImageURL=document.querySelectorAll('.mega-photo-img img')[0].src;
console.log(megaImageURL + " from bot.js");
port.postMessage({key:"download", text: megaImageURL});
background.js
chrome.runtime.onConnect.addListener(function (port) {
console.assert(port.name == "content-script");
port.onMessage.addListener(function(message) {
console.log(message);
if(message.key=="download"){
// Do Something
// Event fires twice
port.postMessage({key:"download", text: "OK"});
}
})
});
contentscript.js
console.log("content script loaded!");
var port = chrome.runtime.connect({name: "content-script"});
port.onMessage.addListener(function(message){
console.log(message);
});
manifest.json
{
"manifest_version": 2,
"name": "Test Extension",
"description": "This extension will download images from gallery",
"version": "1.0",
"icons": {
"16": "bot16.png",
"48": "bot48.png",
"128": "bot128.png" },
"browser_action": {
"default_icon": "bot48.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"downloads",
"http://*/",
"https://*/"
],
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
]
}
The background script declared in manifest.json already has its own page, a hidden background page where it runs, so you should not load it in the popup as it makes no sense in case there are listeners for API events, the background page is already listening for them. In this case the copy also creates the second listener while the popup is open.
Solution: don't load background.js in popup.
See also Accessing console and devtools of extension's background.js.
Related
This question already has an answer here:
Pass a variable from content script to popup
(1 answer)
Closed 4 years ago.
I want to send data from content.js to popup.js,
The content.js is just grabbing the document title and then passing it to the popup.js.
So then popup.js will change the popup.html DOM.
manifest.json:
{
"browser_action": {
"default_icon": {
"64": "icons/icon64.png"
},
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
],
"permissions": [
"tabs",
"activeTab",
"*://*/*"
]
}
popup.html:
<html>
<body>
<span class="info">TAB TITLE</span>
<script src="popup.js"></script>
</body>
</html>
content.js:
console.log('CONTENT IS RUNNING')
var getTitle = function() {
return document.title
}
chrome.runtime.sendMessage(getTitle());
popup.js:
console.log('POPUP IS RUNNING')
chrome.runtime.onMessage.addListener(
function(response, sender, sendResponse) {
var title = response;
return title;
}
);
document.querySelector('.info').innerHTML = title; // error: title is not defind
In popup.js the response parameter is not giving the document title.
The popup runs (and exists) only when it's shown.
The declared content script runs whenever the web page is loaded.
These two events may happen at any time so there's no guarantee they coincide.
Solution 1: don't declare the content script, instead run it manually
popup.js:
chrome.tabs.executeScript({code: 'document.title'}, ([title]) => {
document.querySelector('.info').textContent = title;
});
Note how we use textContent here which is safe, unlike innerHTML which can lead to XSS (it'll be blocked by default but not if you've relaxed the default CSP).
Solution 2: read the tab title directly via chrome.tabs API
popup.js:
chrome.tabs.query({active: true, currentWindow: true}, ([tab]) => {
document.querySelector('.info').textContent = tab.title;
});
manifest.js for both solutions:
{
"browser_action": {
"default_icon": {
"64": "icons/icon64.png"
},
"default_popup": "popup.html"
},
"permissions": ["activeTab"]
}
Note we only need "activeTab" permission so no permissions warning is displayed at installation.
Hello there,
I want to remove thumbnail images that appear on YouTube. I am using the following code for this.
while (true) {
$("ytd-thumbnail").remove()
}
When I paste this code into console, all thumbnail images are removed. I want it to work on the backplane by adding an extension. The code for the plug-in I'm preparing is below.
manifest.json;
{
"manifest_version": 2,
"name": "test",
"description": "test extension",
"version": "1.0",
"browser_action": {
"default_popup": "popup.html"
},
"permissions": [
"activeTab"
]
}
popup.html
<!doctype html>
<html>
<head>
<title>TEST</title>
<script src="popup.js"></script>
</head>
<body>
<h1>TEST</h1>
<button id="checkPage">Check !</button>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function() {
chrome.tabs.getSelected(null, function(tab) {
d = document;
while (true) {
$("ytd-thumbnail").remove()
}
});
}, false);
}, false);
When I press the checkPage button nothing happens. But this code works when I add a console. What is the problem? Thanks in advance.
There are several issues with the extension :
You are trying to use $ i.e. jquery which is not available in your
popup.js
You are trying to access "ytd-thumbnail" dom elements which belong
to youtube page and not your popup.html. So, even if you replace $
with document.querySelector , you won't find those elements.
I created a working version that looks something like this. I have not included popup.html which is same as yours.
manifest.json
{
"manifest_version": 2,
"name": "Hello Extensions",
"description" : "Base Level Extension",
"version": "1.0",
"browser_action": {
"default_icon": "hello_extensions.png",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["background.js"],
"all_frames" : true
}
],
"permissions": [
"activeTab",
"tabs"
]
}
2.popup.js
document.addEventListener('DOMContentLoaded', function()
{
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function()
{
chrome.tabs.query({"active":true}, function (tabs){
chrome.tabs.sendMessage(tabs[0].id, "removeThumbnails", function (response) {
});
});
});
});
When the button is clicked, retrieve the active tab and send it a "removeThumbnails" message.
3.background.js
chrome.runtime.onMessage.addListener(function(message, callback) {
if (message == "removeThumbnails")
{
var elements = document.querySelectorAll("ytd-thumbnail");
elements.forEach(a => a.parentNode.removeChild(a));
}
});
background.js is content script and runs in youtube page. It can now access all the dom elements in youtube page. When we receive a "removeThumbnails" message , get all ytd-thumbnail elements and remove them from page.
Not a duplicate of Executing Chrome extension onclick instead of page load because I need to execute script on a button press in popup.html, not when the user presses the icon.
This is my first chrome extension and I've got my content.js working the way it should on page load, but I only want to execute it after the user pushes a button in popup.html. I know you can specify run_at in manifest.json, but this doesn't work because I want it to only run when the user clicks a button (not the icon), and I'm using pageAction so I need the icon to be grayed out on urls which don't contain the letter 'g', hence the specification in my background.js. I think I must be missing something regarding the communication between background.js and content.js, but I'm feeling very lost so if anyone can explain what I'm missing that would be great.
Here is my manifest.json:
{
"manifest_version": 2,
"name": "my extension",
"description": "it doesnt work",
"version": "0.1",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"permissions": [
"declarativeContent"
],
"page_action": {
"default_popup": "popup.html"
},
"icons" : { "16": "16.png",
"48": "48.png",
"128": "128.png" },
"content_scripts": [
{
"js": ["content.js"],
"matches": ["<all_urls>"],
"run_at": "document_end"
}
]
}
Background.js:
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([
{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlContains: 'g' },
})
],
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {file: "content.js"});
}); ]
}
]);
});
});
Content.js:
document.addEventListener("DOMSubtreeModified", function(event){
if(document.getElementsByClassName(".class")) {
var x = document.querySelectorAll(".class");
var i;
for (i = 0; i < x.length; i++) {
x[i].style.visibility = "hidden";
} }
});
popup.html:
<html>
<head>
<script type="text/javascript" src="content.js"></script>
</head>
<body>
<p>Turn off <span>class, "class"</span></p>
<button type="button">Turn off</button>
</body>
</html>
This will run if you are on the selected tab. You may need to change how you select that tab though. Basically this will will send a request to the tab and the tab listens for these requests. If you get the proper greeting, you can do your function.
popup.html
<html>
<head>
<script type="text/javascript" src="content.js"></script>
</head>
<body>
<p>Turn off <span>class, "class"</span></p>
<button type="button" id="button">Turn off</button>
</body>
</html>
background.js
$('#button').on('click',function()
{
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.executeScript(tab.id, {file: "content_script.js"});
});
});
content.js
if(document.getElementsByClassName(".class")) {
var x = document.querySelectorAll(".class");
var i;
for (i = 0; i < x.length; i++) {
x[i].style.visibility = "hidden";
}
}
Also, as #Makyen had suggested, remove content scripts from your manifest so it isn't always being injected into every tab. I edited my answer as well per this suggestion.
Hello, I am writing a simple chrome extension which will be used to:
1. open new webpage
2. fill timesheet form based on pasted string array
3. submit timesheet (just clicking "ok" button in the form)
4. open new webpage
For this to work my extension needs to contain:
1. popup.html Browser Action popup, with input textfield for string array, and submit button.
2. timesheet.js - javascript file to add logic to popup.html
3. background.js - background page to take care of filling form, after clicking submit button
4. content_script.js - to access newly opened webpage DOM, to fill the form.
For now, I made a simplified version, which is supposed to:
1. open www.google.com in new tab
2. wait few seconds (optionally, wait or page to finish loading)
3. change background color
Everything seems to be woring fine, except for content_script.js listener doesn't react to message send by background.js
Here is the code:
manifest.json:
{
"manifest_version": 2,
"name": "Timesheet Filler",
"description": "Description.",
"version": "1.0",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["http://www.google.com/*"],
"js": ["content_script.js"]
}],
"browser_action": {
"default_title": "Timesheet Filler",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab",
"http://www.google.com/*"
]
}
popup.html:
<!DOCTYPE html>
<html>
<body>
<button id="btn" >Click Me!</button>
<script src="timesheet.js"></script>
</body>
</html>
timesheet.js:
document.addEventListener('DOMContentLoaded', function(){
init();
});
function init(){
var btn = document.getElementById('btn');
btn.onclick = function() { onBtnClick(); }
}
function onBtnClick(){
chrome.runtime.sendMessage({action:"btnClick"}, btnClickCallback);
}
function btnClickCallback(any){
alert(any);
}
background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "btnClick"){
chrome.tabs.create({url: "http://www.google.com", active:true});
setTimeout(function(){ delayed(); }, 3000);
}
});
function delayed(){
chrome.tabs.query({active:true}, queryCallback);
}
function queryCallback(arr){
var tabId = arr[0].id;
console.log("message shown 3 second after clicking button") // THIS IS WORKING
chrome.tabs.sendMessage(tabId, {action:"doSomething"}); // CONTENT SCRIPT DOESNT REACT TO THIS
}
function contentScriptCallback(any){
alert(any);
}
content_script.js:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if( message.action == "doSomething"){
document.body.style.backgroundColor='#000000';
alert("do something");
}
});
Download all files in one ZIP here
How to make content_script.js react to to message and change webpage bg color?
SOLUTION: (thanks to #wOxxOm)
Content script wasn't called since matches in content_scripts section as well as permisions of manifest.json was defined wrong.
Easiest fix was to change URL range to: <all_urls>
(for precise urls matches see this link)
Fixed manifest.json:
{
"manifest_version": 2,
"name": "Timesheet Filler",
"description": "Description.",
"version": "1.0",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}],
"browser_action": {
"default_title": "Timesheet Filler",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab",
"<all_urls>"
]
}
I am trying to append a div to page of current active tab. However I am getting this error:
Error during tabs.executeScript: Cannot access contents of url ....
Extension manifest must request permission to access this host.
My Code: (show_loader.js)
var dv = document.createElement('div');
dv.id = 'myid';
dv.innerHTML = 'test';
document.body.appendChild(dv);
However when I put this code:
document.body.style.backgroundColor = 'green';
It works as expected and background color of current tab is changed with no other change except for the code in show_loader.js file which is run from popup.js like this:
chrome.tabs.executeScript(null, {file: "show_loader.js"});
My Manifest file also does have:
"permissions":
[
"tabs",
"notifications",
"http://*/",
"https://*/"
],
So I wonder why it gives above error when I try to do anything else other than setting background color. Even simple alert or console.log alone on that page gives same above mentioned error.
How to fix that ?
Update: Complete Relevant Code
Manifest:
{
... name and description ...
"icons":
{
"16" : "img/icon.png",
"48" : "img/48.png",
"128" : "img/128.png"
},
"permissions":
[
"tabs",
"notifications",
"http://*/*",
"https://*/*"
],
"browser_action":
{
"default_icon": "img/icon.png",
"default_title": "Page title",
"default_popup": "popup.html"
}
}
popup.js
// send data to server for saving
$('#btn').click(function(){
chrome.tabs.executeScript(null, {file: "show_loader.js"});
$loader.show();
var data = $(this).closest('form').serialize();
$.ajax({.....});
});
window.onload = function(){
var $loader = $('#loader');
$loader.show();
chrome.tabs.getSelected(null, function(tab) {
//console.log(tab);
$('#url').val(tab.url);
$('#title').val(tab.title);
$loader.hide();
});
};
popup.html
<html>
<head>
<link rel="stylesheet" href="css/style.css" type="text/css" />
</head>
<body>
<form action="" method="post" name="frm" id="frm">
<table border="0" cellpadding="3" cellspecing="0" width="370">
......
</table>
</form>
<script src='js/jquery.js'></script>
<script src='popup.js?v=014423432423'></script>
</body>
</html>
show_loader.js
console.log($); // doesn't work
// document.body.style.backgroundColor = 'green'; // WORKS
Code which worked
manifest.json
{
"name": "Manifest Permissions",
"description": "http://stackoverflow.com/questions/14361061/extension-manifest-must-request-permission-to-access-this-host",
"version": "1",
"manifest_version": 2,
"browser_action": {
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"notifications",
"http://*/",
"https://*/"
]
}
popup.html
<html>
<head>
<script src="back.js"></script>
</head>
<body>
<button id="tmp-clipboard">Click Me</button>
</body>
</html>
back.js
document.addEventListener("DOMContentLoaded", function () {
document.getElementById('tmp-clipboard').onclick = function () {
chrome.tabs.executeScript(null, {
file: "script.js"
});
}
});
script.js
var dv = document.createElement('div');
dv.id = 'myid';
dv.innerHTML = 'test';
document.body.appendChild(dv);
Try Eliminating deprecated chrome.tabs.getSelected from your code and use chrome.tabs.query instead.
Sample Usage
chrome.tabs.query({
"currentWindow": true,
"status": true,
"active": true //Add any parameters you want
}, function (tabs) {//It returns an array
for (tab in tabs) {
//Do your stuff here
}
});
Edit 1
If you intention is to capture active browsing tab in current window where he clicked browser action use this code
chrome.tabs.query({
"currentWindow": true,//Filters tabs in current window
"status": "complete", //The Page is completely loaded
"active": true // The tab or web page is browsed at this state,
"windowType": "normal" // Filters normal web pages, eliminates g-talk notifications etc
}, function (tabs) {//It returns an array
for (tab in tabs) {
$('#url').val(tabs[tab].url);
$('#title').val(tabs[tab].title);
$loader.hide();
}
});
The manifest v3 uses a different permission schema. This is what worked for me:
"host_permissions": [
"https://music.youtube.com/*"
],