Author: johnh
Date: Tue May  3 02:41:03 2011
New Revision: 1098895

URL: http://svn.apache.org/viewvc?rev=1098895&view=rev
Log:
Ensure that Flash works appropriately in all timing scenarios. Add 
initialization handshake to ensure that messages sent can be received.



Modified:
    shindig/trunk/content/xpc.swf
    shindig/trunk/features/src/main/flex/Main.as
    shindig/trunk/features/src/main/javascript/features/rpc/flash.transport.js
    shindig/trunk/features/src/main/javascript/features/rpc/rpc.js

Modified: shindig/trunk/content/xpc.swf
URL: 
http://svn.apache.org/viewvc/shindig/trunk/content/xpc.swf?rev=1098895&r1=1098894&r2=1098895&view=diff
==============================================================================
Files shindig/trunk/content/xpc.swf (original) and 
shindig/trunk/content/xpc.swf Tue May  3 02:41:03 2011 differ

Modified: shindig/trunk/features/src/main/flex/Main.as
URL: 
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/flex/Main.as?rev=1098895&r1=1098894&r2=1098895&view=diff
==============================================================================
--- shindig/trunk/features/src/main/flex/Main.as (original)
+++ shindig/trunk/features/src/main/flex/Main.as Tue May  3 02:41:03 2011
@@ -24,8 +24,21 @@ import System.security;
  * Original design by [email protected] (Eduardo Vela)
  */
 class Main {
+  // Ensures that the callbacks installed by the SWF are installed only once 
per page context.
   private static var SINGLETON:Boolean = false;
 
+  // Constructor: unused in this case. 
+  public function Main() {
+  }
+
+  /**
+   * Simple helper function to replace instances of the given from_str in the 
provided
+   * first argument with the given to_str string.
+   * @param str {String} String whose contents to replace.
+   * @param from_str {String} Token to replace in str.
+   * @param to_str {String} String to put in place of from_str in str.
+   * @returns Modified string.
+   */
   public static function replace(str:String, from_str:String, 
to_str:String):String {
     var out_str:String = "";
     var search_ix:Number = 0;
@@ -46,6 +59,11 @@ class Main {
     return replace(str, "\\", "\\\\");
   }
 
+  /**
+   * Removes the port piece of an assumed well-structured provided host:port 
pair.
+   * @param {String} str String representing a URI authority.
+   * @returns The host-only (minus port) piece of the inbound authority String.
+   */
   public static function stripPortIfPresent(str:String):String {
     var col_ix:Number = str.indexOf(":");
     if (col_ix == -1) {
@@ -54,6 +72,32 @@ class Main {
     return str.substring(0, col_ix);
   }
 
+  /**
+   * Implementation of handlers facilitating cross-domain communication through
+   * Flash's ExternalInterface and LocalConnection facilities, offering sender
+   * domain verification.
+   *
+   * This method may only be run once such that it has any effect, a fact 
enforced
+   * by a static SINGLETON boolean. This prevents confusion if the SWF is 
accidentally
+   * loaded more than once in a given Window or page.
+   *
+   * The method whitelists HTTP-to-SWF access only for the domain provided in 
the
+   * inbound 'origin' argument. This argument in turn is passed along in each
+   * call made that passes along cross-domain messages on the caller's behalf, 
ensuring
+   * that domain verification works as intended.
+   *
+   * It installs a single 'setup' handler, callable from JS, which in turn 
sets up
+   * a sendMessage one-way communication method, while starting to listen to
+   * an equivalent channel set up by a party on the opposite side of an IFRAME
+   * boundary. A child context should pass in "INNER" for the role argument to 
setup;
+   * the parent passes "OUTER". rpc_key is a unique key provided on the IFRAME 
URL's
+   * hash disambiguating it from all other IFRAMEs on-page, window, or 
machine, while
+   * channel_id is the child's ID.
+   *
+   * This SWF is written specifically for the gadgets.rpc cross-domain 
communication
+   * library, but could be rather easily adapted - with passed-in callback 
method
+   * names, for instance - to use by other libraries as well.
+   */
   public static function main(swfRoot:MovieClip):Void {
     var escFn:Function = esc;
     var replaceFn:Function = replace;
@@ -64,20 +108,30 @@ class Main {
     var my_origin:String;
 
     if (_level0.origin == undefined){
+      // No origin: accept from all HTTP callers.
+      // Domain verification will not apply.
       my_origin = "http://*";;
     } else {
+      // Get origin from the query string.
       my_origin = _level0.origin;
     }
 
+    // Flash doesn't accept/honor ports, so we strip one if present
+    // for canonicalization.
     var domain:String = stripPortIfPresent(
         my_origin.substr(my_origin.indexOf("//") + 2, my_origin.length));
     
-    if (my_origin.substr(0,5)==="http:") {
+    // Whitelist access to this SWF for the sending HTTP domain.
+    // The my_origin field from which domain derives is passed along
+    // with each sent message. Together, these ensure domain verification
+    // of all sent messages is possible.
+    if (my_origin.substr(0,5) === "http:") {
       security.allowInsecureDomain(domain);
     } else {
       security.allowDomain(domain);
     }
 
+    // Install global communication channel setup method.
     ExternalInterface.addCallback("setup", { },
         function(rpc_key:String, channel_id:String, role:String) {
       var other_role:String;
@@ -91,11 +145,21 @@ class Main {
 
       var receiving_lc:LocalConnection = new LocalConnection();
       var sending_lc:LocalConnection = new LocalConnection();
+      var lastSendingDomain:String;
+      // Allow messages to be sent from any other SWF to this channel.
+      // Message verification itself is handled by both the fact that the
+      // channel ID contains the rpc_token as well as the passed message
+      // contents themselves, containing the token too. The SWF is
+      // largely a simple relay.
+      receiving_lc.allowDomain = function(sendingDomain:String) {
+        lastSendingDomain = sendingDomain;
+        return true;
+      };
       receiving_lc.receiveMessage =
           function(to_origin:String, from_origin:String, in_rpc_key:String, 
message:String) {
         if ((to_origin === "*" || to_origin === my_origin) && (in_rpc_key == 
rpc_key)) {
           ExternalInterface.call("gadgets.rpctx.flash._receiveMessage",
-              escFn(message), escFn(from_origin), escFn(to_origin));
+              escFn(message), escFn(from_origin), escFn(to_origin), 
escFn(lastSendingDomain));
         }
       };
 
@@ -103,16 +167,22 @@ class Main {
             { }, function(message:String, to_origin:String) {
         if (!to_origin) to_origin = "*";
         var sendId:String =
-            replaceFn("channel_" + channel_id + "_" + rpc_key + "_" + 
other_role, ":", "");
+            replaceFn("_channel_" + channel_id + "_" + rpc_key + "_" + 
other_role, ":", "");
         sending_lc.send(sendId,
             "receiveMessage", to_origin, my_origin, rpc_key, message);
       });
-      var recvId:String = replaceFn("channel_" + channel_id + "_" + rpc_key + 
"_" + role, ":", "");
+      var recvId:String = replaceFn("_channel_" + channel_id + "_" + rpc_key + 
"_" + role, ":", "");
       receiving_lc.connect(recvId);
+      if (role == "INNER") {
+        // In child context, trigger notice that the setup method is complete.
+        // This in turn initiates a child-to-parent polling procedure to 
complete a bidirectional
+        // communication handshake, since otherwise meaningful messages could 
be passed and dropped
+        // before the receiving end was ready.
+        ExternalInterface.call("gadgets.rpctx.flash._setupDone");
+      }
     });
+
+    // Signal completion of the setup callback to calling-context JS for 
proper ordering.
     ExternalInterface.call("gadgets.rpctx.flash._ready");
   }
-  
-  public function Main() {
-  }
 }

Modified: 
shindig/trunk/features/src/main/javascript/features/rpc/flash.transport.js
URL: 
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/rpc/flash.transport.js?rev=1098895&r1=1098894&r2=1098895&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/rpc/flash.transport.js 
(original)
+++ shindig/trunk/features/src/main/javascript/features/rpc/flash.transport.js 
Tue May  3 02:41:03 2011
@@ -41,11 +41,18 @@ if (!gadgets.rpctx.flash) {  // make lib
     var relayHandle = null;
 
     var LOADER_TIMEOUT_MS = 100;
-    var MAX_LOADER_RETRIES = 100;
+    var MAX_LOADER_RETRIES = 50;
     var pendingHandshakes = [];
     var setupHandle = null;
     var setupAttempts = 0;
 
+    var SWF_CHANNEL_READY = "_scr";
+    var READY_TIMEOUT_MS = 100;
+    var MAX_READY_RETRIES = 50;
+    var readyAttempts = 0;
+    var readyHandle = null;
+    var readyMsgs = {};
+
     var myLoc = window.location.protocol + "//" + window.location.host;
 
     function getChannelId(receiverId) {
@@ -65,7 +72,7 @@ if (!gadgets.rpctx.flash) {  // make lib
 
     function relayLoader() {
       if (relayHandle === null && document.body && swfUrl) {
-        var theSwf = swfUrl + '?origin=' + myLoc;
+        var theSwf = swfUrl + '?cb=' + Math.random() + '&origin=' + myLoc;
 
         var containerDiv = document.createElement('div');
         containerDiv.style.height = '1px';
@@ -87,6 +94,29 @@ if (!gadgets.rpctx.flash) {  // make lib
       if (setupHandle !== null &&
           (relayHandle !== null || setupAttempts >= MAX_LOADER_RETRIES)) {
         window.clearTimeout(setupHandle);
+      } else {
+        // Either document.body doesn't yet exist or config doesn't.
+        // In either case the relay handle isn't set up properly yet, and
+        // so should be retried.
+        setupHandle = window.setTimeout(relayLoader, LOADER_TIMEOUT_MS);
+      }
+    }
+
+    function childReadyPoller() {
+      // Attempt sending a message to parent indicating that child is ready
+      // to receive messages. This only occurs after the SWF indicates that
+      // its setup() method has been successfully called and completed, and
+      // only in child context.
+      if (readyMsgs[".."]) return;
+      sendChannelReady("..");
+      readyAttempts++;
+      if (readyAttempts >= MAX_READY_RETRIES && readyHandle !== null) {
+        window.clearTimeout(readyHandle);
+        readyHandle = null;
+      } else {
+        // Try again later. The handle will be cleared during receipt of
+        // the setup ACK.
+        readyHandle = window.setTimeout(childReadyPoller, READY_TIMEOUT_MS);
       }
     }
 
@@ -96,11 +126,26 @@ if (!gadgets.rpctx.flash) {  // make lib
           var shake = pendingHandshakes.shift();
           var targetId = shake.targetId;
           relayHandle['setup'](shake.token, getChannelId(targetId), 
getRoleId(targetId));
-          ready(shake.targetId, true);
         }
       }
     }
 
+    function call(targetId, from, rpc) {
+      var targetOrigin = gadgets.rpc.getTargetOrigin(targetId);
+      var rpcKey = gadgets.rpc.getAuthToken(targetId);
+      var handleKey = "sendMessage_" + getChannelId(targetId) + "_" + rpcKey + 
"_" + getRoleId(targetId);
+      var messageHandler = relayHandle[handleKey];
+      messageHandler.call(relayHandle, gadgets.json.stringify(rpc), 
targetOrigin);
+      return true;
+    }
+
+    function sendChannelReady(receiverId) {
+      var myId = gadgets.rpc.RPC_ID;
+      var readyAck = {};
+      readyAck[SWF_CHANNEL_READY] = myId;
+      call(receiverId, myId, readyAck);
+    }
+
     return {
       // "core" transport methods
       getCode: function() {
@@ -135,18 +180,28 @@ if (!gadgets.rpctx.flash) {  // make lib
         return true;
       },
 
-      call: function(targetId, from, rpc) {
-        var targetOrigin = gadgets.rpc.getTargetOrigin(targetId);
-        var rpcKey = gadgets.rpc.getAuthToken(targetId);
-        var handleKey = "sendMessage_" + getChannelId(targetId) + "_" + rpcKey 
+ "_" + getRoleId(targetId);
-        var messageHandler = relayHandle[handleKey];
-        messageHandler.call(relayHandle, gadgets.json.stringify(rpc), 
targetOrigin);
-        return true;
-      },
+      call: call,
 
       // Methods called by relay SWF. Should be considered private.
-      _receiveMessage: function(message, fromOrigin, toOrigin) {
-        window.setTimeout(function() { process(gadgets.json.parse(message), 
fromOrigin); }, 0);
+      _receiveMessage: function(message, fromOrigin, toOrigin, 
sendingSwfDomain) {
+        var jsonMsg = gadgets.json.parse(message);
+        var channelReady = jsonMsg[SWF_CHANNEL_READY];
+        if (channelReady) {
+          // Special message indicating that a ready message has been 
received, indicating
+          // the sender is now prepared to receive messages. This type of 
message is instigated
+          // by child context in polling fashion, and is responded-to by 
parent context(s).
+          // If readyHandle is non-null, then it should first be cleared.
+          // This method is OK to call twice, if it occurs in a race.
+          ready(channelReady, true);
+          readyMsgs[channelReady] = true;
+          if (channelReady !== "..") {
+            // Child-to-parent: immediately signal that parent is ready.
+            // Now that we know that child can receive messages, it's enough 
to send once.
+            sendChannelReady(channelReady);
+          }
+          return;
+        }
+        window.setTimeout(function() { process(jsonMsg, fromOrigin); }, 0);
       },
 
       _ready: function() {
@@ -155,6 +210,15 @@ if (!gadgets.rpctx.flash) {  // make lib
           window.clearTimeout(setupHandle);
         }
         setupHandle = null;
+      },
+
+      _setupDone: function() {
+        // Called by SWF only for role_id = "INNER" ie when initializing to 
parent.
+        // Instantiates a polling handshake mechanism which ensures that any 
enqueued
+        // messages remain so until each side is ready to send.
+        if (!readyMsgs[".."] && readyHandle === null) {
+          readyHandle = window.setTimeout(childReadyPoller, READY_TIMEOUT_MS);
+        }
       }
     };
   }();

Modified: shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
URL: 
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js?rev=1098895&r1=1098894&r2=1098895&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/rpc/rpc.js (original)
+++ shindig/trunk/features/src/main/javascript/features/rpc/rpc.js Tue May  3 
02:41:03 2011
@@ -185,7 +185,7 @@ if (!window['gadgets']['rpc']) { // make
       if (params['flash'] == "1") return gadgets.rpctx.flash;
       return typeof window.postMessage === 'function' ? gadgets.rpctx.wpm :
           typeof window.postMessage === 'object' ? gadgets.rpctx.wpm :
-          window.ActiveXObject ? gadgets.rpctx.nix :
+          window.ActiveXObject ? (gadgets.rpctx.flash ? gadgets.rpctx.flash : 
gadgets.rpctx.nix) :
           navigator.userAgent.indexOf('WebKit') > 0 ? gadgets.rpctx.rmr :
           navigator.product === 'Gecko' ? gadgets.rpctx.frameElement :
           gadgets.rpctx.ifpc;
@@ -196,6 +196,7 @@ if (!window['gadgets']['rpc']) { // make
      * send and receive messages.
      */
     function transportReady(receiverId, readySuccess) {
+      if (receiverTx[receiverId]) return;
       var tx = transport;
       if (!readySuccess) {
         tx = fallbackTransport;
@@ -892,7 +893,12 @@ if (!window['gadgets']['rpc']) { // make
         // target is misconfigured, it won't affect the others.
         // In the case of a sibling relay, channel is not found
         // in the receiverTx map but in the transport itself.
-        var channel = receiverTx[targetId] || transport;
+        var channel = receiverTx[targetId];
+        if (!channel && parseSiblingId(targetId) !== null) {
+          // Sibling-to-sibling communication; use default trasport
+          // (in practice, wpm) despite not being ready()-indicated.
+          channel = transport;
+        }
 
         if (!channel) {
           // Not set up yet. Enqueue the rpc for such time as it is.
@@ -1044,7 +1050,7 @@ if (!window['gadgets']['rpc']) { // make
 
       ACK: ACK,
 
-      RPC_ID: rpcId || "_top",
+      RPC_ID: rpcId || "..",
 
       SEC_ERROR_LOAD_TIMEOUT: LOAD_TIMEOUT,
       SEC_ERROR_FRAME_PHISH: FRAME_PHISH,


Reply via email to