
var gadgets = gadgets || {};
gadgets.util = function() {
 function parseUrlParams() {
 // Get settings from url, 'hash' takes precedence over 'search' component
 // don't use document.location.hash due to browser differences.
 var query;
 var l = document.location.href;
 var queryIdx = l.indexOf("?");
 var hashIdx = l.indexOf("#");
 if (hashIdx === -1) {
 query = l.substr(queryIdx + 1);
 } else {
 // essentially replaces "#" with "&"
 query = [l.substr(queryIdx + 1, hashIdx - queryIdx - 1), "&",
 l.substr(hashIdx + 1)].join("");
 }
 return query.split("&");
 }
 var parameters = null;
 var features = {};
 var onLoadHandlers = [];
 // Maps code points to the value to replace them with.
 // If the value is "false", the character is removed entirely, otherwise
 // it will be replaced with an html entity.
 var escapeCodePoints = {
 // nul; most browsers truncate because they use c strings under the covers.
 0 : false,
 // new line
 10 : true,
 // carriage return
 13 : true,
 // double quote
 34 : true,
 // single quote
 39 : true,
 // less than
 60 : true,
 // greater than
 62 : true,
 // Backslash
 92 : true,
 // line separator
 8232 : true,
 // paragraph separator
 8233 : true
 };
 function unescapeEntity(match, value) {
 return String.fromCharCode(value);
 }
 function init(config) {
 features = config["core.util"] || {};
 }
 if (gadgets.config) {
 gadgets.config.register("core.util", null, init);
 }
 return {
 getUrlParameters : function () {
 if (parameters !== null) {
 return parameters;
 }
 parameters = {};
 var pairs = parseUrlParams();
 var unesc = window.decodeURIComponent ? decodeURIComponent : unescape;
 for (var i = 0, j = pairs.length; i < j; ++i) {
 var pos = pairs[i].indexOf('=');
 if (pos === -1) {
 continue;
 }
 var argName = pairs[i].substring(0, pos);
 var value = pairs[i].substring(pos + 1);
 // difference to IG_Prefs, is that args doesn't replace spaces in
 // argname. Unclear on if it should do:
 // argname = argname.replace(/\+/g, " ");
 value = value.replace(/\+/g, " ");
 parameters[argName] = unesc(value);
 }
 return parameters;
 },
 makeClosure : function (scope, callback, var_args) {
 // arguments isn't a real array, so we copy it into one.
 var baseArgs = [];
 for (var i = 2, j = arguments.length; i < j; ++i) {
 baseArgs.push(arguments[i]);
 }
 return function() {
 // append new arguments.
 var tmpArgs = baseArgs.slice();
 for (var i = 0, j = arguments.length; i < j; ++i) {
 tmpArgs.push(arguments[i]);
 }
 return callback.apply(scope, tmpArgs);
 };
 },
 makeEnum : function (values) {
 var obj = {};
 for (var i = 0, v; (v = values[i]); ++i) {
 obj[v] = v;
 }
 return obj;
 },
 getFeatureParameters : function (feature) {
 return typeof features[feature] === "undefined" ? null : features[feature];
 },
 hasFeature : function (feature) {
 return typeof features[feature] !== "undefined";
 },
 registerOnLoadHandler : function (callback) {
 onLoadHandlers.push(callback);
 },
 runOnLoadHandlers : function () {
 for (var i = 0, j = onLoadHandlers.length; i < j; ++i) {
 onLoadHandlers[i]();
 }
 },
 escape : function(input, opt_escapeObjects) {
 if (!input) {
 return input;
 } else if (typeof input === "string") {
 return gadgets.util.escapeString(input);
 } else if (typeof input === "array") {
 for (var i = 0, j = input.length; i < j; ++i) {
 input[i] = gadgets.util.escape(input[i]);
 }
 } else if (typeof input === "object" && opt_escapeObjects) {
 var newObject = {};
 for (var field in input) {
 if (input.hasOwnProperty(field)) {
 newObject[gadgets.util.escapeString(field)] = gadgets.util.escape(input[field], true);
 }
 }
 return newObject;
 }
 return input;
 },
 escapeString : function(str) {
 var out = [], ch, shouldEscape;
 for (var i = 0, j = str.length; i < j; ++i) {
 ch = str.charCodeAt(i);
 shouldEscape = escapeCodePoints[ch];
 if (shouldEscape === true) {
 out.push("&#", ch, ";");
 } else if (shouldEscape !== false) {
 // undefined or null are OK.
 out.push(str.charAt(i));
 }
 }
 return out.join("");
 },
 unescapeString : function(str) {
 return str.replace(/&#([0-9]+);/g, unescapeEntity);
 }
 };
}();
// Initialize url parameters so that hash data is pulled in before it can be
// altered by a click.
gadgets.util.getUrlParameters();
var gadgets = gadgets || {};
gadgets.json = function () {
 function f(n) {
 return n < 10 ? '0' + n : n;
 }
 Date.prototype.toJSON = function () {
 return [this.getUTCFullYear(), '-',
 f(this.getUTCMonth() + 1), '-',
 f(this.getUTCDate()), 'T',
 f(this.getUTCHours()), ':',
 f(this.getUTCMinutes()), ':',
 f(this.getUTCSeconds()), 'Z'].join("");
 };
 // table of character substitutions
 var m = {
 '\b': '\\b',
 '\t': '\\t',
 '\n': '\\n',
 '\f': '\\f',
 '\r': '\\r',
 '"' : '\\"',
 '\\': '\\\\'
 };
 function stringify(value) {
 var a, // The array holding the partial texts.
 i, // The loop counter.
 k, // The member key.
 l, // Length.
 r = /["\\\x00-\x1f\x7f-\x9f]/g,
 v; // The member value.
 switch (typeof value) {
 case 'string':
 // If the string contains no control characters, no quote characters, and no
 // backslash characters, then we can safely slap some quotes around it.
 // Otherwise we must also replace the offending characters with safe ones.
 return r.test(value) ?
 '"' + value.replace(r, function (a) {
 var c = m[a];
 if (c) {
 return c;
 }
 c = a.charCodeAt();
 return '\\u00' + Math.floor(c / 16).toString(16) +
 (c % 16).toString(16);
 }) + '"' : '"' + value + '"';
 case 'number':
 // JSON numbers must be finite. Encode non-finite numbers as null.
 return isFinite(value) ? String(value) : 'null';
 case 'boolean':
 case 'null':
 return String(value);
 case 'object':
 // Due to a specification blunder in ECMAScript,
 // typeof null is 'object', so watch out for that case.
 if (!value) {
 return 'null';
 }
 // toJSON check removed; re-implement when it doesn't break other libs.
 a = [];
 if (typeof value.length === 'number' &&
 !value.propertyIsEnumerable('length')) {
 // The object is an array. Stringify every element. Use null as a
 // placeholder for non-JSON values.
 l = value.length;
 for (i = 0; i < l; i += 1) {
 a.push(stringify(value[i]) || 'null');
 }
 // Join all of the elements together and wrap them in brackets.
 return '[' + a.join(',') + ']';
 }
 // Otherwise, iterate through all of the keys in the object.
 for (k in value) {
 if (value.hasOwnProperty(k)) {
 if (typeof k === 'string') {
 v = stringify(value[k]);
 if (v) {
 a.push(stringify(k) + ':' + v);
 }
 }
 }
 }
 // Join all of the member texts together and wrap them in braces.
 return '{' + a.join(',') + '}';
 }
 }
 return {
 stringify: stringify,
 parse: function (text) {
// Parsing happens in three stages. In the first stage, we run the text against
// regular expressions that look for non-JSON patterns. We are especially
// concerned with '()' and 'new' because they can cause invocation, and '='
// because it can cause mutation. But just to be safe, we want to reject all
// unexpected forms.
// We split the first stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace all backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
 if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/b-u]/g, '@').
 replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
 replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
 return eval('(' + text + ')');
 }
 // If the text is not JSON parseable, then return false.
 return false;
 }
 };
}();

