Author: johnh
Date: Tue Jun 9 00:54:35 2009
New Revision: 782853
URL: http://svn.apache.org/viewvc?rev=782853&view=rev
Log:
Rolling back rpc.js to r764984.
In the past week and a half, we have had significant pain integrating the
updated (RMR, early-message queueing, refactored) rpc.js into our existing
deployments. The process has been extremely illuminating, highlighting the need
for greatly-improved end-to-end testing of every conceivable configuration and
use of this library.
I am temporarily rolling back the change to take an opportunity to review the
improved version in its entirety and message some of the integration challenges
we've faced - and that others may as well (the one that was ultimately most
difficult for us: some containers chose to cache a many-versions-old rpc.js
that, for subtle reasons, proved incompatible w/ the latest).
Removed:
incubator/shindig/trunk/features/src/main/javascript/features/rpc/dpm.transport.js
incubator/shindig/trunk/features/src/main/javascript/features/rpc/fe.transport.js
incubator/shindig/trunk/features/src/main/javascript/features/rpc/ifpc.transport.js
incubator/shindig/trunk/features/src/main/javascript/features/rpc/nix.transport.js
incubator/shindig/trunk/features/src/main/javascript/features/rpc/rmr.transport.js
incubator/shindig/trunk/features/src/main/javascript/features/rpc/wpm.transport.js
Modified:
incubator/shindig/trunk/features/src/main/javascript/features/rpc/feature.xml
incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
Modified:
incubator/shindig/trunk/features/src/main/javascript/features/rpc/feature.xml
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/rpc/feature.xml?rev=782853&r1=782852&r2=782853&view=diff
==============================================================================
---
incubator/shindig/trunk/features/src/main/javascript/features/rpc/feature.xml
(original)
+++
incubator/shindig/trunk/features/src/main/javascript/features/rpc/feature.xml
Tue Jun 9 00:54:35 2009
@@ -29,21 +29,9 @@
-->
<name>rpc</name>
<gadget>
- <script src="wpm.transport.js"/>
- <script src="dpm.transport.js"/>
- <script src="fe.transport.js"/>
- <script src="nix.transport.js"/>
- <script src="rmr.transport.js"/>
- <script src="ifpc.transport.js"/>
<script src="rpc.js"/>
</gadget>
<container>
- <script src="wpm.transport.js"/>
- <script src="dpm.transport.js"/>
- <script src="fe.transport.js"/>
- <script src="nix.transport.js"/>
- <script src="rmr.transport.js"/>
- <script src="ifpc.transport.js"/>
<script src="rpc.js"/>
</container>
</feature>
Modified:
incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js?rev=782853&r1=782852&r2=782853&view=diff
==============================================================================
--- incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
(original)
+++ incubator/shindig/trunk/features/src/main/javascript/features/rpc/rpc.js
Tue Jun 9 00:54:35 2009
@@ -16,44 +16,13 @@
* specific language governing permissions and limitations under the License.
*/
+/*global gadgets, console */
/**
* @fileoverview Remote procedure call library for gadget-to-container,
* container-to-gadget, and gadget-to-gadget (thru container) communication.
*/
-var gadgets = gadgets || {};
-/**
- * gadgets.rpc Transports
- *
- * All transports are stored in object gadgets.rpctx, and are provided
- * to the core gadgets.rpc library by various build rules.
- *
- * Transports used by core gadgets.rpc code to actually pass messages.
- * each transport implements the same "interface" exposing hooks that
- * the core library calls at strategic points to set up and use
- * the transport. This isn't really an interface, of course, since
- * the code is JS and each transport is a singleton, but it's treated
- * as such by core code.
- *
- * The methods each transport must implement are:
- * + getCode(): returns a string identifying the transport. For debugging.
- * + isParentVerifiable(): indicates (via boolean) whether the method
- * has the property that its relay URL verifies for certain the
- * receiver's protocol://host:port.
- * + init(processFn, readyFn): Performs any global initialization needed.
Called
- * before any other gadgets.rpc methods are invoked. processFn is
- * the function in gadgets.rpc used to process an rpc packet. readyFn is
- * a function that must be called when the transport is ready to send
- * and receive messages bidirectionally. Returns
- * true if successful, false otherwise.
- * + setup(receiverId, token): Performs per-receiver initialization, if any.
- * receiverId will be '..' for gadget-to-container. Returns true if
- * successful, false otherwise.
- * + call(targetId, from, rpc): Invoked to send an actual
- * message to the given targetId, with the given serviceName, from
- * the sender identified by 'from'. Payload is an rpc packet. Returns
- * true if successful, false otherwise.
- */
+var gadgets = gadgets || {};
/**
* @static
@@ -65,15 +34,27 @@
var CALLBACK_NAME = '__cb';
var DEFAULT_NAME = '';
- // Special service name for acknowledgements.
- var ACK = '__ack';
-
- // Timeout and number of setup attempts between each
- // for when setAuthToken is called before the target it specifies exists.
- var SETUP_FRAME_TIMEOUT = 500;
- var SETUP_FRAME_MAX_TRIES = 10;
+ // 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 = [];
var relayUrl = {};
var useLegacyProtocol = {};
var authToken = {};
@@ -82,12 +63,7 @@
var setup = {};
var sameDomain = {};
var params = {};
- var receiverTx = {};
- var earlyRpcQueue = {};
-
- // isGadget =~ isChild for the purposes of rpc (used only in setup).
- var isGadget = (window.top !== window.self);
- var fallbackTransport = gadgets.rpctx.Ifpc;
+ var relayChannel;
// Load the authentication token for speaking to the container
// from the gadget's parameters, or default to '0' if not found.
@@ -97,62 +73,120 @@
authToken['..'] = params.rpctoken || params.ifpctok || 0;
- // Indicates whether to support early-message queueing, which is designed
- // to ensure that all messages sent by gadgets.rpc.call, irrespective
- // when they were made (before/after setAuthToken, before/after transport
- // setup complete), are sent. Hiding behind a query param to allow opt-in
- // for a time while this technique is proven.
- var useEarlyQueueing = (params['rpc_earlyq'] === "1");
-
/*
- * Return a transport representing the best available cross-domain
- * message-passing mechanism available to the browser.
+ * Return a short code representing the best available cross-domain
+ * message transport available to the browser.
+ *
+ * + 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 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).
+ *
+ * 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.
*
- * Transports are selected on a cascading basis determined by browser
- * capability and other checks. The order of preference is:
- * 1. wpm: Uses window.postMessage standard.
- * 2. dpm: Uses document.postMessage, similar to wpm but pre-standard.
- * 3. nix: Uses IE-specific browser hacks.
- * 4. rmr: Signals message passing using relay file's onresize handler.
- * 5. fe: Uses FF2-specific window.frameElement hack.
- * 6. ifpc: Sends messages via active load of a relay file.
+ * fe: Gecko-specific frameElement trick.
+ * - Firefox 1+
*
- * See each transport's commentary/documentation for details.
+ * + 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.
*/
- function getTransport() {
- return typeof window.postMessage === 'function' ? gadgets.rpctx.Wpm :
- typeof document.postMessage === 'function' ? gadgets.rpctx.Dpm :
- window.ActiveXObject ? gadgets.rpctx.Nix :
- navigator.userAgent.indexOf('WebKit') > 0 ? gadgets.rpctx.Rmr :
- navigator.product === 'Gecko' ? gadgets.rpctx.FrameElement :
- gadgets.rpctx.Ifpc;
+ function getRelayChannel() {
+ return typeof window.postMessage === 'function' ? 'wpm' :
+ typeof document.postMessage === 'function' ? 'dpm' :
+ window.ActiveXObject ? 'nix' :
+ navigator.product === 'Gecko' ? 'fe' :
+ 'ifpc';
}
- /**
- * Function passed to, and called by, a transport indicating it's ready to
- * send and receive messages.
- */
- function transportReady(receiverId, readySuccess) {
- var tx = transport;
- if (!readySuccess) {
- tx = fallbackTransport;
- }
- receiverTx[receiverId] = tx;
-
- // If there are any early-queued messages, send them now directly through
- // the needed transport. This queue will only have contents if
- // useEarlyQueueing === true (see call method).
- var earlyQueue = earlyRpcQueue[receiverId] || [];
- for (var i = 0; i < earlyQueue.length; ++i) {
- var rpc = earlyQueue[i];
- // There was no auth/rpc token set before, so set it now.
- rpc.t = gadgets.rpc.getAuthToken(receiverId);
- tx.call(receiverId, rpc.f, rpc);
- }
-
- // Clear the queue so it won't be sent again.
- earlyRpcQueue[receiverId] = [];
- }
+ // Pick the most efficient RPC relay mechanism
+ relayChannel = getRelayChannel();
/**
* Helper function to process an RPC request
@@ -181,12 +215,6 @@
}
}
- if (rpc.s === ACK) {
- // Acknowledgement API, used to indicate a receiver is ready.
- window.setTimeout(function() { transportReady(rpc.f, true); }, 0);
- return;
- }
-
// If there is a callback for this service, attach a callback function
// to the rpc context object for asynchronous rpc services.
//
@@ -224,61 +252,130 @@
}
/**
- * Helper method returning a canonicalized schema://host[:port] for
- * a given input URL, provided as a string. Used to compute convenient
- * relay URLs and to determine whether a call is coming from the same
- * domain as its receiver (bypassing the try/catch capability detection
- * flow, thereby obviating Firebug and other tools reporting an exception).
- *
- * @param {string} url Base URL to canonicalize.
+ * Conducts any initial global work necessary to setup the
+ * channel type chosen.
*/
- function getDomainRoot(url) {
- if (!url) {
- return "";
- }
- url = url.toLowerCase();
- if (url.indexOf("//") == 0) {
- url = window.location.protocol + ":" + url;
- }
- if (url.indexOf("http://") != 0 &&
- url.indexOf("https://") != 0) {
- // Assumed to be schemaless. Default to current protocol.
- url = window.location.protocol + "://" + url;
- }
- // At this point we guarantee that "://" is in the URL and defines
- // current protocol. Skip past this to search for host:port.
- var host = url.substring(url.indexOf("://") + 3);
-
- // Find the first slash char, delimiting the host:port.
- var slashPos = host.indexOf("/");
- if (slashPos != -1) {
- host = host.substring(0, slashPos);
- }
+ 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) {
+ window.setTimeout(
+ function() { process(gadgets.json.parse(data)) }, 0);
+ };
- var protocol = url.substring(0, url.indexOf("://"));
+ 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;
+ }
+ };
- // Use port only if it's not default for the protocol.
- var portStr = "";
- var portPos = host.indexOf(":");
- if (portPos != -1) {
- var port = host.substring(portPos + 1);
- host = host.substring(0, portPos);
- if ((protocol === "http" && port !== "80") ||
- (protocol === "https" && port !== "443")) {
- portStr = ":" + port;
+ // 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';
+ }
}
}
-
- // Return <protocol>://<host>[<port>]
- return protocol + "://" + host + portStr;
}
- // Pick the most efficient RPC relay mechanism.
- var transport = getTransport();
+
+ // Conduct any setup necessary for the chosen channel.
+ setupChannel();
// Create the Default RPC handler.
services[DEFAULT_NAME] = function() {
- gadgets.warn('Unknown RPC service: ' + this.s);
+ if (window['console'] && window['console']['log']) {
+ window['console']['log']('Unknown RPC service: ' + this.s);
+ }
};
// Create a Special RPC handler for callbacks.
@@ -298,33 +395,257 @@
* of the channel once they send their first messages.
*/
function setupFrame(frameId, token) {
- if (setup[frameId] === true) {
+ var frame; // used below
+
+ if (setup[frameId]) {
return;
}
- if (typeof setup[frameId] === 'undefined') {
- setup[frameId] = 0;
+ if (relayChannel === 'fe') {
+ try {
+ frame = document.getElementById(frameId);
+ frame[FE_G2C_CHANNEL] = function(args) {
+ process(gadgets.json.parse(args));
+ };
+ } catch (e1) {
+ // Something went wrong. System will fallback to
+ // IFPC.
+ }
}
- var tgtFrame = document.getElementById(frameId);
- if (frameId === '..' || tgtFrame != null) {
- if (transport.setup(frameId, token) === true) {
- setup[frameId] = true;
- return;
+ if (relayChannel === 'nix') {
+ try {
+ frame = document.getElementById(frameId);
+ var wrapper = window[NIX_GET_WRAPPER](frameId, token);
+ frame.contentWindow.opener = wrapper;
+ } catch (e2) {
+ // Something went wrong. System will fallback to
+ // IFPC.
+ }
+ }
+
+ setup[frameId] = true;
+ }
+
+ /**
+ * Encodes arguments for the legacy IFPC wire format.
+ *
+ * @param {Object} args
+ * @return {String} the encoded args
+ */
+ function encodeLegacyData(args) {
+ var stringify = gadgets.json.stringify;
+ var argsEscaped = [];
+ for(var i = 0, j = args.length; i < j; ++i) {
+ argsEscaped.push(encodeURIComponent(stringify(args[i])));
+ }
+ return argsEscaped.join('&');
+ }
+
+ /**
+ * 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];
+ try {
+ 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;
+ }
+ }
+ } catch (e) {
+ // Ignore; IE7 throws an exception when trying to read readyState and
+ // readyState isn't set.
+ }
+ }
+ // 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);
+ }
+
+ /**
+ * 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} serviceName Service name to call.
+ * @param {String} from Module Id of the calling provider.
+ * @param {Object} rpcData The RPC data for this call.
+ * @param {Array.<Object>} callArgs Original arguments to call()
+ */
+ function callIfpc(targetId, serviceName, from, rpcData, callArgs) {
+ // 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) {
+ if (window['console'] && window['console']['log']) {
+ window['console']['log']('No relay file assigned for IFPC');
}
}
- if (setup[frameId] !== true && setup[frameId]++ < SETUP_FRAME_MAX_TRIES) {
- // Try again in a bit, assuming that frame will soon exist.
- window.setTimeout(function() { setupFrame(frameId, token) },
- SETUP_FRAME_TIMEOUT);
+ // 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(
+ callArgs))])].join('');
} else {
- // Fail: fall back.
- transport = fallbackTransport;
- setup[frameId] = true;
+ // Format: #targetId & sourc...@callid & packetNum & packetId &
packetData
+ src = [relay, '#', targetId, '&', from, '@', callId,
+ '&1&0&', encodeURIComponent(rpcData)].join('');
+ }
+
+ // Conduct the IFPC call by creating the Iframe with
+ // the relay URL and appended message.
+ emitInvisibleIframe(src);
+ }
+
+ /**
+ * 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.
+ *
+ * @param {String} targetId Module Id of the RPC service provider.
+ * @param {String} serviceName Service name to call.
+ * @param {String} from Module Id of the calling provider.
+ * @param {Object} rpcData The RPC data for this call.
+ * @param {Array.<Object>} callArgs Original arguments to call()
+ */
+ function callFrameElement(targetId, serviceName, from, rpcData, callArgs) {
+ 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) {
+ 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, serviceName, from, rpcData, callArgs);
}
+
+
/**
* Attempts to make an rpc by calling the target's receive method directly.
* This works when gadgets are rendered on the same domain as their
container,
@@ -339,14 +660,8 @@
function callSameDomain(target, rpc) {
if (typeof sameDomain[target] === 'undefined') {
// Seed with a negative, typed value to avoid
- // hitting this code path repeatedly.
+ // hitting this code path repeatedly
sameDomain[target] = false;
- var targetRelay = gadgets.rpc.getRelayUrl(target);
- if (getDomainRoot(targetRelay) !== getDomainRoot(window.location.href)) {
- // Not worth trying -- avoid the error and just return.
- return false;
- }
-
var targetEl = null;
if (target === '..') {
targetEl = parent;
@@ -357,8 +672,7 @@
// If this succeeds, then same-domain policy applied
sameDomain[target] = targetEl.gadgets.rpc.receiveSameDomain;
} catch (e) {
- // Shouldn't happen due to domain root check. Caught just in case.
- gadgets.warn("Unexpected: same domain call failed.");
+ // Usual case: different domains
}
}
@@ -372,44 +686,37 @@
}
// gadgets.config might not be available, such as when serving container js.
- if (isGadget && gadgets.config) {
+ if (gadgets.config) {
/**
- * Initializes gadget to container RPC params from the provided
configuration.
+ * Initializes RPC from the provided configuration.
*/
function init(config) {
- var configRpc = config ? config.rpc : {};
- var parentRelayUrl = configRpc.parentRelayUrl;
-
// Allow for wild card parent relay files as long as it's from a
// white listed domain. This is enforced by the rendering servlet.
- if (parentRelayUrl.substring(0, 7) !== 'http://' &&
- parentRelayUrl.substring(0, 8) !== 'https://' &&
- parentRelayUrl.substring(0, 2) !== '//') {
- // Relative path: we append to the parent.
+ if (config.rpc.parentRelayUrl.substring(0, 7) === 'http://') {
+ relayUrl['..'] = config.rpc.parentRelayUrl;
+ } else {
+ // It's a relative path, and we must append to the parent.
// We're relying on the server validating the parent parameter in this
- // case. Because of this, parent may only be passed in the query, not
fragment.
- if (params.parent !== "") {
+ // case. Because of this, parent may only be passed in the query, not
+ // the fragment.
+ var params = document.location.search.substring(0).split("&");
+ var parentParam = "";
+ for (var i = 0, param; (param = params[i]); ++i) {
+ // Only the first parent can be validated.
+ if (param.indexOf("parent=") === 0) {
+ parentParam = decodeURIComponent(param.substring(7));
+ break;
+ }
+ }
+ if (parentParam !== "") {
// Otherwise, relayUrl['..'] will be null, signaling transport
// code to ignore rpc calls since they cannot work without a
// relay URL with host qualification.
- parentRelayUrl = getDomainRoot(params.parent) + parentRelayUrl;
+ relayUrl['..'] = parentParam + config.rpc.parentRelayUrl;
}
}
- relayUrl['..'] = parentRelayUrl;
-
- var useLegacy = !!configRpc.useLegacyProtocol;
- useLegacyProtocol['..'] = useLegacy;
- if (useLegacy) {
- transport = gadgets.rpctx.Ifpc;
- transport.init(process, transportReady);
- }
-
- // Here, we add a hook for the transport to actively set up
- // gadget -> container communication. Running here ensures
- // that relayUri info will be available.
- if (transport.setup('..') === false) {
- transport = fallbackTransport;
- }
+ useLegacyProtocol['..'] = !!config.rpc.useLegacyProtocol;
}
var requiredConfig = {
@@ -427,13 +734,12 @@
* @member gadgets.rpc
*/
register: function(serviceName, handler) {
- if (serviceName === CALLBACK_NAME || serviceName === ACK) {
- throw new Error("Cannot overwrite callback/ack service");
+ if (serviceName === CALLBACK_NAME) {
+ throw new Error("Cannot overwrite callback service");
}
if (serviceName === DEFAULT_NAME) {
- throw new Error("Cannot overwrite default service:"
- + " use registerDefault");
+ throw new Error("Cannot overwrite default service: use
registerDefault");
}
services[serviceName] = handler;
@@ -446,13 +752,12 @@
* @member gadgets.rpc
*/
unregister: function(serviceName) {
- if (serviceName === CALLBACK_NAME || serviceName === ACK) {
- throw new Error("Cannot delete callback/ack service");
+ if (serviceName === CALLBACK_NAME) {
+ throw new Error("Cannot delete callback service");
}
if (serviceName === DEFAULT_NAME) {
- throw new Error("Cannot delete default service:"
- + " use unregisterDefault");
+ throw new Error("Cannot delete default service: use
unregisterDefault");
}
delete services[serviceName];
@@ -466,7 +771,7 @@
* @member gadgets.rpc
*/
registerDefault: function(handler) {
- services[DEFAULT_NAME] = handler;
+ services[''] = handler;
},
/**
@@ -476,7 +781,7 @@
* @member gadgets.rpc
*/
unregisterDefault: function() {
- delete services[DEFAULT_NAME];
+ delete services[''];
},
/**
@@ -486,8 +791,8 @@
* At present this means IFPC or WPM.
*/
forceParentVerifiable: function() {
- if (!transport.isParentVerifiable()) {
- transport = gadgets.rpctx.Ifpc;
+ if (relayChannel !== 'wpm') {
+ relayChannel = 'ifpc';
}
},
@@ -503,7 +808,12 @@
* @member gadgets.rpc
*/
call: function(targetId, serviceName, callback, var_args) {
+ ++callId;
targetId = targetId || '..';
+ if (callback) {
+ callbacks[callId] = callback;
+ }
+
// Default to the container calling.
var from = '..';
@@ -511,18 +821,13 @@
from = window.name;
}
- ++callId;
- if (callback) {
- callbacks[callId] = callback;
- }
-
+ // Not used by legacy, create it anyway...
var rpc = {
s: serviceName,
f: from,
c: callback ? callId : 0,
a: Array.prototype.slice.call(arguments, 3),
- t: authToken[targetId],
- l: useLegacyProtocol[targetId]
+ t: authToken[targetId]
};
// If target is on the same domain, call method directly
@@ -530,29 +835,42 @@
return;
}
- // Attempt to make call via a cross-domain transport.
- var channel = useEarlyQueueing ? receiverTx[targetId] : transport;
+ var rpcData = gadgets.json.stringify(rpc);
- if (!channel) {
- // Not set up yet. Enqueue the rpc for such time as it is.
- if (!earlyRpcQueue[targetId]) {
- earlyRpcQueue[targetId] = [ rpc ];
- } else {
- earlyRpcQueue[targetId].push(rpc);
- }
- return;
- }
+ var channelType = relayChannel;
// If we are told to use the legacy format, then we must
// default to IFPC.
if (useLegacyProtocol[targetId]) {
- channel = gadgets.rpctx.Ifpc;
+ channelType = 'ifpc';
}
- if (channel.call(targetId, from, rpc) === false) {
- // Fall back to IFPC. This behavior may be removed as IFPC is as well.
- transport = fallbackTransport;
- transport.call(targetId, from, rpc);
+ 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];
+ var relay = gadgets.rpc.getRelayUrl(targetId);
+ if (relay) {
+ targetWin.postMessage(rpcData, relay);
+ }
+ break;
+
+ case 'nix': // use NIX.
+ callNix(targetId, serviceName, from, rpcData);
+ break;
+
+ case 'fe': // use FrameElement.
+ callFrameElement(targetId, serviceName, from, rpcData, rpc.a);
+ break;
+
+ default: // use 'ifpc' as a fallback mechanism.
+ callIfpc(targetId, serviceName, from, rpcData, rpc.a);
+ break;
}
},
@@ -566,7 +884,7 @@
getRelayUrl: function(targetId) {
var url = relayUrl[targetId];
// Some RPC methods (wpm, for one) are unhappy with schemeless URLs.
- if (url && url.indexOf('//') == 0) {
+ if (url.indexOf('//') == 0) {
url = document.location.protocol + url;
}
@@ -607,13 +925,6 @@
},
/**
- * Helper method to retrieve the authToken for a given gadget.
- */
- getAuthToken: function(targetId) {
- return authToken[targetId];
- },
-
- /**
* Gets the RPC relay mechanism.
* @return {String} RPC relay mechanism. See above for
* a list of supported types.
@@ -621,12 +932,11 @@
* @member gadgets.rpc
*/
getRelayChannel: function() {
- return transport.getCode();
+ return relayChannel;
},
/**
* Receives and processes an RPC request. (Not to be used directly.)
- * Only used by IFPC.
* @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
@@ -652,29 +962,7 @@
// Pass through to local process method but converting to a local Array
rpc.a = Array.prototype.slice.call(rpc.a);
window.setTimeout(function() { process(rpc); }, 0);
- },
-
- /**
- * Helper method to get the protocol://host:port of an input URL.
- */
- getDomainRoot: getDomainRoot,
-
- /**
- * Internal-only method used to initialize gadgets.rpc.
- */
- init: function() {
- // Conduct any global setup necessary for the chosen transport.
- // Do so after gadgets.rpc definition to allow transport to access
- // gadgets.rpc methods.
- if (transport.init(process, transportReady) === false) {
- transport = fallbackTransport;
- }
- },
-
- /** Exported constant, for use by transports only. */
- ACK: ACK
+ }
};
}();
-// Initialize library/transport.
-gadgets.rpc.init();