Using jquery inside injected script

Forums » Dev.Opera » Opera Extensions Development Discussions

You need to be logged in to post in the forums. If you do not have an account, please sign up first.

Go to last post

8. June 2012, 17:57:21

daun828

Posts: 8

Using jquery inside injected script

Is it possible to do so? If so, what is the appropriate method to do so..

Thanks.

9. June 2012, 04:33:41

spadija

Posts: 1634

Possible? Yes, but current ways to do it are rather inelegant. Also, keep in mind that jQuery is a rather large library, and loading it in every tab could greatly increase Opera's memory usage, slow down the browser a little bit (especially considering the way you currently have to load it) and possibly break some sites. I would advise against doing this unless it is for a limited number of sites and you know jQuery won't cause problems.

Now, if I haven't otherwise deterred you, here's how you would do it:

// background script
var jQuery = null;

// Load the entire jQuery script into memory as a string. Save the result so
// we don't have to keep reloading it each time a tab needs it.
// Alternatively, you could decrease memory usage by loading it each time
// a tab connects at the cost of speed
(function () {
	// do this synchronously to make sure the script is loaded before any 
	// tab can ask for it.
	// I am assuming you are using the minified jQuery script and that it
	// is located in a folder named "js". Change the path to match where
	// your script is. (do use the minified script to reduce memory usage.)
	var xhr = new XMLHttpRequest();
	xhr.open('/js/jquery-min.js', true);	
	xhr.send(null);

	if (xhr.status == 200)
		jQuery = xhr.responseText;
	else
		console.log('Failed to load jQuery');
})();

// Each time a tab connects, send it the jQuery script
opera.extension.onconnect = function (e) {
	e.source.postMessage({
		action: 'load',
		script: jQuery,
	});
}


// injected script
// ==UserScript==
// @include http://some-site.to-jquerify.com/*
// ==/UserScript==

opera.extension.onmessage = function (e) {
	// When the background script tells the injected script to load a script,
	// make a new <script> tag, fill it with the script code and jam it into 
	// the <head> element.
	if (e.data.action === 'load') {
		var script = document.createElement('script');
		script.appendChild(document.createTextNode(e.data.script));
		document.head.appendChild(script);
	}
}

Note: I haven't tested this, so there might be a couple typos or other errors, but I think it should work.

Also, if you want a preview of how this will work in the future, check out this experimental build with the resource loader API.

10. June 2012, 09:30:18

d4n3

Posts: 957

Why exactly do you need jQuery?

For basic page manipulation, you can just as easily use native dom, with modern stuff like querySelectorAll

14. June 2012, 19:51:40

daun828

Posts: 8

I need to get Json data from IMDBapi.com.
Somehow I can't find the way to do it without using Jquery getJson callback.

14. June 2012, 22:22:44

spadija

Posts: 1634

Use an XMLHttpRequest and set its responseType to 'json'.

Of course, if you plan on injecting this script anywhere but IMBDBapi.com, this will only work if IMDBapi.com allows cross-origin resource sharing and you're using Opera 12. If not, you cannot directly get data from it in the injected script (You will probably get security exceptions instead). Instead, make the web request from your background process (which has no cross-origin restrictions) and use messaging to send the result back to the injected script.

In other words, if you were planning on doing this, but IMDBapi.com doesn't send CORS headers...
1. Injected script requests data from IMDBapi.com
2. IMDBapi.com responds
3. Injected script does something with the data

...you need to do this instead:
1. Injected script uses opera.extension.postMessage() to command the background process to make a request
2. Background process uses opera.extension.onmessage to receive the command
3. Background process requests data from IMDBapi.com
4. IMDBapi.com responds
5. Background process uses event.source.postMessage() to send the data to the tab which requested it
6. Injected script uses opera.extension.onmessage to receive the data
7. Injected script does something with the data


Also, make sure your config.xml has an <access> tag which allows access to the site you're making requests to, or requests from the background process will always fail.

15. June 2012, 06:16:58

d4n3

Posts: 957

I just checked, and IMDBApi does support CORS (Access-Control-Allow-Origin: *). (I used Dragonfly's network logger to look at the response headers at http://www.imdbapi.com/?t=True%20Grit&y=1969 )

Makes sense, since cross-origin requests would be their primary use case.

So really, what you want is just use something like this:

<!doctype html>
<script>

function getJson(url, params, callback) {
	params = params || {};
	var q = [];
	
	for (var key in params) {
		if (params.hasOwnProperty(key)) {
			q.push(
				encodeURIComponent(key)+"="+
				encodeURIComponent(JSON.stringify(params[key]))
			);
		}
	}
	
	q = q.join("&");
	
	var xhr = new window.XMLHttpRequest();
	xhr.responseType = "json";
	
	xhr.onreadystatechange = function() {
		if (xhr.readyState === 4 && xhr.status === 200) {
			var json = xhr.response;
			
			callback(json);
		}
	}
	
	xhr.open('GET', url+"?"+q, true);
	xhr.send(null);
}

getJson("http://www.imdbapi.com/", {
	t: "True Grit",
	y: 1969
}, function(data){
	console.log(data);
	
	window.document.querySelector("body").innerText = JSON.stringify(data);
});


</script>



You can save the above as a .html file to test it in your browser.

the getJSON accepts 3 parameters: url, params and callback.

params is an object where you set the url query params (like in this case {t: "True grit", y: 1969} ) and the function converts it into url query params ?t=True%20Grit&y=1969

The callback is the function that handles the response, which receives one parameter, the JSON result data.

15. June 2012, 12:29:25

daun828

Posts: 8

Wow.. thanks for the code.
I missed this part,
" callback(json); "

I don't know callback can be done using native js as most example that I see only use Jquery for that.

Thanks a lot for the code.

15. June 2012, 12:38:34

d4n3

Posts: 957

There's nothing magical about what jQuery is doing, it does exactly the same thing in the background, and its something that's built into JavaScript (functions as objects).

Callbacks are only functions, and the way this works is that the event onreadystatechange is triggered when the data is loaded asynchronously, the data is passed to the callback. You will notice that the nested functions have access to local variables outside of them - you can access callback and xhr from inside the nested onreadystatechange function.

This is called a closure.

I highly recommend watching "JavaScript: The Good Parts" lecture and reading the book with the same name by Douglas Crockford.

It will help you understand JavaScript better and you will see that what jQuery is doing is actually using JavaScript's best features in a very elegant way.

15. June 2012, 13:52:01

spadija

Posts: 1634

Also, it might be useful to change
if (xhr.readyState === 4 && xhr.status === 200) {
	var json = xhr.response;	
	callback(json);
}

to
if (xhr.readyState === 4) { 
	if (xhr.status === 200) {
		var json = xhr.response;
		callback(json);
	} else {
		// There was an error. Inform the user (or at least print something to the error log with console.log) 
		// so that they won't wonder why nothing is happening
	}
}

Forums » Dev.Opera » Opera Extensions Development Discussions