var gadgets = gadgets || {};
gadgets.rpctx = gadgets.rpctx || {};
gadgets.rpctx.wpm = function() {
 var ready;
 return {
 getCode: function() {
 return 'wpm';
 },
 isParentVerifiable: function() {
 return true;
 },
 init: function(processFn, readyFn) {
 ready = readyFn;
 var onmessage = function(packet) {
 // TODO validate packet.domain for security reasons
 processFn(gadgets.json.parse(packet.data));
 };
 // Set up native postMessage handler.
 if (typeof window.addEventListener != 'undefined') {
 window.addEventListener('message', onmessage, false);
 } else if (typeof window.attachEvent != 'undefined') {
 window.attachEvent('onmessage', onmessage);
 }
 ready('..', true); // Immediately ready to send to parent.
 return true;
 },
 setup: function(receiverId, token) {
 // If we're a gadget, send an ACK message to indicate to container
 // that we're ready to receive messages.
 if (receiverId === '..') {
 gadgets.rpc.call(receiverId, gadgets.rpc.ACK);
 }
 return true;
 },
 call: function(targetId, from, rpc) {
 var targetWin = targetId === '..' ? window.parent : window.frames[targetId];
 var relay = gadgets.rpc.getRelayUrl(targetId);
 if (relay) {
 targetWin.postMessage(gadgets.json.stringify(rpc), relay);
 } else {
 gadgets.error("No relay set, cannot send cross-domain message");
 }
 return true;
 }
 };
}();
var gadgets = gadgets || {};
gadgets.rpctx = gadgets.rpctx || {};
gadgets.rpctx.frameElement = function() {
 // Consts for FrameElement.
 var FE_G2C_CHANNEL = '__g2c_rpc';
 var FE_C2G_CHANNEL = '__c2g_rpc';
 var process;
 var ready;
 function callFrameElement(targetId, from, rpc) {
 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](gadgets.json.stringify(rpc));
 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](gadgets.json.stringify(rpc));
 return;
 }
 }
 } catch (e) {
 }
 return true;
 }
 return {
 getCode: function() {
 return 'fe';
 },
 isParentVerifiable: function() {
 return false;
 },
 init: function(processFn, readyFn) {
 // No global setup.
 process = processFn;
 ready = readyFn;
 return true;
 },
 setup: function(receiverId, token) {
 // Indicate OK to call to container. This will be true
 // by the end of this method.
 if (receiverId !== '..') {
 try {
 var frame = document.getElementById(receiverId);
 frame[FE_G2C_CHANNEL] = function(args) {
 process(gadgets.json.parse(args));
 };
 } catch (e) {
 return false;
 }
 }
 if (receiverId === '..') {
 ready('..', true);
 var ackFn = function() {
 window.setTimeout(function() {
 gadgets.rpc.call(receiverId, gadgets.rpc.ACK)
 }, 500);
 }
 // Setup to container always happens before onload.
 // If it didn't, the correct fix would be in gadgets.util.
 gadgets.util.registerOnLoadHandler(ackFn);
 }
 return true;
 },
 call: function(targetId, from, rpc) {
 callFrameElement(targetId, from, rpc);
 }
 };
}();
var gadgets = gadgets || {};
gadgets.rpctx = gadgets.rpctx || {};
gadgets.rpctx.nix = function() {
 // 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 MAX_NIX_SEARCHES = 10;
 var NIX_SEARCH_PERIOD = 500;
 // 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 = {};
 // Store the ready signal method for use on handshake complete.
 var ready;
 var numHandlerSearches = 0;
 // Search for NIX handler to parent. Tries MAX_NIX_SEARCHES times every
 // NIX_SEARCH_PERIOD milliseconds.
 function conductHandlerSearch() {
 // Call from gadget to the container.
 var handler = nix_channels['..'];
 if (handler) {
 return;
 }
 if (++numHandlerSearches > MAX_NIX_SEARCHES) {
 // Handshake failed. Will fall back.
 gadgets.warn('Nix transport setup failed, falling back...');
 ready('..', false);
 return;
 }
 // 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() == gadgets.rpc.getAuthToken('..')) {
 // Auth match - pass it back along with our wrapper to finish.
 // own wrapper and our authentication token for co-verification.
 var token = gadgets.rpc.getAuthToken('..');
 handler.CreateChannel(window[NIX_GET_WRAPPER]('..', token),
 token);
 // Set channel handler
 nix_channels['..'] = handler;
 window.opener = null;
 // Signal success and readiness to send to parent.
 // Container-to-gadget bit flipped in CreateChannel.
 ready('..', true);
 return;
 }
 }
 // Try again.
 window.setTimeout(function() { conductHandlerSearch(); },
 NIX_SEARCH_PERIOD);
 }
 return {
 getCode: function() {
 return 'nix';
 },
 isParentVerifiable: function() {
 return false;
 },
 init: function(processFn, readyFn) {
 ready = readyFn;
 // Ensure VBScript wrapper code is in the page and that the
 // global Javascript handlers have been set.
 // 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() { processFn(gadgets.json.parse(data)) }, 0);
 };
 window[NIX_CREATE_CHANNEL] = function(name, channel, token) {
 // Verify the authentication token of the gadget trying
 // to create a channel for us.
 if (gadgets.rpc.getAuthToken(name) === token) {
 nix_channels[name] = channel;
 ready(name, true);
 }
 };
 // 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) {
 return false;
 }
 }
 return true;
 },
 setup: function(receiverId, token) {
 if (receiverId === '..') {
 conductHandlerSearch();
 return true;
 }
 try {
 var frame = document.getElementById(receiverId);
 var wrapper = window[NIX_GET_WRAPPER](receiverId, token);
 frame.contentWindow.opener = wrapper;
 } catch (e) {
 return false;
 }
 return true;
 },
 call: function(targetId, from, rpc) {
 try {
 // If we have a handler, call it.
 if (nix_channels[targetId]) {
 nix_channels[targetId].SendMessage(gadgets.json.stringify(rpc));
 }
 } catch (e) {
 return false;
 }
 return true;
 }
 };
}();
var gadgets = gadgets || {};
gadgets.rpctx = gadgets.rpctx || {};
gadgets.rpctx.rmr = function() {
 // Consts for RMR, including time in ms RMR uses to poll for
 // its relay frame to be created, and the max # of polls it does.
 var RMR_SEARCH_TIMEOUT = 500;
 var RMR_MAX_POLLS = 10;
 // JavaScript references to the channel objects used by RMR.
 // Gadgets will have but a single channel under
 // rmr_channels['..'] while containers will have a channel
 // per gadget stored under the gadget's ID.
 var rmr_channels = {};
 var process;
 var ready;
 function appendRmrFrame(channelFrame, relayUri, data) {
 var appendFn = function() {
 // Append the iframe.
 document.body.appendChild(channelFrame);
 // Set the src of the iframe to 'about:blank' first and then set it
 // to the relay URI. This prevents the iframe from maintaining a src
 // to the 'old' relay URI if the page is returned to from another.
 // In other words, this fixes the bfcache issue that causes the iframe's
 // src property to not be updated despite us assigning it a new value here.
 channelFrame.src = 'about:blank';
 channelFrame.src = relayUri + '#' + data;
 }
 if (document.body) {
 appendFn();
 } else {
 // Common gadget case: attaching header during in-gadget handshake,
 // when we may still be in script in head. Attach onload.
 gadgets.util.registerOnLoadHandler(function() { appendFn(); });
 }
 }
 function setupRmr(frameId) {
 if (typeof rmr_channels[frameId] === "object") {
 // Sanity check. Already done.
 return;
 }
 var channelFrame = document.createElement('iframe');
 var frameStyle = channelFrame.style;
 frameStyle.position = 'absolute';
 frameStyle.top = '0px';
 frameStyle.border = '0';
 frameStyle.opacity = '0';
 // The width here is important as RMR
 // makes use of the resize handler for the frame.
 // Do not modify unless you test thoroughly!
 frameStyle.width = '10px'
 frameStyle.height = '1px';
 channelFrame.id = 'rmrtransport-' + frameId;
 channelFrame.name = channelFrame.id;
 // Determine the relay uri by taking the existing one,
 // removing the path and appending robots.txt. It is
 // not important if robots.txt actually exists, since RMR
 // browsers treat 404s as legitimate for the purposes of
 // this communication.
 var relayUri =
 gadgets.rpc.getOrigin(gadgets.rpc.getRelayUrl(frameId)) + '/robots.txt';
 rmr_channels[frameId] = {
 frame: channelFrame,
 receiveWindow: null,
 relayUri: relayUri,
 searchCounter : 0,
 width: 10,
 // Waiting means "waiting for acknowledgement to be received."
 // Acknowledgement always comes as a special ACK
 // message having been received. This message is received
 // during handshake in different ways by the container and
 // gadget, and by normal RMR message passing once the handshake
 // is complete.
 waiting: true,
 queue: [],
 // Number of non-ACK messages that have been sent to the recipient
 // and have been acknowledged.
 sendId: 0,
 // Number of messages received and processed from the sender.
 // This is the number that accompanies every ACK to tell the
 // sender to clear its queue.
 recvId: 0
 };
 if (frameId !== '..') {
 // Container always appends a relay to the gadget, before
 // the gadget appends its own relay back to container. The
 // gadget, in the meantime, refuses to attach the container
 // relay until it finds this one. Thus, the container knows
 // for certain that gadget to container communication is set
 // up by the time it finds its own relay. In addition to
 // establishing a reliable handshake protocol, this also
 // makes it possible for the gadget to send an initial batch
 // of messages to the container ASAP.
 appendRmrFrame(channelFrame, relayUri, getRmrData(frameId));
 }
 // Start searching for our own frame on the other page.
 conductRmrSearch(frameId);
 }
 function conductRmrSearch(frameId) {
 var channelWindow = null;
 // Increment the search counter.
 rmr_channels[frameId].searchCounter++;
 try {
 if (frameId === '..') {
 // We are a gadget.
 channelWindow = window.parent.frames['rmrtransport-' + window.name];
 } else {
 // We are a container.
 channelWindow = window.frames[frameId].frames['rmrtransport-..'];
 }
 } catch (e) {
 // Just in case; may happen when relay is set to about:blank or unset.
 // Catching exceptions here ensures that the timeout to continue the
 // search below continues to work.
 }
 var status = false;
 if (channelWindow) {
 // We have a valid reference to "our" RMR transport frame.
 // Register the proper event handlers.
 status = registerRmrChannel(frameId, channelWindow);
 }
 if (!status) {
 // Not found yet. Continue searching, but only if the counter
 // has not reached the threshold.
 if (rmr_channels[frameId].searchCounter > RMR_MAX_POLLS) {
 // If we reach this point, then RMR has failed and we
 // fall back to IFPC.
 return;
 }
 window.setTimeout(function() {
 conductRmrSearch(frameId);
 }, RMR_SEARCH_TIMEOUT);
 }
 }
 function callRmr(targetId, serviceName, from, rpc) {
 var handler = null;
 if (from !== '..') {
 // Call from gadget to the container.
 handler = rmr_channels['..'];
 } else {
 // Call from container to the gadget.
 handler = rmr_channels[targetId];
 }
 if (handler) {
 // Queue the current message if not ACK.
 // ACK is always sent through getRmrData(...).
 if (serviceName !== gadgets.rpc.ACK) {
 handler.queue.push(rpc);
 }
 if (handler.waiting ||
 (handler.queue.length === 0 &&
 !(serviceName === gadgets.rpc.ACK && rpc && rpc.ackAlone === true))) {
 // If we are awaiting a response from any previously-sent messages,
 // or if we don't have anything new to send, just return.
 // Note that we don't short-return if we're ACKing just-received
 // messages.
 return true;
 }
 if (handler.queue.length > 0) {
 handler.waiting = true;
 }
 var url = handler.relayUri + "#" + getRmrData(targetId);
 try {
 // Update the URL with the message.
 handler.frame.contentWindow.location = url;
 // Resize the frame.
 var newWidth = handler.width == 10 ? 20 : 10;
 handler.frame.style.width = newWidth + 'px';
 handler.width = newWidth;
 // Done!
 } catch (e) {
 // Something about location-setting or resizing failed.
 // This should never happen, but if it does, fall back to
 // the default transport.
 return false;
 }
 }
 return true;
 }
 function getRmrData(toFrameId) {
 var channel = rmr_channels[toFrameId];
 var rmrData = {id: channel.sendId};
 if (channel) {
 rmrData.d = Array.prototype.slice.call(channel.queue, 0);
 rmrData.d.push({s:gadgets.rpc.ACK, id:channel.recvId});
 }
 return gadgets.json.stringify(rmrData);
 }
 function processRmrData(fromFrameId) {
 var channel = rmr_channels[fromFrameId];
 var data = channel.receiveWindow.location.hash.substring(1);
 // Decode the RPC object array.
 var rpcObj = gadgets.json.parse(decodeURIComponent(data)) || {};
 var rpcArray = rpcObj.d || [];
 var nonAckReceived = false;
 var noLongerWaiting = false;
 var numBypassed = 0;
 var numToBypass = (channel.recvId - rpcObj.id);
 for (var i = 0; i < rpcArray.length; ++i) {
 var rpc = rpcArray[i];
 // If we receive an ACK message, then mark the current
 // handler as no longer waiting and send out the next
 // queued message.
 if (rpc.s === gadgets.rpc.ACK) {
 // ACK received - whether this came from a handshake or
 // an active call, in either case it indicates readiness to
 // send messages to the from frame.
 ready(fromFrameId, true);
 if (channel.waiting) {
 noLongerWaiting = true;
 }
 channel.waiting = false;
 var newlyAcked = Math.max(0, rpc.id - channel.sendId);
 channel.queue.splice(0, newlyAcked);
 channel.sendId = Math.max(channel.sendId, rpc.id || 0);
 continue;
 }
 // If we get here, we've received > 0 non-ACK messages to
 // process. Indicate this bit for later.
 nonAckReceived = true;
 // Bypass any messages already received.
 if (++numBypassed <= numToBypass) {
 continue;
 }
 ++channel.recvId;
 process(rpc); // actually dispatch the message
 }
 // Send an ACK indicating that we got/processed the message(s).
 // Do so if we've received a message to process or if we were waiting
 // before but a received ACK has cleared our waiting bit, and we have
 // more messages to send. Performing this operation causes additional
 // messages to be sent.
 if (nonAckReceived ||
 (noLongerWaiting && channel.queue.length > 0)) {
 var from = (fromFrameId === '..') ? window.name : '..';
 callRmr(fromFrameId, gadgets.rpc.ACK, from, {ackAlone: nonAckReceived});
 }
 }
 function registerRmrChannel(frameId, channelWindow) {
 var channel = rmr_channels[frameId];
 // Verify that the channel is ready for receiving.
 try {
 var canAccess = false;
 // Check to see if the document is in the window. For Chrome, this
 // will return 'false' if the channelWindow is inaccessible by this
 // piece of JavaScript code, meaning that the URL of the channelWindow's
 // parent iframe has not yet changed from 'about:blank'. We do this
 // check this way because any true *access* on the channelWindow object
 // will raise a security exception, which, despite the try-catch, still
 // gets reported to the debugger (it does not break execution, the try
 // handles that problem, but it is still reported, which is bad form).
 // This check always succeeds in Safari 3.1 regardless of the state of
 // the window.
 canAccess = 'document' in channelWindow;
 if (!canAccess) {
 return false;
 }
 // Check to see if the document is an object. For Safari 3.1, this will
 // return undefined if the page is still inaccessible. Unfortunately, this
 // *will* raise a security issue in the debugger.
 // TODO Find a way around this problem.
 canAccess = typeof channelWindow['document'] == 'object';
 if (!canAccess) {
 return false;
 }
 // Once we get here, we know we can access the document (and anything else)
 // on the window object. Therefore, we check to see if the location is
 // still about:blank (this takes care of the Safari 3.2 case).
 var loc = channelWindow.location.href;
 // Check if this is about:blank for Safari.
 if (loc === 'about:blank') {
 return false;
 }
 } catch (ex) {
 // For some reason, the iframe still points to about:blank. We try
 // again in a bit.
 return false;
 }
 // Save a reference to the receive window.
 channel.receiveWindow = channelWindow;
 // Register the onresize handler.
 function onresize() {
 processRmrData(frameId);
 };
 if (typeof channelWindow.attachEvent === "undefined") {
 channelWindow.onresize = onresize;
 } else {
 channelWindow.attachEvent("onresize", onresize);
 }
 if (frameId === '..') {
 // Gadget to container. Signal to the container that the gadget
 // is ready to receive messages by attaching the g -> c relay.
 // As a nice optimization, pass along any gadget to container
 // queued messages that have backed up since then. ACK is enqueued in
 // getRmrData to ensure that the container's waiting flag is set to false
 // (this happens in the below code run on the container side).
 appendRmrFrame(channel.frame, channel.relayUri, getRmrData(frameId));
 }
 // Attempt to process the messages that the other party may have set in
 // the resize frame's hash. For gadget to container, this is the gadget
 // finding the container-set IFRAME, which has an ACK attached to it.
 // For gadget to container, the gadget may have enqueued messages already,
 // and sent them in the above code. This too will have an ACK message.
 // The net effect is that both sides of the connection will have their
 // "waiting" bit set to false, so they're ready to send messages.
 processRmrData(frameId);
 return true;
 }
 return {
 getCode: function() {
 return 'rmr';
 },
 isParentVerifiable: function() {
 return true;
 },
 init: function(processFn, readyFn) {
 // No global setup.
 process = processFn;
 ready = readyFn;
 return true;
 },
 setup: function(receiverId, token) {
 try {
 setupRmr(receiverId);
 } catch (e) {
 gadgets.warn('Caught exception setting up RMR: ' + e);
 return false;
 }
 return true;
 },
 call: function(targetId, from, rpc) {
 return callRmr(targetId, rpc.s, from, rpc);
 }
 };
}();
var gadgets = gadgets || {};
gadgets.rpctx = gadgets.rpctx || {};
gadgets.rpctx.ifpc = function() {
 var iframePool = [];
 var callId = 0;
 var ready;
 function encodeLegacyData(args) {
 var argsEscaped = [];
 for(var i = 0, j = args.length; i < j; ++i) {
 argsEscaped.push(encodeURIComponent(gadgets.json.stringify(args[i])));
 }
 return argsEscaped.join('&');
 }
 function emitInvisiblePostIframe(src, params, destination)
 {
 var target = 'target_' + (destination.replace('..', 'main'));
 var form = document.getElementById('rpcPostForm_' + target);
 if (!form)
 {
 // create the form
 var form = document.createElement('form');
 form.setAttribute('method', 'post');
 form.setAttribute('id', 'rpcPostForm_' + target);
 form.setAttribute('target', target);
 form.setAttribute('action', src);
 document.body.appendChild(form);
 }
 var inputField = document.getElementById('rpcPostParams_' + target);
 if (!inputField)
 {
 inputField = document.createElement('input');
 inputField.setAttribute('id', 'rpcPostParams_' + target);
 form.appendChild(inputField);
 }
 inputField.setAttribute('name', 'params');
 inputField.value = params;
 var inputDestination = document.getElementById('rpcPostDestination_' + target);
 if (!inputDestination)
 {
 inputDestination = document.createElement('input');
 inputDestination.setAttribute('id', 'rpcPostDestination_' + target);
 form.appendChild(inputDestination);
 }
 inputDestination.setAttribute('name', 'destination');
 inputDestination.value = destination;
 var iframe = document.getElementById('rpcPostFrame_' + target);
 if (!iframe)
 {
 var div = document.createElement('div');
 document.body.appendChild(div);
 div.innerHTML = '<iframe src="' + src + '" name="' + target + '" id="rpcPostFrame_' + target + '"></iframe>';
 }
 // post the form
 form.submit();
 }
 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;
 window.setTimeout(function() { document.body.appendChild(iframe); }, 0);
 }
 return {
 getCode: function() {
 return 'ifpc';
 },
 isParentVerifiable: function() {
 return true;
 },
 init: function(processFn, readyFn) {
 // No global setup.
 ready = readyFn;
 ready('..', true); // Ready immediately.
 return true;
 },
 setup: function(receiverId, token) {
 // Indicate readiness to send to receiver.
 ready(receiverId, true);
 return true;
 },
 call: function(targetId, from, rpc) {
 // 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);
 ++callId;
 if (!relay) {
 gadgets.warn('No relay file assigned for IFPC');
 return;
 }
 // The RPC mechanism supports two formats for IFPC (legacy and current).
 var src = null;
 if (rpc.l) {
 // Use legacy protocol.
 // Format: #iframe_id&callId&num_packets&packet_num&block_of_data
 var callArgs = rpc.a;
 src = [relay, '#', encodeLegacyData([from, callId, 1, 0,
 encodeLegacyData([from, rpc.s, '', '', from].concat(
 callArgs))])].join('');
 } else {
 // Format: #targetId & sourceId@callId & packetNum & packetId & packetData
 src = [relay, '#', targetId, '&', from, '@', callId,
 '&1&0&', encodeURIComponent(gadgets.json.stringify(rpc))].join('');
 }
 if (src.length > 500)
 {
 var src = relay.replace('opensocial/gadgets/files/container/rpc_relay.html', 'rpc_post');
 var params = gadgets.json.stringify(rpc);
 emitInvisiblePostIframe(src, params, targetId);
 }
 else
 {
 emitInvisibleIframe(src);
 }
 // Conduct the IFPC call by creating the Iframe with
 // the relay URL and appended message.
 return true;
 }
 };
}();
var gadgets = gadgets || {};
gadgets.rpc = function() {
 // General constants.
 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;
 var services = {};
 var relayUrl = {};
 var useLegacyProtocol = {};
 var authToken = {};
 var callId = 0;
 var callbacks = {};
 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);
 // Fallback transport is simply a dummy impl that emits no errors
 // and logs info on calls it receives, to avoid undesired side-effects
 // from falling back to IFPC or some other transport.
 var fallbackTransport = (function() {
 function logFn(name) {
 return function() {
 gadgets.info("gadgets.rpc." + name + "(" +
 gadgets.json.stringify(arguments) +
 "): call ignored. [caller: " + document.location +
 ", isGadget: " + isGadget + "]");
 }
 }
 return {
 getCode: function() {
 return "noop";
 },
 isParentVerifiable: function() {
 return true; // Not really, but prevents transport assignment to IFPC.
 },
 init: logFn("init"),
 setup: logFn("setup"),
 call: logFn("call")
 }
 })();
 // 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();
 }
 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");
 function getTransport() {
 var result = typeof window.postMessage === 'function' ? gadgets.rpctx.wpm :
 typeof window.postMessage === 'object' ? gadgets.rpctx.wpm :
 window.ActiveXObject ? gadgets.rpctx.nix :
 navigator.userAgent.indexOf('WebKit') > 0 ? gadgets.rpctx.rmr :
 navigator.product === 'Gecko' ? gadgets.rpctx.frameElement :
 gadgets.rpctx.ifpc;
 if (navigator.appName == 'Microsoft Internet Explorer')
 {
 result = gadgets.rpctx.ifpc;
 }
 return result;
 }
 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] = [];
 }
 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 don't do type coercion here because all entries in the authToken
 // object are strings, as are all url params. See setAuthToken(...).
 // if (authToken[rpc.f] !== rpc.t) {
 // throw new Error("Invalid auth token. " +
 // authToken[rpc.f] + " vs " + rpc.t);
 // }
 }
 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.
 //
 // Synchronous rpc request handlers should simply ignore it and return a
 // value as usual.
 // Asynchronous rpc request handlers, on the other hand, should pass its
 // result to this callback function and not return a value on exit.
 //
 // For example, the following rpc handler passes the first parameter back
 // to its rpc client with a one-second delay.
 //
 // function asyncRpcHandler(param) {
 // var me = this;
 // setTimeout(function() {
 // me.callback(param);
 // }, 1000);
 // }
 if (rpc.c) {
 rpc.callback = function(result) {
 gadgets.rpc.call(rpc.f, CALLBACK_NAME, null, rpc.c, result);
 };
 }
 // Call the requested RPC service.
 var result = (services[rpc.s] ||
 services[DEFAULT_NAME]).apply(rpc, rpc.a);
 // If the rpc request handler returns a value, immediately pass it back
 // to the callback. Otherwise, do nothing, assuming that the rpc handler
 // will make an asynchronous call later.
 if (rpc.c && typeof result !== 'undefined') {
 gadgets.rpc.call(rpc.f, CALLBACK_NAME, null, rpc.c, result);
 }
 }
 }
 function getOrigin(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);
 }
 var protocol = url.substring(0, url.indexOf("://"));
 // 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;
 }
 }
 // Return <protocol>://<host>[<port>]
 return protocol + "://" + host + portStr;
 }
 // Pick the most efficient RPC relay mechanism.
 var transport = getTransport();
 // Create the Default RPC handler.
 services[DEFAULT_NAME] = function() {
 gadgets.warn('Unknown RPC service: ' + this.s);
 };
 // Create a Special RPC handler for callbacks.
 services[CALLBACK_NAME] = function(callbackId, result) {
 var callback = callbacks[callbackId];
 if (callback) {
 delete callbacks[callbackId];
 callback(result);
 }
 };
 function setupFrame(frameId, token) {
 if (setup[frameId] === true) {
 return;
 }
 if (typeof setup[frameId] === 'undefined') {
 setup[frameId] = 0;
 }
 var tgtFrame = document.getElementById(frameId);
 if (frameId === '..' || tgtFrame != null) {
 if (transport.setup(frameId, token) === true) {
 setup[frameId] = true;
 return;
 }
 }
 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);
 } else {
 // Fail: fall back.
 transport = fallbackTransport;
 setup[frameId] = true;
 }
 }
 function callSameDomain(target, rpc) {
 if (typeof sameDomain[target] === 'undefined') {
 // Seed with a negative, typed value to avoid
 // hitting this code path repeatedly.
 sameDomain[target] = false;
 var targetRelay = gadgets.rpc.getRelayUrl(target);
 if (getOrigin(targetRelay) !== getOrigin(window.location.href)) {
 // Not worth trying -- avoid the error and just return.
 return false;
 }
 var targetEl = null;
 if (target === '..') {
 targetEl = window.parent;
 } else {
 targetEl = window.frames[target];
 }
 try {
 // If this succeeds, then same-domain policy applied
 sameDomain[target] = targetEl.gadgets.rpc.receiveSameDomain;
 } catch (e) {
 // Shouldn't happen due to origin check. Caught to emit
 // more meaningful error to the caller.
 gadgets.error("Same domain call failed: parent= incorrectly set.");
 }
 }
 if (typeof sameDomain[target] === 'function') {
 // Call target's receive method
 sameDomain[target](rpc);
 return true;
 }
 return false;
 }
 // gadgets.config might not be available, such as when serving container js.
 if (isGadget && gadgets.config) {
 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.
 // 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 (typeof params.parent === "string" && params.parent !== "") {
 // Otherwise, relayUrl['..'] will be null, signaling transport
 // code to ignore rpc calls since they cannot work without a
 // relay URL with host qualification.
 if (parentRelayUrl.substring(0, 1) !== '/') {
 // Path-relative. Trust that parent is passed in appropriately.
 var lastSlash = params.parent.lastIndexOf('/');
 parentRelayUrl = params.parent.substring(0, lastSlash + 1) + parentRelayUrl;
 } else {
 // Host-relative.
 parentRelayUrl = getOrigin(params.parent) + 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;
 }
 }
 var requiredConfig = {
 parentRelayUrl : gadgets.config.NonEmptyStringValidator
 };
 gadgets.config.register("rpc", requiredConfig, init);
 }
 return {
 register: function(serviceName, handler) {
 if (serviceName === CALLBACK_NAME || serviceName === ACK) {
 throw new Error("Cannot overwrite callback/ack service");
 }
 if (serviceName === DEFAULT_NAME) {
 throw new Error("Cannot overwrite default service:"
 + " use registerDefault");
 }
 services[serviceName] = handler;
 },
 unregister: function(serviceName) {
 if (serviceName === CALLBACK_NAME || serviceName === ACK) {
 throw new Error("Cannot delete callback/ack service");
 }
 if (serviceName === DEFAULT_NAME) {
 throw new Error("Cannot delete default service:"
 + " use unregisterDefault");
 }
 delete services[serviceName];
 },
 registerDefault: function(handler) {
 services[DEFAULT_NAME] = handler;
 },
 unregisterDefault: function() {
 delete services[DEFAULT_NAME];
 },
 forceParentVerifiable: function() {
 if (!transport.isParentVerifiable()) {
 transport = gadgets.rpctx.ifpc;
 }
 },
 call: function(targetId, serviceName, callback, var_args) {
 targetId = targetId || '..';
 // Default to the container calling.
 var from = '..';
 if (targetId === '..') {
 from = window.name;
 }
 ++callId;
 if (callback) {
 callbacks[callId] = callback;
 }
 var rpc = {
 s: serviceName,
 f: from,
 c: callback ? callId : 0,
 a: Array.prototype.slice.call(arguments, 3),
 t: authToken[targetId],
 l: useLegacyProtocol[targetId]
 };
 // If target is on the same domain, call method directly
 // if (callSameDomain(targetId, rpc)) {
 // return;
 // }
 // Attempt to make call via a cross-domain transport.
 var channel = useEarlyQueueing ? receiverTx[targetId] : transport;
 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;
 }
 // If we are told to use the legacy format, then we must
 // default to IFPC.
 if (useLegacyProtocol[targetId]) {
 channel = gadgets.rpctx.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);
 }
 },
 getRelayUrl: function(targetId) {
 var url = relayUrl[targetId];
 // Some RPC methods (wpm, for one) are unhappy with schemeless URLs.
 if (url && url.indexOf('//') == 0) {
 url = document.location.protocol + url;
 }
 return url;
 },
 setRelayUrl: function(targetId, url, opt_useLegacy) {
 relayUrl[targetId] = url;
 useLegacyProtocol[targetId] = !!opt_useLegacy;
 },
 setAuthToken: function(targetId, token) {
 token = token || "";
 // Coerce token to a String, ensuring that all authToken values
 // are strings. This ensures correct comparison with URL params
 // in the process(rpc) method.
 authToken[targetId] = String(token);
 setupFrame(targetId, token);
 },
 getAuthToken: function(targetId) {
 return authToken[targetId];
 },
 getRelayChannel: function() {
 return transport.getCode();
 },
 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])));
 }
 },
 receiveSameDomain: function(rpc) {
 // 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);
 },
 getOrigin: getOrigin,
 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;
 }
 },
 ACK: ACK
 };
}();
// Initialize library/transport.
gadgets.rpc.init();
