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

svn commit: r627954 - in /incubator/shindig/trunk: features/rpc/rpc.js javascript/container/rpc_relay.html

Author: zhen
Date: Thu Feb 14 21:11:00 2008
New Revision: 627954

URL: http://svn.apache.org/viewvc?rev=627954&view=rev
Log:
Implemented gadgets.rpc using three cross-domain RPC relay mechanisms: window.postMessage, document.postMessage, and an improved version of IFrame-based IFPC technique.

Tested in FireFox 2, FireFox 3 beta 3, Safari 3, Opera 9, IE 6, and IE 7.

Container samples and gadget features using cross-domain calls will be updated to leverage gadgets.rpc later.


Added:
    incubator/shindig/trunk/javascript/container/rpc_relay.html
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=627954&r1=627953&r2=627954&view=diff
==============================================================================
--- incubator/shindig/trunk/features/rpc/rpc.js (original)
+++ incubator/shindig/trunk/features/rpc/rpc.js Thu Feb 14 21:11:00 2008
@@ -29,6 +29,95 @@
  * @name gadgets.rpc
  */
 gadgets.rpc = function() {
+  var services_ = {};
+  var iframePool_ = [];
+  var relayUrl_ = {};
+  var callId_ = 0;
+  var callbacks_ = {};
+
+  // 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') {
+    document.addEventListener('message', function(packet) {
+      // TODO validate packet.domain for security reasons
+      process(gadgets.json.parse(packet.data));
+    }, false);
+  }
+
+  // Parent relay URL retrieval
+  var args = gadgets.util.getUrlParameters();
+  relayUrl_['..'] = args.rpc_relay || args.parent;
+
+  // Default RPC handler
+  services_[''] = function() {
+    throw new Error('Unknown RPC service: ' + this.s);
+  };
+
+  // Special RPC handler for callbacks
+  services_['__cb'] = function(callbackId, result) {
+    var callback = callbacks_[callbackId];
+    if (callback) {
+      delete callbacks_[callbackId];
+      callback(result);
+    }
+  };
+
+  /**
+   * Helper function to process an RPC request
+   * @param {Object} rpc RPC request object
+   * @private
+   */
+  function process(rpc) {
+    if (rpc && typeof rpc.s === 'string' && typeof rpc.f === 'string' &&
+        rpc.a instanceof Array) {
+      var result = (services_[rpc.s] || services_['']).apply(rpc, rpc.a);
+      if (rpc.c) {
+        gadgets.rpc.call(rpc.f, '__cb', null, rpc.c, result);
+      }
+    }
+  }
+
+  /**
+   * Helper function to emit an invisible IFrame.
+   * @param {String} src SRC attribute of the IFrame to emit.
+   * @private
+   */
+  function emitInvisibleIframe(src) {
+    var iframe;
+    // Recycle IFrames
+    for (var i = iframePool_.length - 1; i >=0; --i) {
+      var ifr = iframePool_[i];
+      if (ifr && (ifr.recyclable || ifr.readyState === 'complete')) {
+        ifr.parentNode.removeChild(ifr);
+        if (window.ActiveXObject) {
+          // For MSIE, delete any iframes that are no longer being used. MSIE
+          // cannot reuse the IFRAME because a navigational click sound will
+          // be triggered when we set the SRC attribute.
+          // Other browsers scan the pool for a free iframe to reuse.
+          iframePool_[i] = ifr = null;
+          iframePool_.splice(i, 1);
+        } else {
+          ifr.recyclable = false;
+          iframe = ifr;
+          break;
+        }
+      }
+    }
+    // Create IFrame if necessary
+    if (!iframe) {
+      iframe = document.createElement('iframe');
+      iframe.style.border = iframe.style.width = iframe.style.height = '0px';
+      iframe.style.visibility = 'hidden';
+      iframe.style.position = 'absolute';
+      iframe.onload = function() { this.recyclable = true; };
+      iframePool_.push(iframe);
+    }
+    iframe.src = src;
+    setTimeout(function() { document.body.appendChild(iframe); }, 0);
+  }
+
   return /** @scope gadgets.rpc */ {
     /**
      * Registers an RPC service.
@@ -38,7 +127,7 @@
      * @member gadgets.rpc
      */
     register: function(serviceName, handler) {
-      // TODO
+      services_[serviceName] = handler;
     },
 
     /**
@@ -48,18 +137,18 @@
      * @member gadgets.rpc
      */
     unregister: function(serviceName) {
-      // TODO
+      delete services_[serviceName];
     },
 
     /**
      * Registers a default service handler to processes all unknown
-     * RPC calls which fail silently by default.
+     * RPC calls which raise an exception by default.
      * @param {Function} handler Service handler.
      *
      * @member gadgets.rpc
      */
     registerDefault: function(handler) {
-      // TODO
+      services_[''] = handler;
     },
 
     /**
@@ -69,7 +158,7 @@
      * @member gadgets.rpc
      */
     unregisterDefault: function() {
-      // TODO
+      delete services_[''];
     },
 
     /**
@@ -84,7 +173,93 @@
      * @member gadgets.rpc
      */
     call: function(targetId, serviceName, callback, var_args) {
-      // TODO
+      ++callId_;
+      targetId = targetId || '..';
+      if (callback) {
+        callbacks_[callId_] = callback;
+      }
+      var from = targetId === '..' ? window.name : '..';
+      var rpcData = gadgets.json.stringify({
+        s: serviceName,
+        f: from,
+        c: callback ? callId_ : 0,
+        a: Array.prototype.slice.call(arguments, 3)
+      });
+
+      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 relayUrl = gadgets.rpc.getRelayUrl(targetId);
+        if (/^http[s]?:\/\//.test(relayUrl)) {
+          // IFrame packet format:
+          // # targetId & sourceId@callId & packetNum & packetId & packetData
+          // TODO split message if too long
+          var src = [relayUrl, '#', targetId, '&', from, '@', callId_,
+                     '&1&0&', encodeURIComponent(rpcData)].join('');
+          emitInvisibleIframe(src);
+        }
+      }
+    },
+
+    /**
+     * Gets the relay URL of a target frame.
+     * @param {String} targetId Name of the target frame.
+     * @return {String|undefined} Relay URL of the target frame.
+     *
+     * @member gadgets.rpc
+     */
+    getRelayUrl: function(targetId) {
+      return relayUrl_[targetId];
+    },
+
+    /**
+     * Sets the relay URL of a target frame.
+     * @param {String} targetId Name of the target frame.
+     * @param {String} relayUrl Full relay URL of the target frame.
+     *
+     * @member gadgets.rpc
+     */
+    setRelayUrl: function(targetId, relayUrl) {
+      relayUrl_[targetId] = relayUrl;
+    },
+
+    /**
+     * Gets the RPC relay mechanism.
+     * @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
+     */
+    getRelayChannel: function() {
+      return relayChannel_;
+    },
+
+    /**
+     * Receives and processes an RPC request. (Not to be used directly.)
+     * @param {Array.<String>} fragment An RPC request fragment encoded as
+     *        an array. The first 4 elements are target id, source id & call id,
+     *        total packet number, packet id. The last element stores the actual
+     *        JSON-encoded and URI escaped packet data.
+     *
+     * @member gadgets.rpc
+     */
+    receive: function(fragment) {
+      if (fragment.length > 4) {
+        // TODO parse fragment[1..3] to merge multi-fragment messages
+        process(gadgets.json.parse(
+                decodeURIComponent(fragment[fragment.length - 1])));
+      }
     }
   };
 }();

Added: incubator/shindig/trunk/javascript/container/rpc_relay.html
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/rpc_relay.html?rev=627954&view=auto
==============================================================================
--- incubator/shindig/trunk/javascript/container/rpc_relay.html (added)
+++ incubator/shindig/trunk/javascript/container/rpc_relay.html Thu Feb 14 21:11:00 2008
@@ -0,0 +1,9 @@
+<script>
+var u = location.href, h = u.substr(u.indexOf('#') + 1).split('&'), t, r;
+try {
+  t = h[0] === '..' ? parent.parent : parent.frames[h[0]];
+  r = t.gadgets.rpc.receive;
+} catch (e) {
+}
+r && r(h);
+</script>