The Javascript you are running in your Chrome extension is run either in the background page or some popup page. It is NOT the page in your browser where you were when running the extension. That is why you need to executeScript inside a specific tab.
To visualize this better, right click on the button of your extension, and select Inspect Popup. The same for a background page, go to chrome://extensions to your (presumably) unpacked extension and click on background page, and you have developer tools to see what is going on.
In order to package some resources in your chrome extension so they can be used by direct access from web pages later on, you can use Web Accessible Resources. Basically you declare the files that you want accessible by the web pages, then you can load them in or inject them using a URL of the form "chrome-extension://[PACKAGE ID]/[PATH]". In order to use this mechanism and make the packaged jQuery library available to web pages, we add:
"web_accessible_resources": [
"jquery-2.2.2.min.js"
]
in manifest.json.
In order to have access to web pages from the extension, you need to add permissions for it:
"permissions" : [
"tabs",
[...]
"<all_urls>",
"file://*/*",
"http://*/*",
"https://*/*"
],
also in manifest.json. <all_urls> is a special keyword not something I added there and it is a placeholder for any type of URL, regardless of schema. If you want only http and https URLs, just use the last two only.
Here is a function that loads jQuery in the page of a certain tab, assigning it the name ex$ so that it doesn't conflict with other libraries or versions of jQuery.
function initJqueryOnWebPage(tab, callback) {
// see if already injected
chrome.tabs.executeScript(tab.id,{ code: '!!window.ex$'},function(installed) {
// then return
if (installed[0]) return;
// load script from extension (the url is chrome.extension.getUrl('jquery-2.2.2.min.js') )
chrome.tabs.executeScript(tab.id,{ file: 'jquery-2.2.2.min.js' },function() {
// make sure we get no conflicts
// and return true to the callback
chrome.tabs.executeScript(tab.id,{ code: 'window.ex$=jQuery.noConflict(true);true'},callback);
});
});
}
This function is loading jQuery from the extension to the web page, then executes a callback when it's installed. From then one can use the same chrome.tabs.executeScript(tab.id,{ code/file:
to run scripts using jQuery on the remote page.
Since it uses the executeScript method with the file option, it doesn't need the web_accessible_resources declaration for the script
From a nice comment below, I learned about execution environment.
executeScript runs in an "isolated world" so that it doesn't have
access to global variables and whatever gets executed doesn't create
something that can be accessed by the web page. So the entire complicated
function above is overkill. You can still use this system if you do not want
to interact with code on the page, but just with its DOM, and be unconcerned
with any conflicts in code
But the extension does have access to the document of the tab. Here is a function that uses the web_accessible_resources mechanism described at first to create a script tag in the web page that then loads the jQuery library that is packaged in the extension:
// executeScript's a function rather than a string
function remex(tabId, func,callback) {
chrome.tabs.executeScript(tabId,{ code: '('+func.toString()+')()' },callback);
}
function initJqueryOnWebPage(tab, callback) {
// the function to be executed on the loaded web page
function f() {
// return if jQuery is already loaded as ex$
if (window.ex$) return;
// create a script element to load jQuery
var script=document.createElement('script');
// create a second script tag after loading jQuery, to avoid conflicts
script.onload=function() {
var noc=document.createElement('script');
// this should display in the web page's console
noc.innerHTML='window.ex$=jQuery.noConflict(true); console.log(\'jQuery is available as \',window.ex$);';
document.body.appendChild(noc);
};
// use extension.getURL to get at the packed script
script.src=chrome.extension.getURL('jquery-2.2.2.min.js');
document.body.appendChild(script);
};
// execute the content of the function f
remex(tab.id,f,function() {
console.log('jQuery injected'); // this should log in the background/popup page console
if (typeof(callback)=='function') callback();
});
}
It is cumbersome to send scripts to executeScripts, that is why I used the remex function above. Note that there is a difference between remex, which executes a function in the 'isolated world' environment and the code executed by the tab when a script tag is appended to the document. In both cases, though, what actually gets executed is strings, so be careful not to try to carry objects or functions across contexts.
Two consecutive remex calls will be executed in their own context, so even if you load jQuery in one, you won't have it available in the next. One can use the same system as in initJqueryOnWebPage, though, meaning adding the code to a script on the page. Here is a bit of code, use it just like remex
:
function windowContextRemex(tabId,func,callback) {
var code=JSON.stringify(func.toString());
var code='var script = document.createElement("script");'+
'script.innerHTML = "('+code.substr(1,code.length-2)+')();";'+
'document.body.appendChild(script)';
chrome.tabs.executeScript(tabId, {
code : code
}, function () {
if (callback)
return callback.apply(this, arguments);
});
}
So here I have demonstrated:
- executing code in an isolated context that has only access to the DOM
of a page opened in a tab
- executing packaged scripts in the same context as above
- injecting scripts that run in the context of the page
What is missing:
- a nice way of chaining code so that you don't have to create the
script element manually
- loading CSS files through insertCSS (akin to executeScript with a
file option, but that load directly in the page