Reputation: 920
I am creating an extension that I need to have the ability to run the content script multiple times when clicking on the page action button. I have this in my background.js:
chrome.pageAction.onClicked.addListener(function(tab) {
alert('calling content_script');
chrome.tabs.executeScript(null, {
file: 'content_script.js'
},function(){alert("success");});
});
This works the first time the button is clicked. When clicked a second time, I get my popup saying "calling content_script" but the content script is never executed. Why is this?
Here is the background script in its entirety:
function checkForValidUrl(tabId, ChangeInfo, tab){
if(tab.url.indexOf("tiger.armstrong")> -1){
chrome.pageAction.show(tabId);
if(tab.url.indexOf("tiger.armstrong") == 0){
chrome.pageAction.hide(tabId);
}
}
}
chrome.tabs.onUpdated.addListener(checkForValidUrl);
chrome.pageAction.onClicked.addListener(function(tab) {
alert('calling content_script');
chrome.tabs.executeScript(null, {
file: 'content_script.js'
},function(){alert("success");});
});
Here is the manifest:
{
"name": "LiveLab Post Grades",
"version": "2.0",
"permissions": [
"activeTab","tabs","http://*/*","https://*/*"
],
"background": {
"scripts": ["jquery.min.js","background3.js"],
"persistent": false
},
"page_action": {
"default_icon": {
"19": "GIcon.png"
},
"default_title": "LiveLab Tools"
},
"content_scripts": [ {
"js": [ "jquery.min.js" ],
"matches": [ "http://*/*", "https://*/*"],
"run_at": "document_end"
}],
"manifest_version": 2
}
Here is the content script:
var livelabtools = {
/**
* this function is like window.open, but it can POST (rather than GET) from js
* source: http://canop.org/blog/?p=426
*/
canop_open: function (verb, url, data, target) {
var form = document.createElement("form");
form.action = url;
form.method = verb;
form.target = target || "_self";
if (data) {
//for (var key in data) {
var input = document.createElement("input");
input.name = 'data';
input.value = data;//typeof data[key] === "object" ? JSON.stringify(data[key]) : data[key];
form.appendChild(input);
//console.log(form);
//}
}
// these two lines are only needed for ie
//form.style.display = 'none';
//document.body.appendChild(form);
form.submit();
console.log("form submit === " + form);
form.remove();
},
post_grades: function () {
alert('in post grades!!!!');
var str, exercise,
i = 0;
grades = {};
do {
ex_str = "form1:tabSet1:tabInstr:lp2:tabSet4:tabp:lpProgress:ts1:tab7:lp7:table3:rg3:" + i + ":tc3:st3";
lname_str = "form1:tabSet1:tabInstr:lp2:tabSet4:tabp:lpProgress:ts1:tab7:lp7:table3:rg3:" + i + ":tc1:st1";
grade_str = "form1:tabSet1:tabInstr:lp2:tabSet4:tabp:lpProgress:ts1:tab7:lp7:table3:rg3:" + i + ":tc7:st7_field";
exercise = document.getElementById(ex_str);
lname = document.getElementById(lname_str);
grade = document.getElementById(grade_str);
if (exercise != null) {
if (grades[lname.innerHTML] === undefined)
grades[lname.innerHTML] = {};
console.log(lname.innerHTML + ", " + exercise.innerHTML + ", " + grade.innerHTML);
if (grade.value != null && grade.value != '')
grades[lname.innerHTML][exercise.innerHTML] = grade.value;
else
grades[lname.innerHTML][exercise.innerHTML] = "0";
}
i++;
} while (exercise != null);
// console.log(JSON.stringify(grades));
// console.log(JSON.stringify(grades).length)
//window.open("http://aspen2.cscofc.info/jsontocsv.php?data="+JSON.stringify(grades));
console.log('posting...' + "\n JSON.String... = "+ JSON.stringify(grades));
livelabtools.canop_open("post", "http://aspen2.cscofc.info/jsontocsv.php", JSON.stringify(grades));
console.log('done');
return "function end";
}
}
console.log(livelabtools.post_grades());
I won't go into detail about it, unless asked but the important parts to note are the return statement and the console log. Everything runs perfectly fine the first time the page action button is clicked, and when finished, I get "function end" printed to the console. After the initial run, however, whenever I click on the page action button, I get an alert saying "calling content_script" and nothing else happens. Why won't my content script run more than once?
Upvotes: 1
Views: 2947
Reputation: 48211
It seems like when a script has already been injected it is not injected again.
So, the way to go would be to initiate your action by passing a message to the content script. (Of course, if the content script has not been injected yet, you need to inject it first.)
One solution I came up with (and after testing confirmed it works fine) is the following:
On pageAction.onClicked
send a message from the background page to the content script, asking it to do something (e.g. post the grades). Also, request a response to be sent back to the background page.
[See, also, chrome.tabs.sendMessage(...).]
If the content script has already been injected, have it receive the message, send a confirmation back to the background page and proceed doing something (e.g. posting the grades). This process can take place as many times as you want.
[See, also, chrome.runtime.onMessage.]
The first time that the pageAction.onClicked
is fired, there will be no content script listening for messages. In that case, there will be no message confirmation. Instead chrome.runtime.lastError will be set. In that case, the background page will have to inject the content script first and then send the message again.
[See, also, chrome.runtime.lastError.]
Theoreticaly speaking, that should do it !
Practicaly, this is the sample code that worked for me:
manifest.json: (Note: If you have more specific requirements regarding the pages you need to access, you could incorporate them into the manifest and get rid of some of the permissions.)
{
...
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"page_action": {
"default_title": "Test Extension"
},
"permissions": [
"tabs",
"http://*/*",
"https://*/*"
]
...
}
background.js:
function checkForValidURL(tabId, info, tab) {
var idx = tab.url.indexOf("tiger.armstrong");
if (idx > 0) {
chrome.pageAction.show(tabId);
} else {
chrome.pageAction.hide(tabId);
}
}
chrome.tabs.onUpdated.addListener(checkForValidURL);
function onPageActionClicked(tab) {
// Send message to content script, asking to post grades
alert("Calling content_script...");
chrome.tabs.sendMessage(tab.id, { action: "postGrades" }, function() {
if (chrome.runtime.lastError) {
// The error indicates that the content script
// has not been injected yet. Inject it and...
chrome.tabs.executeScript(tab.id, {
file: "content.js"
}, function() {
if (!chrome.runtime.lastError) {
// ...if injected successfully, send the message anew
onPageActionClicked(tab);
}
});
} else {
// The content script called our response callback,
// confirming that it is there and got our message
alert("Message got through !");
}
});
};
chrome.pageAction.onClicked.addListener(onPageActionClicked);
content.js:
var livelabtools = {
/**
* This function is like window.open, but it can POST (rather than GET) from
* JS source: http://canop.org/blog/?p=426
*/
canop_open: function (method, url, data, target) {
var form = document.createElement("form");
form.action = url;
form.method = method;
form.target = target || "_self";
// 'data' is an object with key-value pairs
// of fields to be sent
if (data) {
for (var key in data) {
var input = document.createElement("input");
input.name = key;
input.value = (typeof(data[key]) === "object")
? JSON.stringify(data[key]) : data[key];
form.appendChild(input);
}
}
form.submit();
form.remove();
},
post_grades: function () {
console.log("Posting some grades...");
livelabtools.canop_open("POST",
"http://aspen2.cscofc.info/jsontocsv.php",
"{}");
console.log("Grades sent !");
}
}
// Listen for messages from the background page
// (It actually listens for messages from anyone in the context,
// but the background page is the one that interrests us)
chrome.runtime.onMessage.addListener(function(msg, sender, response) {
// If we've been asked to post grades...
if (msg.action && (msg.action == "postGrades")) {
// ...confirm we got the message and...
response();
// ...do what we do best: post grades !
livelabtools.post_grades();
}
});
Let's hope this covers it :)
Upvotes: 2