Entries Tagged as 'javascript'

Communication between HTML and XUL

As promised yesterday I post the source code for communicating between an unprivileged HTML-page and an privileged Firefox extension.

The extension code

This code is to be inserted in the extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * Provides a way of transfering arbitrary JSON objects between a HTML-page and
 * the extension; this script is to be inserted in extension code
 *
 * For Client (HTML) - Javascript, see <code>dataclient.js</code>
 *
 * @author Phil
 * @date 2007/08/23
 * @see http://forums.mozillazine.org/viewtopic.php?t=171216
 * @see http://www.json.org/js.html for JSON support
 */
var DataTransferListener = {
	ELWMS_EVENT_NAME: "ELWMSDataTransferEvent",
	ELWMS_EVENT_BACK_NAME: "ELWMSDataBackchannelEvent",
	/**
	 * Listener that subscribes to custom ELWMS Event
	 *
	 * @param {Event} aEvent the event thrown by the HTML Element
	 */
	listenToHTML: function(aEvent) {
		// first we have to check if we allow this... based on URL or what?
		if (aEvent.target.ownerDocument.location.host != "localhost") {
			// TODO: add security here (e.g. from Pref/Setting of applet serving host)!
			// alert("As for security issues only secure HTML pages may pass data to ELWMS extension.");
			// return;
		}
		// data is a escaped JSON String
		var data = unescape(aEvent.target.getAttribute("data")).parseJSON();
		// what to do with the received data?
		var retval = DataTransferListener.handleData(data, aEvent.target);
		// if back data is given:
		if (retval != null) {
			// add escaped and JSONified Object to <code>returnvalue</code>-Attribute
			aEvent.target.setAttribute("returnvalue", escape(retval.toJSONString()));
			// fire event to notify HTML-Page of return value
			var ev = window.document.createEvent("Event");
			ev.initEvent(DataTransferListener.ELWMS_EVENT_BACK_NAME, true, false);
			aEvent.target.dispatchEvent(ev);
		}
	},
	/**
	 * this function should handle all arriving data
	 *
	 * @param {Object} data The JSON Object
	 * @param {HTMLNode} target The node that fired the event
	 */
	handleData : function(data, target) {
		alert("DataTransferListener.handleData: obtained " + data.name);
		if (data.id &gt; 1000) {
			alert("DataTransferListener.handleData: returning changed data")
			return {id:2000, name:"Pong"};
		}
		return null;
	}
}
 
/**
 * Acc. to web page (see above) the 4th parameter denotes if Events are accepted from
 * unsecure sources.
 */
document.addEventListener(DataTransferListener.ELWMS_EVENT_NAME, DataTransferListener.listenToHTML, false, true);

The client-side (Javascript in HTML) code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
 * Provides a way of transfering arbitrary JSON objects from a HTML-page to a
 * extension
 *
 * For Extension (XUL) - Javascript, see <code>datatransfer.js</code>
 *
 * @author Phil
 * @date 2007/08/23
 * @see http://forums.mozillazine.org/viewtopic.php?t=171216
 * @see http://www.json.org/js.html for JSON support
 */
