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/10/21 23:08:13 UTC
svn commit: r706769 - in /incubator/shindig/trunk: features/rpc/rpc.js
javascript/container/rpctest_container.html
Author: johnh
Date: Tue Oct 21 14:08:13 2008
New Revision: 706769
URL: http://svn.apache.org/viewvc?rev=706769&view=rev
Log:
gadgets.rpc via NIX technique for huge speed-up and obviation of the relay-file requirement for IE6/7.
See extensive discussion of the technique and its security review at SHINDIG-416, which this commit closes.
Modified:
incubator/shindig/trunk/features/rpc/rpc.js
incubator/shindig/trunk/javascript/container/rpctest_container.html
Modified: incubator/shindig/trunk/features/rpc/rpc.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/rpc/rpc.js?rev=706769&r1=706768&r2=706769&view=diff
==============================================================================
--- incubator/shindig/trunk/features/rpc/rpc.js (original)
+++ incubator/shindig/trunk/features/rpc/rpc.js Tue Oct 21 14:08:13 2008
@@ -19,8 +19,6 @@
/**
* @fileoverview Remote procedure call library for gadget-to-container,
* container-to-gadget, and gadget-to-gadget (thru container) communication.
- *
- *
*/
var gadgets = gadgets || {};
@@ -38,6 +36,21 @@
// 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';
+
+ // 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 = [];
@@ -48,7 +61,6 @@
var callbacks = {};
var setup = {};
var sameDomain = {};
-
var params = {};
// Load the authentication token for speaking to the container
@@ -66,7 +78,7 @@
* + 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
@@ -76,7 +88,7 @@
* 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)
@@ -86,6 +98,49 @@
* dpm: postMessage on the document object.
* - Opera 8+
*
+ * + For Internet Explorer before version 8, the security model allows anyone
+ * parent to set the value of the "opener" property on another window,
+ * with only the receiving window able to read it.
+ * 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).
@@ -123,6 +178,7 @@
function getRelayChannel() {
return typeof window.postMessage === 'function' ? 'wpm' :
typeof document.postMessage === 'function' ? 'dpm' :
+ window.ActiveXObject ? 'nix' :
navigator.product === 'Gecko' ? 'fe' :
'ifpc';
}
@@ -141,6 +197,105 @@
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 =
+ // 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'
+
+ // Stores the auth token used to communicate with
+ // the gadget. The GetChannelCreator method returns
+ // an object that returns this auth token. Upon matching
+ // that with its own, the gadget uses the object
+ // to actually establish the communication channel.
+ + 'Private m_Auth\n'
+
+ // Method for internally setting the value
+ // of the m_Intended property.
+ + 'Public Sub SetIntendedName(name)\n '
+ + 'If isEmpty(m_Intended) Then\n'
+ + 'm_Intended = name\n'
+ + 'End If\n'
+ + 'End Sub\n'
+
+ // Method for internally setting the value of the m_Auth property.
+ + 'Public Sub SetAuth(auth)\n '
+ + 'If isEmpty(m_Auth) Then\n'
+ + 'm_Auth = auth\n'
+ + 'End If\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'
+
+ // Returns the auth token to the gadget, so it can
+ // confirm a match before initiating the connection
+ + 'Public Function GetAuthToken()\n '
+ + 'GetAuthToken = m_Auth\n'
+ + 'End Function\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'
+ + 'End Class\n'
+
+ // Function to get a reference to the wrapper.
+ + 'Function ' + NIX_GET_WRAPPER + '(name, auth)\n'
+ + 'Dim wrap\n'
+ + 'Set wrap = New ' + NIX_WRAPPER + '\n'
+ + 'wrap.SetIntendedName name\n'
+ + 'wrap.SetAuth auth\n'
+ + 'Set ' + NIX_GET_WRAPPER + ' = wrap\n'
+ + 'End Function';
+
+ try {
+ window.execScript(vbscript, 'vbscript');
+ } catch (e) {
+ // Fall through to IFPC.
+ relayChannel = 'ifpc';
+ }
+ }
+ }
}
// Pick the most efficient RPC relay mechanism
@@ -170,7 +325,7 @@
* RPC mechanism. Gadgets, in turn, will complete the setup
* of the channel once they send their first messages.
*/
- function setupFrame(frameId) {
+ function setupFrame(frameId, token) {
if (setup[frameId]) {
return;
}
@@ -187,6 +342,17 @@
}
}
+ if (relayChannel === 'nix') {
+ try {
+ var frame = document.getElementById(frameId);
+ var wrapper = window[NIX_GET_WRAPPER](frameId, token);
+ frame.contentWindow.opener = wrapper;
+ } catch (e) {
+ // Something went wrong. System will fallback to
+ // IFPC.
+ }
+ }
+
setup[frameId] = true;
}
@@ -268,6 +434,72 @@
/**
* 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} serviceName Name of the service to call.
+ * @param {String} from Module Id of the calling provider.
+ * @param {Object} rpcData The RPC data for this call.
+ */
+ function callNix(targetId, serviceName, 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.GetAuthToken) 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.
+ // This is just a sanity check, not a validity check.
+ if (!handler && window.opener && "GetAuthToken" in window.opener) {
+ handler = window.opener;
+
+ // Create the channel to the parent/container.
+ // First verify that it knows our auth token to ensure it's not
+ // an impostor.
+ if (handler.GetAuthToken() == authToken['..']) {
+ // Auth match - pass it back along with our wrapper to finish.
+ // own wrapper and our authentication token for co-verification.
+ var token = authToken['..'];
+ handler.CreateChannel(window[NIX_GET_WRAPPER]('..', token),
+ token);
+ // Set channel handler
+ nix_channels['..'] = handler;
+ window.opener = null;
+ }
+ }
+
+ // 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, serviceName, 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.
@@ -283,7 +515,7 @@
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') {
@@ -597,6 +829,10 @@
targetWin.postMessage(rpcData, relayUrl[targetId]);
break;
+ case 'nix': // use NIX.
+ callNix(targetId, serviceName, from, rpcData);
+ break;
+
case 'fe': // use FrameElement.
callFrameElement(targetId, serviceName, from, rpcData, rpc.a);
break;
@@ -642,7 +878,7 @@
*/
setAuthToken: function(targetId, token) {
authToken[targetId] = token;
- setupFrame(targetId);
+ setupFrame(targetId, token);
},
/**
Modified: incubator/shindig/trunk/javascript/container/rpctest_container.html
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/javascript/container/rpctest_container.html?rev=706769&r1=706768&r2=706769&view=diff
==============================================================================
--- incubator/shindig/trunk/javascript/container/rpctest_container.html (original)
+++ incubator/shindig/trunk/javascript/container/rpctest_container.html Tue Oct 21 14:08:13 2008
@@ -42,12 +42,14 @@
container.innerHTML = "<iframe id='gadget' name='gadget' height=300 width=300 src='" + gadgetUrl + "?parent=" + containerRelay + "#rpctoken=" + secret + "'></iframe>";
gadgets.rpc.setAuthToken('gadget', secret);
+ document.getElementById('relaymethod').innerHTML = gadgets.rpc.getRelayChannel();
+
initPerfTest();
};
</script>
</head>
<body onload="initTest();">
- <div>gadgets.rpc Performance: Container Page</div><hr/>
+ <div>gadgets.rpc Performance: Container Page (method: <span id="relaymethod"></span>)</div><hr/>
<div>Test<br/>
<ul>
<li>Number of messages to send: