How can I pass a parameter to the JavaScript in a content script file which is injected using:
chrome.tabs.executeScript(tab.id, {file: \"content.js\"});
There's not such a thing as "pass a parameter to a file".
What you can do is to either insert a content script before executing the file, or sending a message after inserting the file. I will show an example for these distinct methods below.
If you want to define some variables before inserting the file, just nest chrome.tabs.executeScript calls:
chrome.tabs.executeScript(tab.id, {
code: 'var config = 1;'
}, function() {
chrome.tabs.executeScript(tab.id, {file: 'content.js'});
});
If your variable is not as simple, then I recommend to use JSON.stringify to turn an object in a string:
var config = {somebigobject: 'complicated value'};
chrome.tabs.executeScript(tab.id, {
code: 'var config = ' + JSON.stringify(config)
}, function() {
chrome.tabs.executeScript(tab.id, {file: 'content.js'});
});
With the previous method, the variables can be used in content.js
in the following way:
// content.js
alert('Example:' + config);
The previous method can be used to set parameters after the JS file. Instead of defining variables directly in the global scope, you can use the message passing API to pass parameters:
chrome.tabs.executeScript(tab.id, {file: 'content.js'}, function() {
chrome.tabs.sendMessage(tab.id, 'whatever value; String, object, whatever');
});
In the content script (content.js
), you can listen for these messages using the chrome.runtime.onMessage event, and handle the message:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
// Handle message.
// In this example, message === 'whatever value; String, object, whatever'
});
There are five general ways to pass data to a content script injected with tabs.executeScript()(MDN):
document_start
, without the need for the content script to perform an asynchronous request.chrome.storage.local
(set prior to executing your script)Using this method maintains the execution paradigm you are using of injecting a script that performs a function and then exits. It also does not have the potential security issue of using a dynamic value to build executing code, which is done in the second option below.
chrome.storage.local.set()
, call tabs.executeScript()(MDN).var updateTextTo = document.getElementById('comments').value;
chrome.storage.local.set({
updateTextTo: updateTextTo
}, function () {
chrome.tabs.executeScript({
file: "content_script3.js"
});
});
storage.local
(e.g. remove the key with: chrome.storage.local.remove() (MDN)).chrome.storage.local.get('updateTextTo', function (items) {
assignTextToTextareas(items.updateTextTo);
chrome.storage.local.remove('updateTextTo');
});
function assignTextToTextareas(newText){
if (typeof newText === 'string') {
Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
el.value = newText;
});
}
}
See: Notes 1 & 2.
Prior to executing your script, you can inject some code that sets a variable in the content script context which your primary script can then use:
The following uses "'" + JSON.stringify().replace(/\\/g,'\\\\').replace(/'/g,"\\'") + "'" to encode the data into text which will be proper JSON when interpreted as code, prior to putting it in the code
string. The .replace()
methods are needed to A) have the text correctly interpreted as a string when used as code, and B) quote any '
which exist in the data. It then uses JSON.parse() to return the data to a string in your content script. While this encoding is not strictly required, it is a good idea as you don't know the content of the value which you are going to send to the content script. This value could easily be something that would corrupt the code you are injecting (i.e. The user may be using '
and/or "
in the text they entered). If you do not, in some way, escape the value, there is a security hole which could result in arbitrary code being executed.
tabs.executeScript()
to inject your script (Note: tabs.executeScript()
will execute scripts in the order in which you call tabs.executeScript()
, as long as they have the same value for runAt
. Thus, waiting for the callback of the small code
is not strictly required).var updateTextTo = document.getElementById('comments').value;
chrome.tabs.executeScript({
code: "var newText = JSON.parse('" + encodeToPassToContentScript(updateTextTo) + "');"
}, function () {
chrome.tabs.executeScript({
file: "content_script3.js"
});
});
function encodeToPassToContentScript(obj){
//Encodes into JSON and quotes \ characters so they will not break
// when re-interpreted as a string literal. Failing to do so could
// result in the injection of arbitrary code and/or JSON.parse() failing.
return JSON.stringify(obj).replace(/\\/g,'\\\\').replace(/'/g,"\\'")
}
if (typeof newText === 'string') {
Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
el.value = newText;
});
}
See: Notes 1, 2, & 3.
This requires your content script code to install a listener for a message sent by the popup, or perhaps the background script (if the interaction with the UI causes the popup to close). It is a bit more complex.
tabs.executeScript()
, use tabs.sendMessage()(MDN)(which requires knowing the tabId
), to send the data as a message.var updateTextTo = document.getElementById('comments').value;
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {
file: "content_script3.js"
}, function(){
chrome.tabs.sendMessage(tabs[0].id,{
updateTextTo: updateTextTo
});
});
});
runtime.onMessage
listener#3.2 is optional. You could keep your code active waiting for another message, but that would change the paradigm you are using to one where you load your code and it stays resident waiting for messages to initiate actions.
chrome.runtime.onMessage.addListener(assignTextToTextareas);
function assignTextToTextareas(message){
newText = message.updateTextTo;
if (typeof newText === 'string') {
Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
el.value = newText;
});
}
chrome.runtime.onMessage.removeListener(assignTextToTextareas); //optional
}
See: Notes 1 & 2.
Note 1: Using Array.from() is fine if you are not doing it many times and are using a browser version which has it (Chrome >= version 45, Firefox >= 32). In Chrome and Firefox, Array.from() is slow compared to other methods of getting an array from a NodeList. For a faster, more compatible conversion to an Array, you could use the asArray()
code in this answer. The second version of asArray()
provided in that answer is also more robust.
Note 2: If you are willing to limit your code to Chrome version >= 51 or Firefox version >= 50, Chrome has a forEach()
method for NodeLists as of v51. Thus, you don't need to convert to an array. Obviously, you don't need to convert to an Array if you use a different type of loop.
Note 3: While I have previously used this method (injecting a script with the variable value) in my own code, I was reminded that I should have included it here when reading this answer.