I am developing a chrome extension in which one can select a color scheme from list given in popup and apply it to the open (highlighted) tab. From one of code snippet I comes to know that using code : "document.body.style.backgroundColor='red'" in chrome.tabs.executeScript change the background color. but there is only one line in code.
What my steps are
select the color scheme from popup
get the class name of the selected li
apply that class to the DOM document
Please see the code below
popup.js
document.addEventListener('DOMContentLoaded', function () {
var li = document.querySelectorAll('li');
for (var i = 0; i < li.length; i++) {
li[i].addEventListener('click', click);
}
});
function click(e) {
// console.log(e.target.className); // gives correct value
chrome.tabs.executeScript(null, {
code : "var scriptOptions = { param1: e.target.className} ;"}, function(e){
console.log('clicked class');
console.info(param1); // gives nothing
document.body.setAttribute('class', e.target.className);
});
window.close();
}
How to get e.target.className inside function(e) ?
again If I use jquery. it changed the that popup background color only, see the code
$(function(){
console.log('jQuery added');
$(document).on ('click', 'li', function(){
var cl = this.className;
$('body').removeClass().addClass(cl);
});
});
Please tell me
What is the proper way to accomplish this in both javascript and jQuery
How to get e.target.className inside function(e) ?
Let's look at the following sample code:
var a = 1;
function f(a) {
alert(a);
}
f(2);
This is a simplified version of your problem. There is a variable a in the global scope, but by naming your function parameter a you're essentially making a local variable of the same name.
In your code:
function click(e) {
// e is now from click(e)
chrome.tabs.executeScript(null, {
code : "var scriptOptions = { param1: e.target.className} ;"}, function(e){
// e is now from function(e)
});
}
The solution is simple: you're not using the parameter of the callback of executeScript, so just use function() { /* ... */ } as a callback.
If I use jQuery, it changes the popup background color only
Your code operates in the context of your popup; $('body') refers to popup's body. Same with document.body - the callback of executeScript executes in the popup.
To change the active tab, this needs to be done from the content script in that tab.
What is the proper way to accomplish this
While you could just inject code, it's better to make a content script that waits for a command.
// content.js
if(!injected) { // Make sure it's only executed once
injected = true;
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "bodyClass") {
document.body.setAttribute('class', message.class);
}
});
}
Then from the popup, you inject this script then message it:
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
// requires only activeTab permission
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function() {
// This code executes in the popup after the content script code executes
// so it is ready for the message
chrome.tabs.sendMessage(tabs[0].id, {action: "bodyClass", class: "example"});
});
});
If you need jQuery, you need to inject it first:
chrome.tabs.executeScript(tabs[0].id, {file: "jquery.js"}, function() {
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function() {
/* content script ready */
}
}
Alternatively, you can define the script in the manifest and not inject it every time, but this potentially drains memory as it is injected in tabs where it is not needed.
There is bug in chromium and in chrome I need to use JSON.stringify(e.target.className) the before sending via code
code : "var scriptOptions = { selectedClass: " + JSON.stringify(cl) + " }"
from chorme.sendMessage documentation
Sending a request from the extension to a content script looks very
similar, except that you need to specify which tab to send it to.
function click(e) {
var cl = e.target.className; // both gives the same result that is OK.
chrome.tabs.query({ active: true, highlighted: true, currentWindow: true }, function(htab) {
// console.log(JSON.stringify(htab, ['active', 'id', 'index', 'windowId', 'title', 'url'], 4));
chrome.tabs.executeScript(htab[0].id, {
code : "var scriptOptions = { selectedClass:" + JSON.stringify(cl) + " }" }, function() {
chrome.tabs.executeScript(htab[0].id, { file: "js/script.js" }, function(){
console.log('Inside script file');
chrome.tabs.sendMessage(htab[0].id, { action: "bodyColor" }, function(resp) {
console.log('response aaya');
});
});
});
});
}
Related
i try create simple Chrome Extension, after click button in pop-up, i need send function setInput() to page, function change value and i need use trigger('keyup'), if i try use this function in Chrome Console - trigger work. But if i send this function after click in pop-up - trigger not work(
Chrome Extension - Trigger not Work
Console - Trigger Work
popup.html
<head>
<script src="popup.js"></script>
</head>
<body>
<div class="btn">Click</div>
</body>
popup.js
function sendMessage() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {"message": "start"});
});
}
function onWindowLoad() {
chrome.tabs.executeScript(null, { file: "PageReader.js" });
}
document.addEventListener("DOMContentLoaded", function() {
var btn = document.querySelector('.btn');
btn.addEventListener('click', function() {
sendMessage();
});
});
window.onload = onWindowLoad;
PageReader.js
- in file top i include Jquery
function setInput() {
var input = $('.text input');
input.val('1111').trigger('keyup');
}
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "start" ) {
setInput();
}
}
);
thanks all for help, i find answer, i delete jQuery, and create event "keyup"
Old:
var input = $('.text input');
input.val('1111').trigger('keyup');
New:
var evt = document.createEvent('KeyboardEvent');
evt.initEvent('keyup', true, true);
var input = document.querySelector('.text input');
input.value = '1111';
input.dispatchEvent(evt);
Please add debugger after btn.addEventListener('click', function() { too see what is going on. if this event handler is attached.
Second thing - you may want to wrap you initialisation code into setTimeout call with let's say 100ms of delay, to check if this page you are dealing with is not only working with some framework that generates this HTML and this is done after DOMContentLoaded. This means basically wrap everything inside
document.addEventListener("DOMContentLoaded", function() {
with setTimeout(function() {/*everything inside goes here*/}, 100)
I would like to change a variable in my contentscript file by clicking on the icon of my Chrome extension. Do you know how I can do it?
Here is a sample of my code :
background.js :
var myVariable = false;
chrome.browserAction.onClicked.addListener(function (tab) { //Fired when User Clicks ICON
chrome.tabs.executeScript(tab.id, {
"file": "zen-reading.js"
}, function () {
console.log("Script Executed .. ");
});
if (myVariable === false) {
chrome.tabs.executeScript({
code: 'myVariable = ' + true + ';'
});
myVariable = true;
}
else {
chrome.tabs.executeScript({
code: 'myVariable = ' + false + ';'
});
zenMode = myVariable;
}
});
contentscript.js :
if (myVariable === true) {
doSomething();
}
if (myVariable === false) {
doSomethingElse();
}
Method 1: divide the content script in two and depending on myVariable inject one of them.
Method 2: inject the code that sets the variable prior to injecting the script file:
chrome.tabs.executeScript(tab.id, {
code: "var myVariable=" + myVariable.toString()
}, function(results) {
chrome.tabs.executeScript(tab.id, {file: "zen-reading.js"}, function(results) {
..........
});
});
In case it's not a boolean/number use JSON:
code: 'var myVariable=JSON.parse("' + JSON.stringify(myVariable) + '")'
Method 3: inject the file once and use messaging. This is slightly more complicated because you will need to check first (using executeScript that will look for a variable that your content script should define) if the active tab has the content script injected which may not be the case if the user navigated to another url in the same tab.
I want to run a content script to identify the first image that links somewhere and return the link to the background script. But I can't pass any messages between them, and I don't know why. Much of the code is comment, I'm trying to simply test if the content script is executing by telling it to change the first header to "test" (since the only thing I can do without messages is change a page's HTML and CSS). The code follows:
EDIT: full updated code
Background script:
chrome.browserAction.onClicked.addListener(function () {
chrome.tabs.query({active:true, currentWindow:true}, function(tabs)
{
chrome.tabs.executeScript(tabs[0].id, {file:"content.js"}, function ()
{
console.log("sending first time");
console.log(tabs[0].id);
chrome.tabs.sendMessage(tabs[0].id,{test: "test"}, function (response)
{
if (undefined == response)
{
console.log ("first one didn't work");
chrome.tabs.sendMessage(tabs[0].id, {test: "test"}, function (response)
{
console.log(response.test);
});
}
});
});
});
});
Content Script:
chrome.runtime.onMessage.addListener( function (msg, sender, sendResponse)
{
/*var test1 = document.getElementsByTagName("h1")[0];
test1.innerHTML="test";*/
sendReponse({test: "123"});
/*var parentList =document.getElementsByTagName("a");
var link = "";
var child;
for (var i=2; i<parentList.length;i++)
{
child=parentList[i].firstChild;
if ("IMG"==child.nodeName)
{
link=parentList[i].href;
break;
}
}
teste1.innerHTML=link;
var answer = new Object();
Answer.link = link;
teste1.innerHTML=answer.link;
sendResponse({messager:answer});*/
});
I was testing the extension at the extension page itself. But it seems you can't inject javascript in it (probably because it is a browser's settings page). It worked nicely on other pages.
I am working on a chrome extension ,this extension have 2 icons in the browser action (On & Off) ;
basically when it is On the background execute the script.js (Inject the file:script.js)
using the chrome.tabs.executeScript(tab.id,{file:"script.js",function(){});
I had problems to turn it off !
I have tried to use messages communication between the background.js and the script.js but this does not work neither .
If I understand correctly, your extension should have two states, On and Off. Clicking the extension icon toggles it on/off.
In this case you should use storage so the extension knows what state it is in. So on a click event, use something like:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.storage.sync.get('state', function(data) {
if (data.state === 'on') {
chrome.storage.sync.set({state: 'off'});
//do something, removing the script or whatever
} else {
chrome.storage.sync.set({state: 'on'});
//inject your script
}
});
});
Note though that this is happening at the extension/browser level and will apply to all tabs, so you may need something more complex that records both the tab ID and the state.
You then have the choice to either always run a content script and check the on/off state before performing some action, or inject and remove the script. I'm not sure if you remove a script though. Depending on what the script does, you may just want to refresh the page (i.e. if your script messes with the DOM and you want to undo that when turning the extension off).
background.js
var enable=false;
chrome.browserAction.onClicked.addListener(function (tab) {
enable = enable ? false : true;
if(enable){
//turn on...
chrome.browserAction.setIcon({ path: 'icon.png' });
chrome.browserAction.setBadgeText({ text: 'ON' });
chrome.tabs.executeScript(null, { file: 'content.js' });
}else{
//turn off...
chrome.browserAction.setIcon({ path: 'disable.png'});
chrome.browserAction.setBadgeText({ text: '' });
}
});
To add onto what #david-gilbertson stated for making it active and inactive for certain tabs, I have created that functionality here. I also took added some functions for removing and adding tabs to the array. Enjoy!
function addTab(array, new_tab_id)
{
array.push(new_tab_id);
//then call the set to update with modified value
chrome.storage.sync.set({
active_tabs:array
}, function() {
console.log("added tab");
});
}
function removeTab(array, rem_tab_id)
{
const index = array.indexOf(rem_tab_id);
if (index > -1) {
array.splice(index, 1);
}
//then call the set to update with modified value
chrome.storage.sync.set({
active_tabs:array
}, function() {
console.log("removed tab");
});
}
chrome.browserAction.onClicked.addListener(function (tab) {`enter code here`
chrome.storage.sync.get({active_tabs : []}, function(data) {
if (data.active_tabs.includes(request.tab_id)) {
removeTab(data.active_tabs, request.tab_id)
console.log("Turned Off ".concat(request.tab_id))
document.removeEventListener("mousemove", highlightCurrentHover, false);
} else {
addTab(data.active_tabs, request.tab_id)
console.log("Turned On ".concat(request.tab_id))
document.addEventListener('mousemove', highlightCurrentHover, false);
}
});
);
It seems that helloworld.js gets loaded multiple times based on the number of times I click #load. I say this because when I look at Google Chromes Developer Tools Network tab, it shows helloworld.js as many times as I click #load.
$(document).ready(function() {
$("#load").click(function(){
$.getScript('helloworld.js', function() {
hello();
});
});
});
The hello() function looks like this:
function hello(){
alert("hello");
}
Is it possible to detect if helloworld.js has already loaded?
So if it hasn't loaded, load it, and if it has loaded, don't load it.
This is what Developer Tools currently shows me if I click the #load button 4 times:
Set a flag when file loaded successfully. If flag is set then skip the file loading again.
Try this code,
var isLoaded = 0; //Set the flag OFF
$(document).ready(function() {
$("#load").click(function(){
if(isLoaded){ //If flag is ON then return false
alert("File already loaded");
return false;
}
$.getScript('helloworld.js', function() {
isLoaded = 1; //Turn ON the flag
hello();
});
});
});
So why not only fire the event once like this:
$("#load").one("click", function() {
$load = $(this);
$.getScript('helloworld.js', function() {
hello();
// bind hello to the click event of load for subsequent calls
$load.on('click', hello);
});
});
That would prevent subsequent loads and avoids the use of a global
Another option is letting .getScript() run but let it take the script from browser's cache so you won't have it reloaded each and every time.
To achieve this, add such code:
$.ajaxSetup({
cache: true
});
This is taken from the documentation page.
You could create a helper function:
var getScript = (function() {
var loadedFiles = {};
return function(filename, callback) {
if(loadedFiles[filename]) {
callback();
} else {
$.getScript(filename, function() {
loadedFiles[filename] = true;
callback();
});
}
};
})();