Tuesday, April 12, 2016

Why is chrome.tabs.executeScript() necessary to change the current website DOM and how can I use jQuery to achieve the same effect?

Leave a Comment

I have already made some chrome extensions before but this is the first time I need to change something on the website the user is current looking at to respond to button clicks placed in the extension popup.

As I realized just executing some jQuery lines have no effect. What works is a method I found on google developer page sample extension: chrome.tabs.executeScript() but I have no clue why it's necessary.

There must be some basic concept I'm not aware of. Could anyone explain it to me? An can I execute jQuery (which is loaded) too?

Full example:

$('#change_button').on('click', function() {    //this doesen't work   $("body").css({backgroundColor: 'blue'});    //but this line does   chrome.tabs.executeScript(null, {code:"document.body.style.backgroundColor='red'"});  }); 

Actually I need jQuery badly to make some more changes in the DOM and respond to them i.e:

if( $(".list,.list-header-name").hasClass("tch")) {   $(".list,.list-header-name").addClass("tch"); } 

3 Answers

Answers 1

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.

Update: In order to package some resources in extensions in order to be used by 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]". Perhaps that will make the process easier for you.

Here is a sample code. I tested it, but it's not the most beautiful code ever. It should get one started though.

First of all, I needed to make the jQuery library I packaged in my extension available to web pages. You do that via

"web_accessible_resources": [     "jquery-2.2.2.min.js" ] 

in manifest.json. Secondly, in order to have access to web pages from the extension, you need to add permissions for it:

"permissions" : [     "tabs",     [...]     "http://*/",     "https://*/" ], 

also in manifest.json. Then 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.

It is cumbersome to send scripts to executeScripts, but one can use the new ECMAScript6 templated literals if supported or just function.toString, since the context of the functions in the extension is lost anyway when transferred to the web page. Something like this:

function remex(func,callback) {     chrome.tabs.executeScript(tab.id,{ code: '('+func.toString()+')()' },callback); } 

used like this:

remex(function() {   //... code ... using ex$ instead of $ or jQuery },callback); 

Answers 2

Popup page lives in the context of extension, while the DOM in the current web page is another context, they are different processes so they need some ways to communicate, tabs.executeScript is a way to insert code into a page programmatically.

If you heavily depend on jQuery, instead of inserting code directly, you can inject file first then feel free to use the jquery method.

chrome.tabs.executeScript(null, {file: "jquery.js"}, function() {     chrome.tabs.executeScript(null, {code:"$("body").css({backgroundColor: 'blue'})"); 

Besides Programming injection, you can also inject your content scripts by specifying matches field in manifest.json. In this way, when user clicks button in popup page, you can use Message Passing to communicate between extension process and content scripts, then use content scripts to manipulate the current page.

Answers 3

The thing you're missing is that the popup's javascript only affects the popup's DOM. To affect the tab's DOM, you need to use a content script (either from the manifest, or by injecting it).

Let's suppose you have this as popup.html:

<!doctype html> <html> <head> <title>popup html</title> <style> body { padding: 3em; } button { width: 10em; margin: 1ex; } </style> </head>  <body>   <p>    <button id="onlyPopup">Color the popup</button>    <button id="affectWebpage">Color the webpage</button>    <button id="jqWebpage">Inject jQuery</button>   </p>   <script src="jquery.min.js"></script>   <script src="popup.js"></script>  </body> </html> 

And this as popup.js:

$('#onlyPopup').click(function() {     $("body").css('backgroundColor','blue'); });  $("#affectWebpage").click(function() {     chrome.tabs.executeScript(null, {code:"document.body.style.backgroundColor='red'"}); });  $("#jqWebpage").click(function() {     chrome.tabs.executeScript(null, {file:"jquery.min.js"}, callback); });  function callback() {     chrome.tabs.executeScript(null, {code:"$('body').css('backgroundColor','yellow');"}); } 

So your original "doesn't work" code actually does work, just not the way you expect. It colors the background of the popup, because that's where the code is executing. To execute a jQuery code in the tab, you need to first inject jQuery and then run your code (and the only permissio this needs is activeTab). The temptation would be

$("#jqWebpage").click(function() { // don't do this     chrome.tabs.executeScript(null, {file:"jquery.min.js"});     chrome.tabs.executeScript(null, {code:"$('body').css('backgroundColor','yellow');"}); }); 

But this won't work because of asynchronicity. These two lines will run before the file is fully injected. This means that the second line of code can (and probably will) run before jQuery is fully loaded, with unknown consequences. Instead, you need to use the callback parameter to execute the script you want after jQuery has finished loading.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment