You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by jo...@apache.org on 2008/06/28 02:05:51 UTC

svn commit: r672433 - /incubator/shindig/trunk/features/rpc/rpc.js

Author: johnh
Date: Fri Jun 27 17:05:51 2008
New Revision: 672433

URL: http://svn.apache.org/viewvc?rev=672433&view=rev
Log:
Reverting NIX-related rpc.js changes for now. Makes sense to JIRAfy it for a while.


Modified:
    incubator/shindig/trunk/features/rpc/rpc.js

Modified: incubator/shindig/trunk/features/rpc/rpc.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/rpc/rpc.js?rev=672433&r1=672432&r2=672433&view=diff
==============================================================================
--- incubator/shindig/trunk/features/rpc/rpc.js (original)
+++ incubator/shindig/trunk/features/rpc/rpc.js Fri Jun 27 17:05:51 2008
@@ -18,7 +18,7 @@
 
 /**
  * @fileoverview Remote procedure call library for gadget-to-container,
- * container-to-gadget, and gadget-to-gadget (thru container) communication.
+ * container-to-gadget, and gadget-to-gadget communication.
  */
 
 var gadgets = gadgets || {};
@@ -29,30 +29,6 @@
  * @name gadgets.rpc
  */
 gadgets.rpc = function() {
-  // General constants.
-  var CALLBACK_NAME = '__cb';
-  var DEFAULT_NAME = '';
-
-  // Consts for FrameElement.
-  var FE_G2C_CHANNEL = '__g2c_rpc';
-  var FE_C2G_CHANNEL = '__c2g_rpc';
- 
-  // Consts for NIX. VBScript doesn't
-  // allow items to start with _ for some reason,
-  // so we need to make these names quite unique, as 
-  // they will go into the global namespace.
-  var NIX_WRAPPER = 'GRPC____NIXVBS_wrapper';
-  var NIX_GET_WRAPPER = 'GRPC____NIXVBS_get_wrapper';
-  var NIX_HANDLE_MESSAGE = 'GRPC____NIXVBS_handle_message';
-  var NIX_CREATE_CHANNEL = 'GRPC____NIXVBS_create_channel';
-  var NIX_ID_FIELD = 'GRPC____NIXVBS_container';
- 
-  // JavaScript reference to the NIX VBScript wrappers.
-  // Gadgets will have but a single channel under
-  // nix_channels['..'] while containers will have a channel
-  // per gadget stored under the gadget's ID.
-  var nix_channels = {};
-
   var services = {};
   var iframePool = [];
   var relayUrl = {};
@@ -60,135 +36,28 @@
   var authToken = {};
   var callId = 0;
   var callbacks = {};
-  var setup = {};
-
-  var params = {};
-
-  // Load the authentication token for speaking to the container
-  // from the gadget's parameters, or default to '0' if not found.
-  if (gadgets.util) {
-    params = gadgets.util.getUrlParameters();
-  }
 
+  var params = gadgets.util.getUrlParameters();
   authToken['..'] = params.rpctoken || params.ifpctok || 0;
 
-  // Pick the most efficient RPC relay mechanism:
-  //
-  // * For those browsers that support native messaging (various implementations
-  //   of the HTML5 postMessage method), use that. Officially defined at
-  //   http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html.
-  //   
-  //   postMessage is a native implementation of XDC. A page registers that
-  //   it would like to receive messages by listening the the "message" event
-  //   on the window (document in DPM) object. In turn, another page can
-  //   raise that event by calling window.postMessage (document.postMessage
-  //   in DPM) with a string representing the message and a string
-  //   indicating on which domain the receiving page must be to receive
-  //   the message. The target page will then have its "message" event raised
-  //   if the domain matches and can, in turn, check the origin of the message
-  //   and process the data contained within.
-  //  
-  //     wpm: postMessage on the window object.
-  //        - Internet Explorer 8+
-  //        - Safari (latest nightlies as of 26/6/2008)
-  //        - Firefox 3+
-  //        - Opera 9+
-  //
-  //     dpm: postMessage on the document object.
-  //        - Opera 8+
-  //
-  // * For Internet Explorer before version 8, the security model allows a
-  //   child to set the value of the "opener" property on the parent window
-  //   and vice versa. This method is dubbed "Native IE XDC" (NIX). 
-  //   
-  //   This method works by placing a handler object in the "opener" property
-  //   of a gadget when the container sets up the authentication information
-  //   for that gadget (by calling setAuthToken(...)). At that point, a NIX
-  //   wrapper is created and placed into the gadget by calling
-  //   theframe.contentWindow.opener = wrapper. Note that as a result, NIX can
-  //   only be used by a container to call a particular gadget *after* that
-  //   gadget has called the container at least once via NIX.
-  //
-  //   The NIX wrappers in this RPC implementation are instances of a VBScript
-  //   class that is created when this implementation loads. The reason for
-  //   using a VBScript class stems from the fact that any object can be passed
-  //   into the opener property.
-  //   While this is a good thing, as it lets us pass functions and setup a true
-  //   bidirectional channel via callbacks, it opens a potential security hole
-  //   by which the other page can get ahold of the "window" or "document"
-  //   objects in the parent page and in turn wreak havok. This is due to the
-  //   fact that any JS object useful for establishing such a bidirectional
-  //   channel (such as a function) can be used to access a function
-  //   (eg. obj.toString, or a function itself) created in a specific context,
-  //   in particular the global context of the sender. Suppose container
-  //   domain C passes object obj to gadget on domain G. Then the gadget can
-  //   access C's global context using:
-  //   var parentWindow = (new obj.toString.constructor("return window;"))();
-  //   Nulling out all of obj's properties doesn't fix this, since IE helpfully 
-  //   restores them to their original values if you do something like:
-  //   delete obj.toString; delete obj.toString;
-  //   Thus, we wrap the necessary functions and information inside a VBScript
-  //   object. VBScript objects in IE, like DOM objects, are in fact COM
-  //   wrappers when used in JavaScript, so we can safely pass them around
-  //   without worrying about a breach of context while at the same time
-  //   allowing them to act as a pass-through mechanism for information
-  //   and function calls. The implementation details of this VBScript wrapper
-  //   can be found in the setupChannel() method below.
-  //
-  //     nix: Internet Explorer-specific window.opener trick.
-  //       - Internet Explorer 6
-  //       - Internet Explorer 7
-  //
-  // * For Gecko-based browsers, the security model allows a child to call a
-  //   function on the frameElement of the iframe, even if the child is in
-  //   a different domain. This method is dubbed "frameElement" (fe).
-  //
-  //   The ability to add and call such functions on the frameElement allows
-  //   a bidirectional channel to be setup via the adding of simple function
-  //   references on the frameElement object itself. In this implementation,
-  //   when the container sets up the authentication information for that gadget
-  //   (by calling setAuth(...)) it as well adds a special function on the
-  //   gadget's iframe. This function can then be used by the gadget to send
-  //   messages to the container. In turn, when the gadget tries to send a
-  //   message, it checks to see if this function has its own function stored
-  //   that can be used by the container to call the gadget. If not, the
-  //   function is created and subsequently used by the container.
-  //   Note that as a result, FE can only be used by a container to call a
-  //   particular gadget *after* that gadget has called the container at
-  //   least once via FE.
-  //
-  //     fe: Gecko-specific frameElement trick.
-  //        - Firefox 1+
-  //
-  // * For all others, we have a fallback mechanism known as "ifpc". IFPC
-  //   exploits the fact that while same-origin policy prohibits a frame from
-  //   accessing members on a window not in the same domain, that frame can,
-  //   however, navigate the window heirarchy (via parent). This is exploited by
-  //   having a page on domain A that wants to talk to domain B create an iframe
-  //   on domain B pointing to a special relay file and with a message encoded
-  //   after the hash (#). This relay, in turn, finds the page on domain B, and
-  //   can call a receipt function with the message given to it. The relay URL
-  //   used by each caller is set via the gadgets.rpc.setRelayUrl(..) and
-  //   *must* be called before the call method is used.
-  //
-  //     ifpc: Iframe-based method, utilizing a relay page, to send a message.
-  //
-  var relayChannel = typeof window.postMessage === 'function' ? 'wpm' :
-		     typeof document.postMessage === 'function' ? 'dpm' :
-		     window.ActiveXObject ? 'nix' : 
-                     navigator.product === 'Gecko' ? 'fe' :
+  // Pick the most efficient RPC relay mechanism
+  var relayChannel = typeof document.postMessage === 'function' ? 'dpm' :
+                     typeof window.postMessage === 'function' ? 'wpm' :
                      'ifpc';
+  if (relayChannel === 'dpm' || relayChannel === 'wpm') {
+    window.addEventListener('message', function(packet) {
+      // TODO validate packet.domain for security reasons
+      process(gadgets.json.parse(packet.data));
+    }, false);
+  }
 
-  // Conduct any setup necessary for the chosen channel.
-  setupChannel();
-
-  // Create the Default RPC handler.
-  services[DEFAULT_NAME] = function() {
+  // Default RPC handler
+  services[''] = function() {
     throw new Error('Unknown RPC service: ' + this.s);
   };
 
-  // Create a Special RPC handler for callbacks.
-  services[CALLBACK_NAME] = function(callbackId, result) {
+  // Special RPC handler for callbacks
+  services['__cb'] = function(callbackId, result) {
     var callback = callbacks[callbackId];
     if (callback) {
       delete callbacks[callbackId];
@@ -197,147 +66,6 @@
   };
 
   /**
-   * Conducts any initial global work necessary to setup the
-   * channel type chosen.
-   */
-  function setupChannel() {
-    // If the channel type is one of the native
-    // postMessage based ones, setup the handler to receive
-    // messages.
-    if (relayChannel === 'dpm' || relayChannel === 'wpm') {
-      window.addEventListener('message', function(packet) {
-        // TODO validate packet.domain for security reasons
-        process(gadgets.json.parse(packet.data));
-      }, false);
-    }
-
-    // If the channel type is NIX, we need to ensure the
-    // VBScript wrapper code is in the page and that the
-    // global Javascript handlers have been set.
-    if (relayChannel === 'nix') {
-      // VBScript methods return a type of 'unknown' when
-      // checked via the typeof operator in IE. Fortunately
-      // for us, this only applies to COM objects, so we
-      // won't see this for a real Javascript object.
-      if (typeof window[NIX_GET_WRAPPER] !== 'unknown') {
-        window[NIX_HANDLE_MESSAGE] = function(data) {
-          process(gadgets.json.parse(data));
-        };
-
-        window[NIX_CREATE_CHANNEL] = function(name, channel, token) {
-          // Verify the authentication token of the gadget trying
-          // to create a channel for us.
-          if (authToken[name] == token) {
-            nix_channels[name] = channel;
-          }
-        };
-
-        // Inject the VBScript code needed.
-        var vbscript = 
-          '<scr' + 'ipt language="VBScript">'
-          // We create a class to act as a wrapper for
-          // a Javascript call, to prevent a break in of
-          // the context.
-          + 'Class ' + NIX_WRAPPER + '\n '
-
-          // An internal member for keeping track of the
-          // name of the document (container or gadget)
-          // for which this wrapper is intended. For
-          // those wrappers created by gadgets, this is not
-          // used (although it is set to "..")
-          + 'Private m_Intended\n'
-
-          // Method for internally setting the value
-          // of the m_Intended property.
-          + 'Public Sub SetIntendedName(name)\n '
-          + 'm_Intended = name\n'
-          + 'End Sub\n' 
-
-          // A wrapper method which actually causes a 
-          // message to be sent to the other context.
-          + 'Public Sub SendMessage(data)\n '
-          + NIX_HANDLE_MESSAGE + '(data)\n'
-          + 'End Sub\n' 
-
-          // Method for setting up the container->gadget
-          // channel. Not strictly needed in the gadget's
-          // wrapper, but no reason to get rid of it. Note here
-          // that we pass the intended name to the NIX_CREATE_CHANNEL
-          // method so that it can save the channel in the proper place
-          // *and* verify the channel via the authentication token passed
-          // here.
-          + 'Public Sub CreateChannel(channel, auth)\n '
-          + 'Call ' + NIX_CREATE_CHANNEL + '(m_Intended, channel, auth)\n'
-          + 'End Sub\n'
-
-          // An empty field with a unique identifier to
-          // prevent the code from confusing this wrapper
-          // with a run-of-the-mill value found in window.opener.
-          + 'Public Sub ' + NIX_ID_FIELD + '()\n '
-          + 'End Sub\n'
-          + 'End Class\n '
-
-          // Function to get a reference to the wrapper.
-          + 'Function ' + NIX_GET_WRAPPER + '(name)\n'
-          + 'Dim wrap\n'
-          + 'Set wrap = New ' + NIX_WRAPPER + '\n'
-          + 'wrap.SetIntendedName name\n'
-          + 'Set ' + NIX_GET_WRAPPER + ' = wrap\n'
-          + 'End Function'
-          + '</scr' + 'ipt>'
-
-          // Resets the "default" scripting language in IE back
-          // to Javascript. This is needed or else any script
-          // tags without a proper Language="..." will be treated as
-          // VBScript once this code is added to the document.
-          + '<scri' + 'pt language="JScript"></scr' + 'ipt>';
-
-        // Note that this code can only be run once the document has
-        // fully loaded.
-        // TODO: Perhaps add some sort of check here for this?
-        document.write(vbscript);
-      }
-    }
-  }
-
-  /**
-   * Conducts any frame-specific work necessary to setup
-   * the channel type chosen. This method is called when
-   * the container page first registers the gadget in the
-   * RPC mechanism. Gadgets, in turn, will complete the setup
-   * of the channel once they send their first messages.
-   */
-  function setupFrame(frameId) {
-    if (setup[frameId]) {
-      return;
-    }
-
-    if (relayChannel === 'fe') {
-      try {
-        var frame = document.getElementById(frameId);
-        frame[FE_G2C_CHANNEL] = function(args) {
-          process(gadgets.json.parse(args));
-        };
-      } catch (e) {
-        // Something went wrong. System will fallback to
-        // IFPC.
-      }
-    }
-
-    if (relayChannel === 'nix') {
-      try {
-        var frame = document.getElementById(frameId);
-        frame.contentWindow.opener = window[NIX_GET_WRAPPER](frameId);
-      } catch (e) {
-        // Something went wrong. System will fallback to
-        // IFPC.
-      }
-    }
-
-    setup[frameId] = true;
-  }
-
-  /**
    * Encodes arguments for the legacy IFPC wire format.
    *
    * @param {Object} args
@@ -358,17 +86,8 @@
    * @private
    */
   function process(rpc) {
-    //
-    // RPC object contents:
-    //   s: Service Name
-    //   f: From
-    //   c: The callback ID or 0 if none.
-    //   a: The arguments for this RPC call.
-    //   t: The authentication token.
-    //
     if (rpc && typeof rpc.s === 'string' && typeof rpc.f === 'string' &&
         rpc.a instanceof Array) {
-
       // Validate auth token.
       if (authToken[rpc.f]) {
         // We allow type coercion here because all the url params are strings.
@@ -377,158 +96,43 @@
         }
       }
 
-      // Call the requested RPC service.
-      var result = (services[rpc.s] ||
-                    services[DEFAULT_NAME]).apply(rpc, rpc.a);
-
-      // If there is a callback for this service, initiate it as well.
-      if (rpc.c) {
-        gadgets.rpc.call(rpc.f, CALLBACK_NAME, null, rpc.c, result);
-      }
-    }
-  }
-
-  /**
-   * Attempts to conduct an RPC call to the specified
-   * target with the specified data via the NIX
-   * method. If this method fails, the system attempts again
-   * using the known default of IFPC.
-   *
-   * @param {String} targetId Module Id of the RPC service provider.
-   * @param {String} from Module Id of the calling provider.
-   * @param {Object} rpcData The RPC data for this call.
-   */
-  function callNIX(targetId, from, rpcData) {
-    try {
-      if (from != '..') {
-        // Call from gadget to the container.
-        var handler = nix_channels['..'];
-
-        // If the gadget has yet to retrieve a reference to
-        // the NIX handler, try to do so now. We don't do a
-        // typeof(window.opener[NIX_ID_FIELD]) check here
-        // because it means accessing that field on the COM object, which,
-        // being an internal function reference, is not allowed.
-        // "in" works because it merely checks for the prescence of 
-        // the key, rather than actually accessing the object's property.
-        if (!handler && window.opener && NIX_ID_FIELD in window.opener) {
-          handler = nix_channels['..'] = window.opener;
-
-          // Create the channel to the parent/container. We pass both our
-          // own wrapper and our authentication token for verification.
-          handler.CreateChannel(window[NIX_GET_WRAPPER]('..'), authToken['..']);
-        }
-
-        // If we have a handler, call it.
-        if (handler) {
-          handler.SendMessage(rpcData);
-          return;
-        }
-      } else {
-        // Call from container to a gadget[targetId].
-
-        // If we have a handler, call it.
-        if (nix_channels[targetId]) {
-          nix_channels[targetId].SendMessage(rpcData);
-          return;
-        }
-      }
-    } catch (e) {
-    }
-
-    // If we have reached this point, something has failed
-    // with the NIX method, so we default to using
-    // IFPC for this call.
-    callIFPC(targetId, from, rpcData);
-  }
-  
-  /**
-   * Attempts to conduct an RPC call to the specified
-   * target with the specified data via the FrameElement
-   * method. If this method fails, the system attempts again
-   * using the known default of IFPC.
-   *
-   * @param {String} targetId Module Id of the RPC service provider.
-   * @param {String} from Module Id of the calling provider.
-   * @param {Object} rpcData The RPC data for this call.
-   */
-  function callFrameElement(targetId, from, rpcData) {
-    try {
-      if (from != '..') {
-        // Call from gadget to the container.
-        var fe = window.frameElement;
-      
-        if (typeof fe[FE_G2C_CHANNEL] === 'function') {
-          // Complete the setup of the FE channel if need be.
-          if (typeof fe[FE_G2C_CHANNEL][FE_C2G_CHANNEL] !== 'function') {
-            fe[FE_G2C_CHANNEL][FE_C2G_CHANNEL] = function(args) {
+      // The Gecko engine used by FireFox etc. allows an IFrame to directly call
+      // methods on the frameElement property added by the container page even
+      // if their domains don't match.
+      // Here we try to set up a relay channel using the frameElement technique
+      // to greatly reduce the latency of cross-domain calls if the postMessage
+      // method is not supported.
+      if (relayChannel === 'ifpc') {
+        if (rpc.f === '..') {
+          // Container-to-gadget call
+          try {
+            var fel = window.frameElement;
+            if (typeof fel.__g2c_rpc === 'function' &&
+                typeof fel.__g2c_rpc.__c2g_rpc != 'function') {
+              fel.__g2c_rpc.__c2g_rpc = function(args) {
+                process(gadgets.json.parse(args));
+              };
+            }
+          } catch (e) {
+          }
+        } else {
+          // Gadget-to-container call
+          var iframe = document.getElementById(rpc.f);
+          if (iframe && typeof iframe.__g2c_rpc != 'function') {
+            iframe.__g2c_rpc = function(args) {
               process(gadgets.json.parse(args));
             };
           }
-
-          // Conduct the RPC call.
-          fe[FE_G2C_CHANNEL](rpcData);
-          return;
-        }
-      } else {
-        // Call from container to gadget[targetId].
-        var frame = document.getElementById(targetId);
-
-        if (typeof frame[FE_G2C_CHANNEL] === 'function' &&
-            typeof frame[FE_G2C_CHANNEL][FE_C2G_CHANNEL] === 'function') {
-
-          // Conduct the RPC call.
-          frame[FE_G2C_CHANNEL][FE_C2G_CHANNEL](rpcData);
-          return;
         }
       }
-    } catch (e) {
-    }
-
-    // If we have reached this point, something has failed
-    // with the FrameElement method, so we default to using
-    // IFPC for this call.
-    callIFPC(targetId, from, rpcData);
-  }
-
-  /**
-   * Conducts an RPC call to the specified
-   * target with the specified data via the IFPC
-   * method.
-   *
-   * @param {String} targetId Module Id of the RPC service provider.
-   * @param {String} from Module Id of the calling provider.
-   * @param {Object} rpcData The RPC data for this call.
-   */
-  function callIFPC(targetId, from, rpcData) {
-    // Retrieve the relay file used by IFPC. Note that
-    // this must be set before the call, and so we conduct
-    // an extra check to ensure it is not blank.
-    var relay = gadgets.rpc.getRelayUrl(targetId);
-
-    if (!relay) {
-      throw new Error('No relay file assigned for IFPC');
-    }
 
-    // The RPC mechanism supports two formats for IFPC (legacy and current).
-    var src = null;
-    if (useLegacyProtocol[targetId]) {
-      // Format: #iframe_id&callId&num_packets&packet_num&block_of_data
-      src = [relay, '#', encodeLegacyData([from, callId, 1, 0,
-             encodeLegacyData([from, serviceName, '', '', from].concat(
-               Array.prototype.slice.call(arguments, 3)))])].join('');
-    } else {
-      // Format: #targetId & sourceId@callId & packetNum & packetId & packetData
-      src = [relay, '#', targetId, '&', from, '@', callId,
-             '&1&0&', encodeURIComponent(rpcData)].join('');
+      var result = (services[rpc.s] || services['']).apply(rpc, rpc.a);
+      if (rpc.c) {
+        gadgets.rpc.call(rpc.f, '__cb', null, rpc.c, result);
+      }
     }
-
-    // Conduct the IFPC call by creating the Iframe with
-    // the relay URL and appended message.
-    emitInvisibleIframe(src);
   }
 
-
   /**
    * Helper function to emit an invisible IFrame.
    * @param {String} src SRC attribute of the IFrame to emit.
@@ -617,15 +221,6 @@
      * @member gadgets.rpc
      */
     register: function(serviceName, handler) {
-      if (serviceName == CALLBACK_NAME) {
-        throw new Error("Cannot overwrite callback service");
-      }
-
-      if (serviceName == DEFAULT_NAME) {
-        throw new Error("Cannot overwrite default service:" 
-                        + " use registerDefault");
-      }
-
       services[serviceName] = handler;
     },
 
@@ -636,15 +231,6 @@
      * @member gadgets.rpc
      */
     unregister: function(serviceName) {
-      if (serviceName == CALLBACK_NAME) {
-        throw new Error("Cannot delete callback service");
-      }
-
-      if (serviceName == DEFAULT_NAME) {
-        throw new Error("Cannot delete default service:" 
-                        + " use unregisterDefault");
-      }
-
       delete services[serviceName];
     },
 
@@ -686,14 +272,12 @@
       if (callback) {
         callbacks[callId] = callback;
       }
-
-      // Default to the container calling.
-      var from = '..';
-
+      var from;
       if (targetId === '..') {
         from = window.name;
+      } else {
+        from = '..';
       }
-
       // Not used by legacy, create it anyway...
       var rpcData = gadgets.json.stringify({
         s: serviceName,
@@ -703,37 +287,53 @@
         t: authToken[targetId]
       });
 
-      var channelType = relayChannel;
-
-      // If we are told to use the legacy format, then we must
-      // default to IFPC.
       if (useLegacyProtocol[targetId]) {
-        channelType = 'ifpc';
+        relayChannel = 'ifpc';
       }
 
-      switch (channelType) {
-        case 'dpm': // use document.postMessage.
-          var targetDoc = targetId === '..' ? parent.document :
-                                              frames[targetId].document;
-          targetDoc.postMessage(rpcData);
-          break;
-
-        case 'wpm': // use window.postMessage.
-          var targetWin = targetId === '..' ? parent : frames[targetId];
-          targetWin.postMessage(rpcData, "*");
-          break;
-
-        case 'nix': // use NIX.
-          callNIX(targetId, from, rpcData);
-          break;
-
-        case 'fe': // use FrameElement.
-          callFrameElement(targetId, from, rpcData);
-          break;
-
-        default: // use 'ifpc' as a fallback mechanism.
-          callIFPC(targetId, from, rpcData);
-          break;
+      switch (relayChannel) {
+      case 'dpm': // use document.postMessage
+        var targetDoc = targetId === '..' ? parent.document :
+                                            frames[targetId].document;
+        targetDoc.postMessage(rpcData);
+        break;
+      case 'wpm': // use window.postMessage
+        var targetWin = targetId === '..' ? parent : frames[targetId];
+        targetWin.postMessage(rpcData, "*");
+        break;
+      default: // use 'ifpc' as a fallback mechanism
+        var relay = gadgets.rpc.getRelayUrl(targetId);
+        // TODO split message if too long
+        var src;
+        if (useLegacyProtocol[targetId]) {
+          // #iframe_id&callId&num_packets&packet_num&block_of_data
+          src = [relay, '#', encodeLegacyData([from, callId, 1, 0,
+                 encodeLegacyData([from, serviceName, '', '', from].concat(
+                 Array.prototype.slice.call(arguments, 3)))])].join('');
+        } else {
+          // Try the frameElement channel if available
+          try {
+            if (from === '..') {
+              // Container-to-gadget
+              var iframe = document.getElementById(targetId);
+              if (typeof iframe.__g2c_rpc.__c2g_rpc === 'function') {
+                iframe.__g2c_rpc.__c2g_rpc(rpcData);
+                return;
+              }
+            } else {
+              // Gadget-to-container
+              if (typeof window.frameElement.__g2c_rpc === 'function') {
+                window.frameElement.__g2c_rpc(rpcData);
+                return;
+              }
+            }
+          } catch (e) {
+          }
+          // # targetId & sourceId@callId & packetNum & packetId & packetData
+          src = [relay, '#', targetId, '&', from, '@', callId,
+                 '&1&0&', encodeURIComponent(rpcData)].join('');
+        }
+        emitInvisibleIframe(src);
       }
     },
 
@@ -772,13 +372,15 @@
      */
     setAuthToken: function(targetId, token) {
       authToken[targetId] = token;
-      setupFrame(targetId);
     },
 
     /**
      * Gets the RPC relay mechanism.
-     * @return {String} RPC relay mechanism. See above for
-     *   a list of supported types.
+     * @return {String} RPC relay mechanism. Supported types:
+     *                  'wpm' - Use window.postMessage (defined by HTML5)
+     *                  'dpm' - Use document.postMessage (defined by an early
+     *                          draft of HTML5 and implemented by Opera)
+     *                  'ifpc' - Use invisible IFrames
      *
      * @member gadgets.rpc
      */