var Communicator = {
	ELWMS_EVENT_NAME: "ELWMSDataTransferEvent",
	ELWMS_EVENT_BACK_NAME: "ELWMSDataBackchannelEvent",
	ELWMS_CALLER_ID : "elwmsdataelement",
	ELWMS_ELEMENT_NAME : "ELWMSDataElement",
 
	/**
	 * initializes the Element and Listeners
	 *
	 */
	init : function() {
		// create data / event firing Elements
		var element = Communicator.createElement();
		// register custom event on callback
		element.addEventListener(Communicator.ELWMS_EVENT_BACK_NAME, Communicator.calledBack, true);
	},
 
	/**
	 * creates the data element
	 *
	 * @return {HTMLElement} the created Element of the type <code>Communicator.ELWMS_ELEMENT_NAME</code>
	 */
	createElement : function() {
		// may I create an Event?
		if ("createEvent" in document) {
		  	// if element is not yet existing
	  		if (!document.getElementById(Communicator.ELWMS_CALLER_ID)) {
		  		var element = document.createElement(Communicator.ELWMS_ELEMENT_NAME);
		  		element.setAttribute("id", Communicator.ELWMS_CALLER_ID);
		  		// attribute containing "data parameter" for extension call
				element.setAttribute("data", "");
				// attribute containing "return value" of extension
				element.setAttribute("returnvalue", "");
		  		document.documentElement.appendChild(element);
		  		return element;
	  		} else {
	  			// element exists - return that
	  			return document.getElementById(Communicator.ELWMS_CALLER_ID);
	  		}
	  	} else {
	  		// some error...
	  		alert("dataclient.js - Communicator.createElement ERROR!");
	  		return null;
	  	}
	},
 
	/**
	 * calls the extension with JSON - data (object)
	 *
	 * @param {Object} data the data to transfer to extension - must be convertible to JSON
	 */
	call : function(data) {
		// create or get our element
		var element = Communicator.createElement();
		element.setAttribute("data", escape(data.toJSONString()));
		// create and fire custom Event to notify extension
		var ev = document.createEvent("Event");
		ev.initEvent(Communicator.ELWMS_EVENT_NAME, true, false);
		element.dispatchEvent(ev);
	},
 
	/**
	 * is called when the extensions fires ELWMS_EVENT_BACK_NAME - Event; data
	 * may be collected from <code>returnvalue</code>-Attribute.
	 *
	 * @param {Event} aEvent the event
	 */
	calledBack : function(aEvent) {
		// TODO: decide what to do here!
		alert("Communicator.calledBack : " + unescape(aEvent.target.getAttribute("returnvalue")));
	}
};
 
function func(aEvent) {
	Communicator.call({id:1100, name:"Ping"});
}
 
/**
 * on page load, the Communicator is initialized
 */
document.addEventListener("DOMContentLoaded", function(aEvent) {
	Communicator.init();
	// add event Listener on button to test...
	document.getElementById("communicator").addEventListener("click", func, true);
}, false);

I think the code is commented well enough to be understandable without any further explanation. For a short overview how this works, see yesterday’s post.

Java Applets in XUL

Java applets from a extension directly embedded in a XUL document (via a HTML-Namespace) seem to be a difficult thing to achieve. They are not only close to impossible to reference (Java plugin obviously doesn’t know chrome://-URIs) but also seem to be restricted to 300x300px. Not good. As our project depends strongly on data exchange between an applet and XUL code I searched the dark alleys of old forum entries and found a different approach yesterday and implemented it:

  • The applet is embedded not in XUL but in a HTML page that is contained in an iframe
  • The applet may access the embedding HTML by mayscript parameter
  • In HTML a script generates a custom DOMElement, puts the data to hand over to the extension JSONified in an attribute x and finally fires a custom Event A, which is bubbling up through all parent nodes, …
  • … until the listening extension captures this custom Event A. Data is read from the attribute x, handled and return values are written in another attribute y of said DOMElement. Then the extension itself fires another custom Event B from the Element.
  • The script in the HTML listens to Event B, and — when fired — reads the returned value from attribute y and passes it to the applet.

Easy, isn’t it?

So much hoops to jump through to solve such a seemingly simple task! And that only because the Java / Firefox bridges are mostly ropeways…

Communication between HTML and extension works so far, source code will be posted tomorrow. When I finished tackling the applet-to-HTML-thingy I will update, promised.

Aspects of Javascript

A problem that I come upon with Firefox Extension Development is that often it is close to impossible to find out, which object / function provided by Firefox is called when certain Events occur. For example: in spite of my all-day taking quest to find a central piece of code to detect changes of bookmarks I still don’t know exactly what functions are called when a Bookmark is created / deleted / changed1.

But thanks to Google and some Python background (Decorators) I found Beppu’s Decorator / AOP snippet for Javascript that is incredibly useful for finding out what exactly is happening without needing to alter Firefox’s source code2. This code allows to execute a script before or after some miscellaneous code is executed. Even wrapping a function is possible without any problem… check it out!


  1. note: this is not exactly true. There is the infamous DataSource "rdf:bookmarks" of Mozillas nsIRDFService. But recognizing exact acts seems to be too tedious to really start trying… So this approach stays my last resort! 

  2. doesn’t work for native XPCOM code, only for Javascript objects / functions.