Ivan Zuzak home about blog projects talks & papers other

Pmrpc discovery and publish-subscribe support + systematization of cross-context browser communication systems

15 Jun 2010

EDIT 29 August 2011: I've published two papers on concepts presented in this blog post. First, I've published a paper about the Pmrpc library, its architecture and benefits over other libraries, at the MIPRO 2011 conference. Also, I've published a paper about the classification framework for cross-context communication in Web browsers. The paper presents a more detailed and advanced analysis of cross-context communication than the one addressed in the second part of this blog post. Furthermore, a few moths ago I gave a talk on inter-widget communication at the Open University, UK. Since inter-widget communication significantly overlaps with cross-context communication, the talk touches on many concepts from the cross-context communication framework. Links for downloading the papers and presentation slides are in the talks and papers section.

The pmrpc cross-context browser communication library has grown from the last time I blogged about it. Last time I wrote about adding support for RPC-style communication for WebWorkers. Today I'll first introduce two new features - dynamic discovery of remote procedures and a publish-subscribe communication model. Also, I'll write about our systematization of existing systems for cross-context browser communication.

Discovery and publish subscribe

Up until now, pmrpc supported only remote procedure call as the communication model for inter-window and WebWorker communication. This model of communication is based on a client-server interaction where the client both a) has an object reference of the destination window/webworker context and b) knows the name of the remote procedure it wants to call. Here's an example of a pmrpc RPC call:

1 // server [window object A] exposes a procedure in it's window
2 pmrpc.register( {
3   publicProcedureName : "HelloPMRPC",
4   procedure : function(printParam) { alert(printParam); }
5 } );
1 // client [window object B] calls the exposed procedure remotely, from its own window
2 pmrpc.call( {
3   destination : window.frames["myIframe"],
4   publicProcedureName : "HelloPMRPC",
5   params : ["Hello World!"]
6 } );

This works well for cases when the client knows how to obtain a reference of the server context object. However, it turns out that in many real-world cases the client does not know which window/iframe/worker implements the procedure it wants to call. Mashups and widget portals are the best example. Imagine that both the client and the server are widgets on iGoogle. Although you may be in control of both the client and server iframe contents, in most cases you will not know the names of iframe elements which contain them and which are located on the top container page. And you need the values of the name attributes so that you can obtain a reference to the iframe object which you could then use to call postMessage.

This referencing problem was the motivation for implementing both the discovery and publish-subscribe features. Both features enable clients to call a remote procedure without a-priori knowing which destination object implements the  procedure. First, the publish-subscribe feature enables clients to perform a remote call without specifying the destination context object reference. If a publish-subscribe call is made, the pmrpc infrastructure will make a RPC call for the specified procedure to all window, iframe and WebWorker object it can find (thus simulating a "publish" event on a channel named by the remote procedure name). The API for using the publish-subscribe feature is simple; it reuses the pmrpc.call method and instead of passing the reference of the destination context object, a "publish" string parameter is passed instead:

1 // server [window object A] exposes a procedure in it's window
2 pmrpc.register( {
3   publicProcedureName : "HelloPMRPC",
4   procedure : function(printParam) { alert(printParam); }
5 } );
1 // client [window object B] calls the exposed procedure remotely, from its own window
2 pmrpc.call( {
3   destination : "publish",
4   publicProcedureName : "HelloPMRPC",
5   params : ["Hello World!"]
6 } );

Under the hood, the publish-subscribe feature uses the discovery feature to discover all window, iframe and WebWorker context objects and retrieves their references. Also, the publish-subscribe feature may be used with other pmrpc features, like access control and retries.

The discovery feature enables dynamic discovery of procedures registered at remote contexts and their filtering using various optional criteria. Discovered procedures may be filtered by the destination window/iframe/webworker context object, destination context origin (specified as a regular expression) and destination procedure name (also specified as a regular expression). For each discovered procedure that wasn't filtered out, the procedure name, origin of the destination context, the access control rules specified for the procedure and the destination context object. The feature is implemented as a new pmrpc method - pmrpc.discover. The method is asynchronous and accepts a parameter object which specifies the filters and a callback method which will be called when all registered procedures are discovered. Here's an example:

1 // server [window object A] exposes a procedure in it's window
2 pmrpc.register( {
3   publicProcedureName : "HelloPMRPC",
4   procedure : function(printParam) { alert(printParam); }
5 } );
 1 // client [window object B] discovers and calls the exposed procedure remotely, from its own window
 2 var discoCallback = function(discoveredProcedures) {
 3   if (discoveredProcedures.length > 0) {
 4     console.log(discoveredProcedures[0].publicProcedureName);
 5     console.log(discoveredProcedures[0].destinationOrigin);
 6     console.log(discoveredProcedures[0].procedureACL);
 7     pmrpc.call( {
 8       destination : discoveredProcedures[0].destination,
 9       publicProcedureName : "HelloPMRPC",
10       params : ["Hello World!"]
11     } );
12   }
13 };
14 pmrpc.discover( {
15   destination : [window.frames["someiFrameName"], window.frames["myIframe"]],
16   origin : ".*goodOrigin.*",
17   publicProcedureName : ".*Hello.*",
18   callback : discoCallback
19 } );

The implementation of the discover method is based on recursively visiting every iframe in the window.frames object starting from window.top:

  1. The method fetches a reference to the top window object (window.top), adds the reference to the result array and repeats the procedure for each nested iframe (by iterating through the window.frames object). This way, all iframes in the current window are visited. This process is depicted by the figure below.
  2. The method adds all local web worker objects to the result array.
  3. On each context object in the result array, a special pmrpc infrastructure method is invoked to obtain a list of registered procedures.
  4. The list of all obtained procedures is filtered by the specified criteria.
  5. The method invokes the callback, passing the list of discovered procedures as a parameter.

It's not an ideal way of discovering remote contexts, but it's better than nothing and works with the latest versions of all major browsers.

Systematization of cross-context browser communication systems

It's evident that the WWW is becoming ever more componentized as almost every web site is a mashup, contains widgets or uses WebWorkers. As a result, there has been a increase in development of systems that enable communication between these different browser contexts - windows, iframes and WebWorkers. Pmrpc is only one example of such system and lots of other systems exist. However, as the number of these systems increases, the understanding of each systems' capabilities and their comparison and evaluation becomes a more difficult task since no effort is being made to systematize this ecosystem.

This is why Marko and I are trying to create a systematization of existing cross-context communication systems. Our work on this systematization is in its early stages but is openly available to anyone at a pmrpc wiki page. At the moment we are trying to do the following:

  1. Make a list of all mechanisms for cross-context communication. We are including both in-browser mechanisms like the postMessage API, cookies and URL fragment, and 3rd party libraries like easyXDM and pmrpc.
  2. Define dimensions for classifying the gathered systems. Each dimension defines a system characteristic whereas values along each dimension define a set of possible design choices.  Currently, we have defined about a dozen dimension and are still thinking some through. Here are some examples: Type of system (browser built-in, pure client-side framework, server-mediated initialization framework, server-mediated communication framework), Transport mechanism (cookies, URL fragment, postMessage, ...), Programming model (message-oriented, shared memory, RPC, publish-subscribe).
  3. Evaluate the listed systems according to the defined dimensions.

I'd love to hear what you think of the new pmrpc features and the systematization (see more examples at our API docs page)! Do you know of any cross-context communication systems that are not listed? Do you think some other system characteristic should be included in the classification? @izuzak

Comments