window['___jsl'] = window['___jsl'] || {};window['___jsl']['uc'] = 'http:\/\/wembed.corp.gmodules.com\/gadgets\/js\/core:rpc?debug=1';window['___jsl']['u'] = 'http:\/\/wembed.corp.gmodules.com\/gadgets\/js\/core:rpc?debug=1';window['___jsl']['f'] = ['core','rpc'];window['___jsl']['ms'] = 'https://apis.google.com';
/* [start] feature=gapi-globals */
// Input 0
var gapi = window["gapi"] || {};
gapi.client = window["gapi"] && window["gapi"]["client"] || {};

;

/* [end] feature=gapi-globals */

/* [start] feature=globals */
// Input 0
var gadgets = window["gadgets"] || {}, shindig = window["shindig"] || {}, osapi = window["osapi"] = window["osapi"] || {}, google = window["google"] || {};

;

/* [end] feature=globals */

/* [start] feature=taming */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

/**
 * @namespace The global safeJSON namespace
 * @type {Object}
 */
var safeJSON = window['safeJSON'];

/**
 * @namespace The global tamings___ namespace
 * @type {Array.<Function>}
 */
var tamings___ = window['tamings___'] || [];

/**
 * @namespace The global bridge___ namespace
 * @type {Object}
 */
var bridge___;

/**
 * @namespace The global caja___ namespace
 * @type {Object}
 */
var caja___ = window['caja___'];

/**
 * @namespace The global ___ namespace
 * @type {Object}
 */
var ___ = window['___'];
;

/* [end] feature=taming */

/* [start] feature=core.config.base */
window['___cfg'] = window['___cfg'] || window['___gcfg'];;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Provides unified configuration for all features.
 *
 *
 * <p>This is a custom shindig library that has not yet been submitted for
 * standardization. It is designed to make developing of features for the
 * opensocial / gadgets platforms easier and is intended as a supplemental
 * tool to Shindig's standardized feature loading mechanism.
 *
 * <p>Usage:
 * First, you must register a component that needs configuration:
 * <pre>
 *   var config = {
 *     name : gadgets.config.NonEmptyStringValidator,
 *     url : new gadgets.config.RegExValidator(/.+%mySpecialValue%.+/)
 *   };
 *   gadgets.config.register("my-feature", config, myCallback);
 * </pre>
 *
 * <p>This will register a component named "my-feature" that expects input config
 * containing a "name" field with a value that is a non-empty string, and a
 * "url" field with a value that matches the given regular expression.
 *
 * <p>When gadgets.config.init is invoked by the container, it will automatically
 * validate your registered configuration and will throw an exception if
 * the provided configuration does not match what was required.
 *
 * <p>Your callback will be invoked by passing all configuration data passed to
 * gadgets.config.init, which allows you to optionally inspect configuration
 * from other features, if present.
 *
 * <p>Note that the container may optionally bypass configuration validation for
 * performance reasons. This does not mean that you should duplicate validation
 * code, it simply means that validation will likely only be performed in debug
 * builds, and you should assume that production builds always have valid
 * configuration.
 */

if (!window['gadgets']['config']) {
gadgets.config = function() {
  var ___jsl;
  var components = {};
  var configuration = {};

  function foldConfig(origConfig, updConfig) {
    for (var key in updConfig) {
      if (!updConfig.hasOwnProperty(key)) {
        continue;
      }
      if (typeof origConfig[key] === "object" &&
          typeof updConfig[key] === "object") {
        // Both have the same key with an object value. Recurse.
        foldConfig(origConfig[key], updConfig[key]);
      } else {
        // If updConfig has a new key, or a value of different type
        // than the original config for the same key, or isn't an object
        // type, then simply replace the value for the key.
        origConfig[key] = updConfig[key];
      }
    }
  }

  function getLoadingScript() {
    // Attempt to retrieve config augmentation from latest script node.
    var scripts = document.scripts || document.getElementsByTagName("script");
    if (!scripts || scripts.length == 0) return null;
    var scriptTag;
    if (___jsl['u']) {
      for (var i = 0; !scriptTag && i < scripts.length; ++i) {
        var candidate = scripts[i];
        if (candidate.src &&
            candidate.src.indexOf(___jsl['u']) == 0) {
          // Do indexOf test to allow for fragment info
          scriptTag = candidate;
        }
      }
    }
    if (!scriptTag) {
      scriptTag = scripts[scripts.length - 1];
    }
    if (!scriptTag.src) return null;
    return scriptTag;
  }

  function getInnerText(scriptNode) {
    var scriptText = "";
    if (scriptNode.nodeType == 3 || scriptNode.nodeType == 4) {
      scriptText = scriptNode.nodeValue;
    } else if (scriptNode.innerText) {
      scriptText = scriptNode.innerText;
    } else if (scriptNode.innerHTML) {
      scriptText = scriptNode.innerHTML;
    } else if (scriptNode.firstChild) {
      var content = [];
      for (var child = scriptNode.firstChild; child; child = child.nextSibling) {
        content.push(getInnerText(child));
      }
      scriptText = content.join('');
    }
    return scriptText;
  }

  function parseConfig(configText) {
    if (!configText) {
      return {};
    }
    var config;
    while (configText.charCodeAt(configText.length - 1) == 0) {
      // IE9 null-terminates this text, causing problems downstream
      // unless we strip away the terminator.
      configText = configText.substring(0, configText.length - 1);
    }
    try {
      config = (new Function("return (" + configText + "\n)"))();
    } catch (e) { }
    if (typeof config === "object") {
      return config;
    }
    try {
      config = (new Function("return ({" + configText + "\n})"))();
    } catch (e) { }
    return typeof config === "object" ? config : {};
  }

  function augmentConfig(baseConfig) {
    var globalConfig = window['___cfg'];
    if (globalConfig) {
      foldConfig(baseConfig, globalConfig);
    }
    var loadScript = getLoadingScript();
    if (!loadScript) {
      return;
    }
    var scriptText = getInnerText(loadScript);
    var configAugment = parseConfig(scriptText);
    if (___jsl['f'] && ___jsl['f'].length == 1) {
      // Single-feature load on current request.
      // Augmentation adds to just this feature's config if
      // "short-form" syntax is used ie. skipping top-level feature key.
      var feature = ___jsl['f'][0];
      if (!configAugment[feature]) {
        var newConfig = {};
        newConfig[___jsl['f'][0]] = configAugment;
        configAugment = newConfig;
      }
    }
    foldConfig(baseConfig, configAugment);
  }

  /**
   * Iterates through all registered components.
   * @param {function(string,Object)} processor The processor method.
   */
  function forAllComponents(processor) {
    for (var name in components) {
      if (components.hasOwnProperty(name)) {
        var componentList = components[name];
        for (var i = 0, j = componentList.length; i < j; ++i) {
          processor(name, componentList[i]);
        }
      }
    }
  }

  return {
    /**
     * Registers a configurable component and its configuration parameters.
     * Multiple callbacks may be registered for a single component if needed.
     *
     * @param {string} component The name of the component to register. Should
     *     be the same as the fully qualified name of the <Require> feature or
     *     the name of a fully qualified javascript object reference
     *     (e.g. "gadgets.io").
     * @param {Object=} opt_validators Mapping of option name to validation
     *     functions that take the form function(data) {return isValid(data);}.
     * @param {function(Object)=} opt_callback A function to be invoked when a
     *     configuration is registered. If passed, this function will be invoked
     *     immediately after a call to init has been made. Do not assume that
     *     dependent libraries have been configured until after init is
     *     complete. If you rely on this, it is better to defer calling
     *     dependent libraries until you can be sure that configuration is
     *     complete. Takes the form function(config), where config will be
     *     all registered config data for all components. This allows your
     *     component to read configuration from other components.
     * @param {boolean=} opt_callOnUpdate Whether the callback shall be call
     *     on gadgets.config.update() as well.
     * @member gadgets.config
     * @name register
     * @function
     */
    register: function(component, opt_validators, opt_callback,
        opt_callOnUpdate) {
      var registered = components[component];
      if (!registered) {
        registered = [];
        components[component] = registered;
      }

      registered.push({
        validators: opt_validators || {},
        callback: opt_callback,
        callOnUpdate: opt_callOnUpdate
      });
    },

    /**
     * Retrieves configuration data on demand.
     *
     * @param {string=} opt_component The component to fetch. If not provided
     *     all configuration will be returned.
     * @return {Object} The requested configuration, or an empty object if no
     *     configuration has been registered for that component.
     * @member gadgets.config
     * @name get
     * @function
     */
    get: function(opt_component) {
      if (opt_component) {
        return configuration[opt_component] || {};
      }
      return configuration;
    },

    /**
     * Initializes the configuration.
     *
     * @param {Object} config The full set of configuration data.
     * @param {boolean=} opt_noValidation True if you want to skip validation.
     * @throws {Error} If there is a configuration error.
     * @member gadgets.config
     * @name init
     * @function
     */
    init: function(config, opt_noValidation) {
      ___jsl = window["___jsl"] || {};
      foldConfig(configuration, config);
      augmentConfig(configuration);
      var inlineOverride = window["___config"] || {};
      foldConfig(configuration, inlineOverride);
      forAllComponents(function(name, component) {
        var conf = configuration[name];
        if (conf && !opt_noValidation) {
          var validators = component.validators;
          for (var v in validators) {
            if (validators.hasOwnProperty(v)) {
              if (!validators[v](conf[v])) {
                throw new Error('Invalid config value "' + conf[v] +
                    '" for parameter "' + v + '" in component "' +
                    name + '"');
              }
            }
          }
        }

        if (component.callback) {
          component.callback(configuration);
        }
      });
    },

    /**
     * Method largely for dev and debugging purposes that
     * replaces or manually updates feature config.
     * @param updateConfig {Object} Config object, with keys for features.
     * @param opt_replace {Boolean} true to replace all configuration.
     */
    update: function(updateConfig, opt_replace) {
      // Iterate before changing updateConfig and configuration.
      var callbacks = [];
      forAllComponents(function(name, component) {
        if (updateConfig.hasOwnProperty(name) ||
            (opt_replace && configuration && configuration[name])) {
          if (component.callback && component.callOnUpdate) {
            callbacks.push(component.callback);
          }
        }
      });
      configuration = opt_replace ? {} : configuration || {};
      foldConfig(configuration, updateConfig);
      for (var i = 0, j = callbacks.length; i < j; ++i) {
        callbacks[i](configuration);
      }
    }
  };
}();

} else {
  // TODO(xiangtian): remove this temporary fix for b/4463948
  gadgets.config = window['gadgets']['config'];
  gadgets.config.register = gadgets.config['register'];
  gadgets.config.get = gadgets.config['get'];
  gadgets.config.init = gadgets.config['init'];
  gadgets.config.update = gadgets.config['update'];
} // ! end double inclusion guard
;

/* [end] feature=core.config.base */

/* [start] feature=core.log */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Support for basic logging capability for gadgets.
 *
 * This functionality replaces alert(msg) and window.console.log(msg).
 *
 * <p>Currently only works on browsers with a console (WebKit based browsers,
 * Firefox with Firebug extension, or Opera).
 *
 * <p>API is designed to be equivalent to existing console.log | warn | error
 * logging APIs supported by Firebug and WebKit based browsers. The only
 * addition is the ability to call gadgets.setLogLevel().
 */

/**
 * @static
 * @namespace Support for basic logging capability for gadgets.
 * @name gadgets.log
 */

gadgets.log = (function() {
  /** @const */
  var info_ = 1;
  /** @const */
  var warning_ = 2;
  /** @const */
  var error_ = 3;
  /** @const */
  var none_ = 4;

  /**
 * Log an informational message
 * @param {Object|string} message - the message to log.
 * @member gadgets
 * @name log
 * @function
 */
  var log = function(message) {
    logAtLevel(info_, message);
  };

  /**
 * Log a warning
 * @param {Object|string} message - the message to log.
 * @static
 */
  gadgets.warn = function(message) {
    logAtLevel(warning_, message);
  };

  /**
 * Log an error
 * @param {Object|string} message - The message to log.
 * @static
 */
  gadgets.error = function(message) {
    logAtLevel(error_, message);
  };

  /**
 * Sets the log level threshold.
 * @param {number} logLevel - New log level threshold.
 * @static
 * @member gadgets.log
 * @name setLogLevel
 */
  gadgets.setLogLevel = function(logLevel) {
    logLevelThreshold_ = logLevel;
  };

  /**
 * Logs a log message if output console is available, and log threshold is met.
 * @param {number} level - the level to log with. Optional, defaults to gadgets.log.INFO.
 * @param {Object|string} message - The message to log.
 * @private
 */
  function logAtLevel(level, message) {
    if (level < logLevelThreshold_ || !_console) {
      return;
    }

    if (level === warning_ && _console.warn) {
      _console.warn(message);
    } else if (level === error_ && _console.error) {
      // Avoid Firefox 3.6 + Firebug error Window._firebugStackTrace. Happens
      // when called in iframe embedded in container with different domain.
      try {
        _console.error(message);
      } catch (e) {
      }
    } else if (_console.log) {
      _console.log(message);
    }
  };

  /**
 * Log level for informational logging.
 * @static
 * @const
 * @member gadgets.log
 * @name INFO
 */
  log['INFO'] = info_;

  /**
 * Log level for warning logging.
 * @static
 * @const
 * @member gadgets.log
 * @name WARNING
 */
  log['WARNING'] = warning_;

  /**
 * Log level for no logging
 * @static
 * @const
 * @member gadgets.log
 * @name NONE
 */
  log['NONE'] = none_;

  /**
 * Current log level threshold.
 * @type {number}
 * @private
 */
  var logLevelThreshold_ = info_;



  /**
 * Console to log to
 * @private
 * @static
 */
  var _console = window.console ? window.console :
                       window.opera ? window.opera.postError : undefined;

  return log;
})();
;
;

/* [end] feature=core.log */

/* [start] feature=core.config */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

// Defines default validators in a separate file from the rest of the config
// system, to enable its separability from these.

(function() {
  /**
    * Ensures that data is one of a fixed set of items.
    * Also supports argument sytax: EnumValidator("Dog", "Cat", "Fish");
    *
    * @param {Array.<string>} list The list of valid values.
    *
    * @member gadgets.config
    * @name  EnumValidator
    * @function
    */
  gadgets.config.EnumValidator = function(list) {
    var listItems = [];
    if (arguments.length > 1) {
      for (var i = 0, arg; (arg = arguments[i]); ++i) {
        listItems.push(arg);
      }
    } else {
      listItems = list;
    }
    return function(data) {
      for (var i = 0, test; (test = listItems[i]); ++i) {
        if (data === listItems[i]) {
          return true;
        }
      }
      return false;
    };
  };

  /**
   * Tests the value against a regular expression.
   * @member gadgets.config
   * @name RegexValidator
   * @function
   */
  gadgets.config.RegExValidator = function(re) {
    return function(data) {
      return re.test(data);
    };
  };

  /**
   * Validates that a value was provided.
   * @param {*} data
   * @member gadgets.config
   * @name ExistsValidator
   * @function
   */
  gadgets.config.ExistsValidator = function(data) {
    return typeof data !== 'undefined';
  };

  /**
   * Validates that a value is a non-empty string.
   * @param {*} data
   * @member gadgets.config
   * @name NonEmptyStringValidator
   * @function
   */
  gadgets.config.NonEmptyStringValidator = function(data) {
    return typeof data === 'string' && data.length > 0;
  };

  /**
   * Validates that the value is a boolean.
   * @param {*} data
   * @member gadgets.config
   * @name BooleanValidator
   * @function
   */
  gadgets.config.BooleanValidator = function(data) {
    return typeof data === 'boolean';
  };

  /**
   * Similar to the ECMAScript 4 virtual typing system, ensures that
   * whatever object was passed in is "like" the existing object.
   * Doesn't actually do type validation though, but instead relies
   * on other validators.
   *
   * This can be used recursively as well to validate sub-objects.
   *
   * @example
   *
   *  var validator = new gadgets.config.LikeValidator(
   *    "booleanField" : gadgets.config.BooleanValidator,
   *    "regexField" : new gadgets.config.RegExValidator(/foo.+/);
   *  );
   *
   *
   * @param {Object} test The object to test against.
   * @member gadgets.config
   * @name BooleanValidator
   * @function
   */
  gadgets.config.LikeValidator = function(test) {
    return function(data) {
      for (var member in test) {
        if (test.hasOwnProperty(member)) {
          var t = test[member];
          if (!t(data[member])) {
            return false;
          }
        }
      }
      return true;
    };
  };
})();
;

/* [end] feature=core.config */

/* [start] feature=core.util.base */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview General purpose utilities that gadgets can use.
 */

/**
 * @static
 * @class Provides general-purpose utility functions.
 * @name gadgets.util
 */
gadgets.util = gadgets.util || {};

(function() {
/**
 * Creates a closure that is suitable for passing as a callback.
 * Any number of arguments
 * may be passed to the callback;
 * they will be received in the order they are passed in.
 *
 * @param {Object} scope The execution scope; may be null if there is no
 *     need to associate a specific instance of an object with this
 *     callback.
 * @param {Function} callback The callback to invoke when this is run;
 *     any arguments passed in will be passed after your initial arguments.
 * @param {...(Object|string)} var_args Initial arguments to be passed to the callback.
 *
 * @member gadgets.util
 * @private Implementation detail.
 */
gadgets.util.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);
  };
};


/**
 * Utility function for generating an "enum" from an array.
 *
 * @param {Array.<string>} values The values to generate.
 * @return {Object.<string,string>} An object with member fields to handle
 *   the enum.
 *
 * @private Implementation detail.
 */
gadgets.util.makeEnum = function(values) {
  var i, v, obj = {};
  for (i = 0; (v = values[i]); ++i) {
    obj[v] = v;
  }
  return obj;
};

})();
;

/* [end] feature=core.util.base */

/* [start] feature=core.util.dom */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview General purpose utilities that gadgets can use.
 */

/**
 * @static
 * @class Provides general-purpose utility functions.
 * @name gadgets.util
 */
gadgets.util = gadgets.util || {};

(function() {  

  var XHTML_SPEC = 'http://www.w3.org/1999/xhtml';

  function attachAttributes(elem, opt_attribs) {
    var attribs = opt_attribs || {};
    for (var attrib in attribs) {
      if (attribs.hasOwnProperty(attrib)) {
        elem[attrib] = attribs[attrib];
      }
    }
  }

  function stringifyElement(tagName, opt_attribs) {
    var arr = ['<', tagName];
    var attribs = opt_attribs || {};
    for (var attrib in attribs) {
      if (attribs.hasOwnProperty(attrib)) {
        arr.push(' ');
        arr.push(attrib);
        arr.push('="');
        arr.push(gadgets.util.escapeString(attribs[attrib]));
        arr.push('"');
      }
    }
    arr.push('></');
    arr.push(tagName);
    arr.push('>');
    return arr.join('');
  }

  function getInnerText(node) {
    var text = '';
    if (node.nodeType == 3 || node.nodeType == 4) {
      text = node.nodeValue;
    } else if (node.innerText) {
      text = node.innerText;
    } else if (node.innerHTML) {
      text = node.innerHTML;
    } else if (node.firstChild) {
      var content = [];
      for (var child = node.firstChild; child; child = child.nextSibling) {
        content.push(getInnerText(child));
      }
      text = content.join('');
    }
    return text;
  }

  /**
   * Creates an HTML or XHTML element.
   * @param {string} tagName The type of element to construct.
   * @return {Element} The newly constructed element.
   */
  gadgets.util.createElement = function(tagName) {
    var element;
    if ((!document.body) || document.body.namespaceURI) {
      try {
        element = document.createElementNS(XHTML_SPEC, tagName);
      } catch (nonXmlDomException) {
      }
    }
    return element || document.createElement(tagName);
  };

  /**
   * Creates an HTML or XHTML iframe element with attributes.
   * @param {Object=} opt_attribs Optional set of attributes to attach. The
   * only working attributes are spelled the same way in XHTML attribute
   * naming (most strict, all-lower-case), HTML attribute naming (less strict,
   * case-insensitive), and JavaScript property naming (some properties named
   * incompatibly with XHTML/HTML).
   * @return {Element} The DOM node representing body.
   */
  gadgets.util.createIframeElement = function(opt_attribs) {
    var frame = gadgets.util.createElement('iframe');
    try {
      // TODO: provide automatic mapping to only set the needed
      // and JS-HTML-XHTML compatible subset through stringifyElement (just
      // 'name' and 'id', AFAIK). The values of the attributes will be
      // stringified should the stringifyElement code path be taken (IE)
      var tagString = stringifyElement('iframe', opt_attribs);
      var ieFrame = gadgets.util.createElement(tagString);
      if (ieFrame &&
          ((!frame) ||
           ((ieFrame.tagName == frame.tagName) &&
            (ieFrame.namespaceURI == frame.namespaceURI)))) {
        frame = ieFrame;
      }
    } catch (nonStandardCallFailed) {
    }
    attachAttributes(frame, opt_attribs);
    return frame;
  };  

  /**
   * Gets the HTML or XHTML body element.
   * @return {Element} The DOM node representing body.
   */
  gadgets.util.getBodyElement = function() {
    if (document.body) {
      return document.body;
    }
    try {
      var xbodies = document.getElementsByTagNameNS(XHTML_SPEC, 'body');
      if (xbodies && (xbodies.length == 1)) {
        return xbodies[0];
      }
    } catch (nonXmlDomException) {
    }
    return document.documentElement || document;
  };

  /**
   * @param {Element} node DOM node to get inner text from.
   * @return {string} The inner value/text/html and content of the given node.
   */
  gadgets.util.getInnerText = function(node) {
    return getInnerText(node);
  };
})();
;

/* [end] feature=core.util.dom */

/* [start] feature=core.util.event */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview General purpose utilities that gadgets can use.
 */

gadgets.util = gadgets.util || {};

(function() {
/**
 * Attach an event listener to given DOM element (Not a gadget standard)
 *
 * @param {Object} elem  DOM element on which to attach event.
 * @param {string} eventName  Event type to listen for.
 * @param {function()} callback  Invoked when specified event occurs.
 * @param {boolean} useCapture  If true, initiates capture.
 */
gadgets.util.attachBrowserEvent = function(elem, eventName, callback, useCapture) {
  if (typeof elem.addEventListener != 'undefined') {
    elem.addEventListener(eventName, callback, useCapture);
  } else if (typeof elem.attachEvent != 'undefined') {
    elem.attachEvent('on' + eventName, callback);
  } else {
    gadgets.warn('cannot attachBrowserEvent: ' + eventName);
  }
};

/**
 * Remove event listener. (Shindig internal implementation only)
 *
 * @param {Object} elem  DOM element from which to remove event.
 * @param {string} eventName  Event type to remove.
 * @param {function()} callback  Listener to remove.
 * @param {boolean} useCapture  Specifies whether listener being removed was added with
 *                              capture enabled.
 */
gadgets.util.removeBrowserEvent = function(elem, eventName, callback, useCapture) {
  if (elem.removeEventListener) {
    elem.removeEventListener(eventName, callback, useCapture);
  } else if (elem.detachEvent) {
    elem.detachEvent('on' + eventName, callback);
  } else {
    gadgets.warn('cannot removeBrowserEvent: ' + eventName);
  }
};

})();
;

/* [end] feature=core.util.event */

/* [start] feature=core.util.onload */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview General purpose utilities that gadgets can use.
 */

/**
 * @static
 * @class Provides general-purpose utility functions for onload.
 * @name gadgets.util
 */
gadgets.util = gadgets.util || {};

(function() {

  var onLoadHandlers = [];

  /**
   * Registers an onload handler.
   * @param {function()} callback The handler to run.
   *
   * @member gadgets.util
   */
  gadgets.util.registerOnLoadHandler = function(callback) {
    onLoadHandlers.push(callback);
  };

  /**
   * Runs all functions registered via registerOnLoadHandler.
   * @private Only to be used by the container, not gadgets.
   */
  gadgets.util.runOnLoadHandlers = function() {
    for (var i = 0, j = onLoadHandlers.length; i < j; ++i) {
      onLoadHandlers[i]();
    }
  };

})();

;

/* [end] feature=core.util.onload */

/* [start] feature=core.util.string */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview General purpose utilities that gadgets can use.
 */

/**
 * @static
 * @class Provides a thin method for parsing url parameters.
 * @name gadgets.util
 */
gadgets.util = gadgets.util || {};

(function() {

  /**
   * @enum {boolean}
   * @const
   * @private
   * 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,
    // fullwidth quotation mark
    65282 : true,
    // fullwidth apostrophe
    65287 : true,
    // fullwidth less-than sign
    65308 : true,
    // fullwidth greater-than sign
    65310 : true,
    // fullwidth reverse solidus
    65340 : true
  };

  /**
   * Regular expression callback that returns strings from unicode code points.
   *
   * @param {Array} match Ignored.
   * @param {number} value The codepoint value to convert.
   * @return {string} The character corresponding to value.
   */
  function unescapeEntity(match, value) {
    // TODO: b0rked for UTF-16 and can easily be convinced to generate
    // truncating NULs or completely invalid non-Unicode characters. Here's a
    // fixed version (it handles entities for valid codepoints from U+0001 ...
    // U+10FFFD, except for the non-character codepoints U+...FFFE and
    // U+...FFFF; isolated UTF-16 surrogate pairs are supported for
    // compatibility with previous versions of escapeString, 0 generates the
    // empty string rather than a possibly-truncating '\0', and all other inputs
    // generate U+FFFD (the replacement character, standard practice for
    // non-signalling Unicode codecs like this one)
    //     return (
    //         (value > 0) &&
    //         (value <= 0x10fffd) &&
    //         ((value & 0xffff) < 0xfffe)) ?
    //       ((value <= 0xffff) ?
    //         String.fromCharCode(value) :
    //         String.fromCharCode(
    //           ((value - 0x10000) >> 10) | 0xd800,
    //           ((value - 0x10000) & 0x3ff) | 0xdc00)) :
    //       ((value === 0) ? '' : '\ufffd');
    return String.fromCharCode(value);
  }

  /**
   * Escapes the input using html entities to make it safer.
   *
   * If the input is a string, uses gadgets.util.escapeString.
   * If it is an array, calls escape on each of the array elements
   * if it is an object, will only escape all the mapped keys and values if
   * the opt_escapeObjects flag is set. This operation involves creating an
   * entirely new object so only set the flag when the input is a simple
   * string to string map.
   * Otherwise, does not attempt to modify the input.
   *
   * @param {Object|string} input The object to escape.
   * @param {boolean=} opt_escapeObjects Whether to escape objects.
   * @return {Object|string} The escaped object.
   * @private Only to be used by the container, not gadgets.
   */
  gadgets.util.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;
  };

  /**
   * Escapes the input using html entities to make it safer.
   *
   * Currently not in the spec -- future proposals may change
   * how this is handled.
   *
   * @param {string} str The string to escape.
   * @return {string} The escaped string.
   */
  gadgets.util.escapeString = function(str) {
    if (!str) return 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('');
  };

  /**
   * Reverses escapeString
   *
   * @param {string} str The string to unescape.
   * @return {string}
   */
  gadgets.util.unescapeString = function(str) {
    if (!str) return str;
    return str.replace(/&#([0-9]+);/g, unescapeEntity);
  };

})();
;

/* [end] feature=core.util.string */

/* [start] feature=core.util.urlparams */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview General purpose utilities that gadgets can use.
 */

/**
 * @static
 * @class Provides a thin method for parsing url parameters.
 * @name gadgets.util
 */
gadgets.util = gadgets.util || {};

(function() {  
  var parameters = null;

  /**
   * Parses URL parameters into an object.
   * @param {string} url - the url parameters to parse.
   * @return {Array.<string>} The parameters as an array.
   */
  function parseUrlParams(url) {
    // Get settings from url, 'hash' takes precedence over 'search' component
    // don't use document.location.hash due to browser differences.
    var query;
    var queryIdx = url.indexOf('?');
    var hashIdx = url.indexOf('#');
    if (hashIdx === -1) {
      query = url.substr(queryIdx + 1);
    } else {
      // essentially replaces "#" with "&"
      query = [url.substr(queryIdx + 1, hashIdx - queryIdx - 1), '&',
               url.substr(hashIdx + 1)].join('');
    }
    return query.split('&');
  }

  /**
   * Gets the URL parameters.
   *
   * @param {string=} opt_url Optional URL whose parameters to parse.
   *                         Defaults to window's current URL.
   * @return {Object} Parameters passed into the query string.
   * @member gadgets.util
   * @private Implementation detail.
   */
  gadgets.util.getUrlParameters = function(opt_url) {
    var no_opt_url = typeof opt_url === 'undefined';
    if (parameters !== null && no_opt_url) {
      // "parameters" is a cache of current window params only.
      return parameters;
    }
    var parsed = {};
    var pairs = parseUrlParams(opt_url || document.location.href);
    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, ' ');
      try {
        parsed[argName] = unesc(value);
      } catch (e) {
        // Undecodable/invalid value; ignore.
      }
    }
    if (no_opt_url) {
      // Cache current-window params in parameters var.
      parameters = parsed;
    }
    return parsed;
  };

  // Initialize url parameters so that hash data is pulled in before it can be
  // altered by a click.
  gadgets.util.getUrlParameters();
})();
;

/* [end] feature=core.util.urlparams */

/* [start] feature=core.util */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview General purpose utilities that gadgets can use.
 */

/**
 * @static
 * @class Provides general-purpose utility functions.
 * @name gadgets.util
 */
gadgets.util = gadgets.util || {};

(function() {  
  
  var features = {};
  var services = {};

  /**
   * Initializes feature parameters.
   */
  function init(config) {
    features = config['core.util'] || {};
  }
  if (gadgets.config) {
    gadgets.config.register('core.util', null, init);
  }

  /**
   * Gets the feature parameters.
   *
   * @param {string} feature The feature to get parameters for.
   * @return {Object} The parameters for the given feature, or null.
   *
   * @member gadgets.util
   */
  gadgets.util.getFeatureParameters = function(feature) {
    return typeof features[feature] === 'undefined' ? null : features[feature];
  };

  /**
   * Returns whether the current feature is supported.
   *
   * @param {string} feature The feature to test for.
   * @return {boolean} True if the feature is supported.
   *
   * @member gadgets.util
   */
  gadgets.util.hasFeature = function(feature) {
    return typeof features[feature] !== 'undefined';
  };

  /**
   * Returns the list of services supported by the server
   * serving this gadget.
   *
   * @return {Object} List of Services that enumerate their methods.
   *
   * @member gadgets.util
   */
  gadgets.util.getServices = function() {
    return services;
  };

})();
;

/* [end] feature=core.util */

/* [start] feature=shindig.auth */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*global gadgets */

/**
 * @fileoverview
 *
 * Manages the gadget security token AKA the gadget auth token AKA the
 * social token.  Also provides an API for the container server to
 * efficiently pass authenticated data to the gadget at render time.
 *
 * The shindig.auth package is not part of the opensocial or gadgets spec,
 * and gadget authors should never use these functions or the security token
 * directly.  These APIs are an implementation detail and are for shindig
 * internal use only.
 *
 * Passing authenticated data into the gadget at render time:
 *
 * The gadget auth token is the only way for the container to allow the
 * gadget access to authenticated data.  gadgets.io.makeRequest for SIGNED
 * or OAUTH requests relies on the authentication token.  Access to social data
 * also relies on the authentication token.
 *
 * The authentication token is normally passed into the gadget on the URL
 * fragment (after the #), and so is not visible to the gadget rendering
 * server.  This keeps the token from being leaked in referer headers, but at
 * the same time limits the amount of authenticated data the gadget can view
 * quickly: fetching authenticated data requires an extra round trip.
 *
 * If the authentication token is passed to the gadget as a query parameter,
 * the gadget rendering server gets an opportunity to view the token during
 * the rendering process.  This allows the rendering server to quickly inject
 * authenticated data into the gadget, at the price of potentially leaking
 * the authentication token in referer headers.  That risk can be mitigated
 * by using a short-lived authentication token on the query string, which
 * the gadget server can swap for a longer lived token at render time.
 *
 * If the rendering server injects authenticated data into the gadget in the
 * form of a JSON string, the resulting javascript object can be accessed via
 * shindig.auth.getTrustedData.
 *
 * To access the security token:
 *   var st = shindig.auth.getSecurityToken();
 *
 * To update the security token with new data from the gadget server:
 *   shindig.auth.updateSecurityToken(newToken);
 *
 * To quickly access a javascript object that has been authenticated by the
 * container and the rendering server:
 *   var trusted = shindig.auth.getTrustedData();
 *   doSomething(trusted.foo.bar);
 */

/**
 * Class used to mange the gadget auth token.  Singleton initialized from
 * auth-init.js.
 *
 * @constructor
 */
shindig.Auth = function() {
  /**
   * The authentication token.
   */
  var authToken = null;

  /**
   * Trusted object from container.
   */
  var trusted = null;

  /**
   * Copy URL parameters into the auth token
   *
   * The initial auth token can look like this:
   *    t=abcd&url=$&foo=
   *
   * If any of the values in the token are '$', a matching parameter
   * from the URL will be inserted, for example:
   *    t=abcd&url=http%3A%2F%2Fsome.gadget.com&foo=
   *
   * Why do this at all?  The only currently known use case for this is
   * efficiently including the gadget URL in the auth token.  If you embed
   * the entire URL in the security token, you effectively double the size
   * of the URL passed on the gadget rendering request:
   *   /gadgets/ifr?url=<gadget-url>#st=<encrypted-gadget-url>
   *
   * This can push the gadget render URL beyond the max length supported
   * by browsers, and then things break.  To work around this, the
   * security token can include only a (much shorter) hash of the gadget-url:
   *  /gadgets/ifr?url=<gadget-url>#st=<xyz>
   *
   * However, we still want the proxy that handles gadgets.io.makeRequest
   * to be able to look up the gadget URL efficiently, without requring
   * a database hit.  To do that, we modify the auth token here to fill
   * in any blank values.  The auth token then becomes:
   *    t=<xyz>&url=<gadget-url>
   *
   * We send the expanded auth token in the body of post requests, so we
   * don't run into problems with length there.  (But people who put
   * several hundred characters in their gadget URLs are still lame.)
   * @param {Object} urlParams
   */
  function addParamsToToken(urlParams) {
    var args = authToken.split('&');
    for (var i = 0; i < args.length; i++) {
      var nameAndValue = args[i].split('=');
      if (nameAndValue.length === 2) {
        var name = nameAndValue[0];
        var value = nameAndValue[1];
        if (value === '$') {
          value = encodeURIComponent(urlParams[name]);
          args[i] = name + '=' + value;
        }
      }
    }
    authToken = args.join('&');
  }

  function init(configuration) {
    var urlParams = gadgets.util.getUrlParameters();
    var config = configuration['shindig.auth'] || {};

    // Auth token - might be injected into the gadget directly, or might
    // be on the URL (hopefully on the fragment).
    if (config['authToken']) {
      authToken = config['authToken'];
    } else if (urlParams['st']) {
      authToken = urlParams['st'];
    }
    if (authToken !== null) {
      addParamsToToken(urlParams);
    }

    // Trusted JSON.  We use eval directly because this was injected by the
    // container server and json parsing is slow in IE.
    if (config['trustedJson']) {
      trusted = /** @type {Object} */ (eval('(' +
          config['trustedJson'] + ')'));
    }
  }

  gadgets.config.register('shindig.auth', null, init);

  return /** @scope shindig.auth */ {

    /**
     * Gets the auth token.
     *
     * @return {string} the gadget authentication token.
     *
     * @member shindig.auth
     */
    getSecurityToken: function() {
      return authToken;
    },

    /**
     * Updates the security token with new data from the gadget server.
     *
     * @param {string} newToken the new auth token data.
     *
     * @member shindig.auth
     */
    updateSecurityToken: function(newToken) {
      authToken = newToken;
    },

    /**
     * Quickly retrieves data that is known to have been injected by
     * a trusted container server.
     * @return {Object}
     */
    getTrustedData: function() {
      return trusted;
    }
  };
};
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview
 *
 * Bootstraps auth.js.
 */

shindig.auth = new shindig.Auth();
;

/* [end] feature=shindig.auth */

/* [start] feature=core.json */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview
 * The global object gadgets.json contains two methods.
 *
 * gadgets.json.stringify(value) takes a JavaScript value and produces a JSON
 * text. The value must not be cyclical.
 *
 * gadgets.json.parse(text) takes a JSON text and produces a JavaScript value.
 * It will return false if there is an error.
 */

/**
 * @static
 * @class Provides operations for translating objects to and from JSON.
 * @name gadgets.json
 */

/**
 * Just wrap native JSON calls when available.
 */
if (window.JSON && window.JSON.parse && window.JSON.stringify) {
  // HTML5 implementation, or already defined.
  // Not a direct alias as the opensocial specification disagrees with the HTML5 JSON spec.
  // JSON says to throw on parse errors and to support filtering functions. OS does not.
  gadgets.json = (function() {
    var endsWith___ = /___$/;

    function getOrigValue(key, value) {
      var origValue = this[key];
      return origValue;
    }

    return {
      /* documented below */
      parse: function(str) {
        try {
          return window.JSON.parse(str);
        } catch (e) {
          return false;
        }
      },
      /* documented below */
      stringify: function(obj) {
        var orig = window.JSON.stringify;
        function patchedStringify(val) {
          return orig.call(this, val, getOrigValue);
        }
        var stringifyFn = (Array.prototype.toJSON && orig([{x:1}]) === "\"[{\\\"x\\\": 1}]\"") ?
            patchedStringify : orig;
        try {
          return stringifyFn(obj, function(k,v) {
            return !endsWith___.test(k) ? v : void 0;
          });
        } catch (e) {
          return null;
        }
      }
    };
  })();
}
;
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview
 * The global object gadgets.json contains two methods.
 *
 * gadgets.json.stringify(value) takes a JavaScript value and produces a JSON
 * text. The value must not be cyclical.
 *
 * gadgets.json.parse(text) takes a JSON text and produces a JavaScript value.
 * It will return false if there is an error.
 */

/**
 * @static
 * @class Provides operations for translating objects to and from JSON.
 * @name gadgets.json
 */

/**
 * JavaScript-based implementation when window.JSON is not present.
 * Port of the public domain JSON library by Douglas Crockford.
 * See: http://www.json.org/json2.js
 */
if (!(window.JSON && window.JSON.parse && window.JSON.stringify)) {
  /**
   * Port of the public domain JSON library by Douglas Crockford.
   * See: http://www.json.org/json2.js
   */
  gadgets.json = function() {

    /**
     * Formats integers to 2 digits.
     * @param {number} n
     * @private
     */
    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
    /**
     * @const
     */
    var m = {
      '\b': '\\b',
      '\t': '\\t',
      '\n': '\\n',
      '\f': '\\f',
      '\r': '\\r',
      '"' : '\\"',
      '\\': '\\\\'
    };

    /**
     * Converts a json object into a string.
     * @param {*} value
     * @return {string}
     * @member gadgets.json
     */
    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 (/___$/.test(k))
              continue;
            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 '';
    }

    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;
      }
    };
  }();
}
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * Flatten an object to a stringified values. Useful for dealing with
 * json->querystring transformations. Note: not in official specification yet
 *
 * @param {Object} obj
 * @return {Object} object with only string values.
 */
gadgets.json.flatten = function(obj) {
  var flat = {};

  if (obj === null || obj === undefined) return flat;

  for (var k in obj) {
    if (obj.hasOwnProperty(k)) {
      var value = obj[k];
      if (null === value || undefined === value) {
        continue;
      }
      flat[k] = (typeof value === 'string') ? value : gadgets.json.stringify(value);
    }
  }
  return flat;
};
;

/* [end] feature=core.json */

/* [start] feature=core.io */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*global ActiveXObject, DOMParser */
/*global shindig */

/**
 * @fileoverview Provides remote content retrieval facilities.
 *     Available to every gadget.
 */

/**
 * @static
 * @class Provides remote content retrieval functions.
 * @name gadgets.io
 */

gadgets.io = function() {
  /**
   * Holds configuration-related data such as proxy urls.
   */
  var config = {};

  /**
   * Holds state for OAuth.
   */
  var oauthState;

  /**
   * Internal facility to create an xhr request.
   */
  function makeXhr() {
    var x;
    var wrapperXhr = window['shindig'] && window['shindig']['xhrwrapper'] &&
        window['shindig']['xhrwrapper']['createXHR'];
    if (wrapperXhr) {
      return wrapperXhr();
    } else if (typeof ActiveXObject != 'undefined') {
      x = new ActiveXObject('Msxml2.XMLHTTP');
      if (!x) {
        x = new ActiveXObject('Microsoft.XMLHTTP');
      }
      return x;
    }
    // The second construct is for the benefit of jsunit...
    else if (typeof XMLHttpRequest != 'undefined' || window.XMLHttpRequest) {
      return new window.XMLHttpRequest();
    }
    else throw ('no xhr available');
  }

  /**
   * Checks the xobj for errors, may call the callback with an error response
   * if the error is fatal.
   *
   * @param {Object} xobj The XHR object to check.
   * @param {function(Object)} callback The callback to call if the error is fatal.
   * @return {boolean} true if the xobj is not ready to be processed.
   */
  function hadError(xobj, callback) {
    if (xobj['readyState'] !== 4) {
      return true;
    }
    try {
      if (xobj['status'] !== 200) {
        var error = ('' + xobj['status']);
        if (xobj['responseText']) {
          error = error + ' ' + xobj['responseText'];
        }
        callback({
          'errors': [error],
          'rc': xobj['status'],
          'text': xobj['responseText']
        });
        return true;
      }
    } catch (e) {
      callback({
        'errors': [e['number'] + ' Error not specified'],
        'rc': e['number'],
        'text': e['description']
      });
      return true;
    }
    return false;
  }

  /**
   * Handles non-proxied XHR callback processing.
   *
   * @param {string} url
   * @param {function(Object)} callback
   * @param {Object} params
   * @param {Object} xobj
   */
  function processNonProxiedResponse(url, callback, params, xobj) {
    if (hadError(xobj, callback)) {
      return;
    }
    var data = {
      'body': xobj['responseText']
    };
    callback(transformResponseData(params, data));
  }

  var UNPARSEABLE_CRUFT = "throw 1; < don't be evil' >";

  /**
   * Handles XHR callback processing.
   *
   * @param {string} url
   * @param {function(Object)} callback
   * @param {Object} params
   * @param {Object} xobj
   */
  function processResponse(url, callback, params, xobj) {
    if (hadError(xobj, callback)) {
      return;
    }
    var txt = xobj['responseText'];

    // remove unparseable cruft used to prevent cross-site script inclusion
    var offset = txt.indexOf(UNPARSEABLE_CRUFT) + UNPARSEABLE_CRUFT.length;

    // If no cruft then just return without a callback - avoid JS errors
    // TODO craft an error response?
    if (offset < UNPARSEABLE_CRUFT.length) return;
    txt = txt.substr(offset);

    // We are using eval directly here  because the outer response comes from a
    // trusted source, and json parsing is slow in IE.
    var data = eval('(' + txt + ')');
    data = data[url];
    // Save off any transient OAuth state the server wants back later.
    if (data['oauthState']) {
      oauthState = data['oauthState'];
    }
    // Update the security token if the server sent us a new one
    if (data['st']) {
      shindig.auth.updateSecurityToken(data['st']);
    }
    callback(transformResponseData(params, data));
  }

  /**
   * @param {Object} params
   * @param {Object} data
   * @return {Object}
   */

  function transformResponseData(params, data) {
    // Sometimes rc is not present, generally when used
    // by jsonrpccontainer, so assume 200 in its absence.
    var resp = {
      'text': data['body'],
      'rc': data['rc'] || 200,
      'headers': data['headers'],
      'oauthApprovalUrl': data['oauthApprovalUrl'],
      'oauthError': data['oauthError'],
      'oauthErrorText': data['oauthErrorText'],
      'errors': []
    };

    if (resp['rc'] < 200 || resp['rc'] >= 400) {
      resp['errors'] = [resp['rc'] + ' Error'];
    } else if (resp['text']) {
      if (resp['rc'] >= 300 && resp['rc'] < 400) {
        // Redirect pages will usually contain arbitrary
        // HTML which will fail during parsing, inadvertently
        // causing a 500 response. Thus we treat as text.
        params['CONTENT_TYPE'] = 'TEXT';
      }
      switch (params['CONTENT_TYPE']) {
        case 'JSON':
        case 'FEED':
          resp['data'] = gadgets.json.parse(resp.text);
          if (!resp['data']) {
            resp['errors'].push('500 Failed to parse JSON');
            resp['rc'] = 500;
            resp['data'] = null;
          }
          break;
        case 'DOM':
          var dom;
          if (typeof ActiveXObject != 'undefined') {
            dom = new ActiveXObject('Microsoft.XMLDOM');
            dom.async = false;
            dom.validateOnParse = false;
            dom.resolveExternals = false;
            if (!dom.loadXML(resp['text'])) {
              resp['errors'].push('500 Failed to parse XML');
              resp['rc'] = 500;
            } else {
              resp['data'] = dom;
            }
          } else {
            var parser = new DOMParser();
            dom = parser.parseFromString(resp['text'], 'text/xml');
            if ('parsererror' === dom.documentElement.nodeName) {
              resp['errors'].push('500 Failed to parse XML');
              resp['rc'] = 500;
            } else {
              resp['data'] = dom;
            }
          }
          break;
        default:
          resp['data'] = resp['text'];
          break;
      }
    }
    return resp;
  }

  /**
   * Sends an XHR post or get request
   *
   * @param {string} realUrl The url to fetch data from that was requested by the gadget.
   * @param {string} proxyUrl The url to proxy through.
   * @param {function()} callback The function to call once the data is fetched.
   * @param {Object} paramData The params to use when processing the response.
   * @param {string} method
   * @param {function(string,function(Object),Object,Object)}
   *     processResponseFunction The function that should process the
   *     response from the sever before calling the callback.
   * @param {Object=} opt_headers - Optional headers including a Content-Type that defaults to
   *     'application/x-www-form-urlencoded'.
   */
  function makeXhrRequest(realUrl, proxyUrl, callback, paramData, method,
      params, processResponseFunction, opt_headers) {
    var xhr = makeXhr();

    if (proxyUrl.indexOf('//') == 0) {
      proxyUrl = document.location.protocol + proxyUrl;
    }

    xhr.open(method, proxyUrl, true);
    if (callback) {
      xhr.onreadystatechange = gadgets.util.makeClosure(
          null, processResponseFunction, realUrl, callback, params, xhr);
    }
    if (paramData !== null) {
      var contentTypeHeader = 'Content-Type';
      var contentType = 'application/x-www-form-urlencoded';
      if (typeof opt_headers === "string") {
        // This turned out to come directly from a public API, so we need to
        // keep compatibility...
        contentType = opt_headers;
        opt_headers = {};
      }
      var headers = opt_headers || {};
      if (!headers[contentTypeHeader]) headers[contentTypeHeader] = contentType;

      for (var headerName in headers) {
        xhr.setRequestHeader(headerName, headers[headerName]);
      }
    }
    xhr.send(paramData);
  }

  /**
   * Satisfy a request with data that is prefetched as per the gadget Preload
   * directive. The preloader will only satisfy a request for a specific piece
   * of content once.
   *
   * @param {Object} postData The definition of the request to be executed by the proxy.
   * @param {Object} params The params to use when processing the response.
   * @param {function(Object)} callback The function to call once the data is fetched.
   * @return {boolean} true if the request can be satisfied by the preloaded
   *         content false otherwise.
   */
  function respondWithPreload(postData, params, callback) {
    if (gadgets.io.preloaded_ && postData.httpMethod === 'GET') {
      for (var i = 0; i < gadgets.io.preloaded_.length; i++) {
        var preload = gadgets.io.preloaded_[i];
        if (preload && (preload.id === postData.url)) {
          // Only satisfy once
          delete gadgets.io.preloaded_[i];

          if (preload['rc'] !== 200) {
            callback({'rc': preload['rc'], 'errors': [preload['rc'] + ' Error']});
          } else {
            if (preload['oauthState']) {
              oauthState = preload['oauthState'];
            }
            var resp = {
              'body': preload['body'],
              'rc': preload['rc'],
              'headers': preload['headers'],
              'oauthApprovalUrl': preload['oauthApprovalUrl'],
              'oauthError': preload['oauthError'],
              'oauthErrorText': preload['oauthErrorText'],
              'errors': []
            };
            callback(transformResponseData(params, resp));
          }
          return true;
        }
      }
    }
    return false;
  }

  /**
   * @param {Object} configuration Configuration settings.
   * @private
   */
  function init(configuration) {
    config = configuration['core.io'] || {};
  }

  gadgets.config.register('core.io', null, init);

  return /** @scope gadgets.io */ {
    /**
     * Fetches content from the provided URL and feeds that content into the
     * callback function.
     *
     * Example:
     * <pre>
     * gadgets.io.makeRequest(url, fn,
     *    {contentType: gadgets.io.ContentType.FEED});
     * </pre>
     *
     * @param {string} url The URL where the content is located.
     * @param {function(Object)} callback The function to call with the data from
     *     the URL once it is fetched.
     * @param {Object.<string, (Object|string)>=} opt_params
     *     Additional
     *     <a href="gadgets.io.RequestParameters.html">parameters</a>
     *     to pass to the request.
     *
     * @member gadgets.io
     */
    makeRequest: function(url, callback, opt_params) {
      // TODO: This method also needs to respect all members of
      // gadgets.io.RequestParameters, and validate them.

      var params = opt_params || {};

      var httpMethod = params['METHOD'] || 'GET';
      var refreshInterval = params['REFRESH_INTERVAL'];

      // Check if authorization is requested
      var auth, st;
      if (params['AUTHORIZATION'] && params['AUTHORIZATION'] !== 'NONE') {
        auth = params['AUTHORIZATION'].toLowerCase();
        st = shindig.auth.getSecurityToken();
      } else {
        // Unauthenticated GET requests are cacheable
        if (httpMethod === 'GET' && refreshInterval === undefined) {
          refreshInterval = 3600;
        }
      }

      // Include owner information?
      var signOwner = true;
      if (typeof params['OWNER_SIGNED'] !== 'undefined') {
        signOwner = params['OWNER_SIGNED'];
      }

      // Include viewer information?
      var signViewer = true;
      if (typeof params['VIEWER_SIGNED'] !== 'undefined') {
        signViewer = params['VIEWER_SIGNED'];
      }

      var headers = params['HEADERS'] || {};
      if (httpMethod === 'POST' && !headers['Content-Type']) {
        headers['Content-Type'] = 'application/x-www-form-urlencoded';
      }

      var urlParams = gadgets.util.getUrlParameters();

      var paramData = {
        'url': url,
        'httpMethod': httpMethod,
        'headers': gadgets.io.encodeValues(headers, false),
        'postData': params['POST_DATA'] || '',
        'authz': auth || '',
        'st': st || '',
        'contentType': params['CONTENT_TYPE'] || 'TEXT',
        'numEntries': params['NUM_ENTRIES'] || '3',
        'getSummaries': !!params['GET_SUMMARIES'],
        'signOwner': signOwner,
        'signViewer': signViewer,
        'gadget': urlParams['url'],
        'container': urlParams['container'] || urlParams['synd'] || 'default',
        // should we bypass gadget spec cache (e.g. to read OAuth provider URLs)
        'bypassSpecCache': gadgets.util.getUrlParameters()['nocache'] || '',
        'getFullHeaders': !!params['GET_FULL_HEADERS']
      };

      // OAuth goodies
      if (auth === 'oauth' || auth === 'signed') {
        if (gadgets.io.oauthReceivedCallbackUrl_) {
          paramData['OAUTH_RECEIVED_CALLBACK'] = gadgets.io.oauthReceivedCallbackUrl_;
          gadgets.io.oauthReceivedCallbackUrl_ = null;
        }
        paramData['oauthState'] = oauthState || '';
        // Just copy the OAuth parameters into the req to the server
        for (var opt in params) {
          if (params.hasOwnProperty(opt)) {
            if (opt.indexOf('OAUTH_') === 0) {
              paramData[opt] = params[opt];
            }
          }
        }
      }

      var proxyUrl = config['jsonProxyUrl'].replace('%host%', document.location.host);

      // FIXME -- processResponse is not used in call
      if (!respondWithPreload(paramData, params, callback)) {
        if (httpMethod === 'GET' && refreshInterval > 0) {
          // this content should be cached
          // Add paramData to the URL
          var extraparams = '?refresh=' + refreshInterval + '&'
              + gadgets.io.encodeValues(paramData);

          makeXhrRequest(url, proxyUrl + extraparams, callback,
              null, 'GET', params, processResponse);

        } else {
          makeXhrRequest(url, proxyUrl, callback,
              gadgets.io.encodeValues(paramData), 'POST', params,
              processResponse);
        }
      }
    },

    /**
     * @param {string} relativeUrl url to fetch via xhr
     * @param callback callback to call when response is received or for error
     * @param {Object=} opt_params
     * @param {Object=} opt_headers
     *
     */
    makeNonProxiedRequest: function(relativeUrl, callback, opt_params, opt_headers) {
      var params = opt_params || {};
      makeXhrRequest(relativeUrl, relativeUrl, callback, params['POST_DATA'],
          params['METHOD'], params, processNonProxiedResponse, opt_headers);
    },

    /**
     * Used to clear out the oauthState, for testing only.
     *
     * @private
     */
    clearOAuthState: function() {
      oauthState = undefined;
    },

    /**
     * Converts an input object into a URL-encoded data string.
     * (key=value&amp;...)
     *
     * @param {Object} fields The post fields you wish to encode.
     * @param {boolean=} opt_noEscaping An optional parameter specifying whether
     *     to turn off escaping of the parameters. Defaults to false.
     * @return {string} The processed post data in www-form-urlencoded format.
     *
     * @member gadgets.io
     */
    encodeValues: function(fields, opt_noEscaping) {
      var escape = !opt_noEscaping;

      var buf = [];
      var first = false;
      for (var i in fields) {
        if (fields.hasOwnProperty(i) && !/___$/.test(i)) {
          if (!first) {
            first = true;
          } else {
            buf.push('&');
          }
          buf.push(escape ? encodeURIComponent(String(i)) : i);
          buf.push('=');
          buf.push(escape ? encodeURIComponent(String(fields[i])) : fields[i]);
        }
      }
      return buf.join('');
    },

    /**
     * Gets the proxy version of the passed-in URL.
     *
     * @param {string} url The URL to get the proxy URL for.
     * @param {Object.<string, Object>=} opt_params Optional Parameter Object.
     *     The following properties are supported:
     *       .REFRESH_INTERVAL The number of seconds that this
     *           content should be cached.  Defaults to 3600.
     *
     * @return {string} The proxied version of the URL.
     * @member gadgets.io
     */
    getProxyUrl: function(url, opt_params) {
      var params = opt_params || {};
      var refresh = params['REFRESH_INTERVAL'];
      if (refresh === undefined) {
        refresh = '3600';
      }

      var urlParams = gadgets.util.getUrlParameters();

      var rewriteMimeParam =
          params['rewriteMime'] ? '&rewriteMime=' + encodeURIComponent(String(params['rewriteMime'])) : '';
      var ret = config['proxyUrl'].replace('%url%', encodeURIComponent(url)).
          replace('%host%', document.location.host).
          replace('%rawurl%', url).
          replace('%refresh%', encodeURIComponent(String(refresh))).
          replace('%gadget%', encodeURIComponent(urlParams['url'])).
          replace('%container%', encodeURIComponent(urlParams['container'] || urlParams['synd'] || 'default')).
          replace('%rewriteMime%', rewriteMimeParam);
      if (ret.indexOf('//') == 0) {
        ret = window.location.protocol + ret;
      }
      return ret;
    }
  };
}();

/**
 * @const
 */
gadgets.io.RequestParameters = gadgets.util.makeEnum([
  'METHOD',
  'CONTENT_TYPE',
  'POST_DATA',
  'HEADERS',
  'AUTHORIZATION',
  'NUM_ENTRIES',
  'GET_SUMMARIES',
  'GET_FULL_HEADERS',
  'REFRESH_INTERVAL',
  'OAUTH_SERVICE_NAME',
  'OAUTH_USE_TOKEN',
  'OAUTH_TOKEN_NAME',
  'OAUTH_REQUEST_TOKEN',
  'OAUTH_REQUEST_TOKEN_SECRET',
  'OAUTH_RECEIVED_CALLBACK'
]);

/**
 * @const
 */
gadgets.io.MethodType = gadgets.util.makeEnum([
  'GET', 'POST', 'PUT', 'DELETE', 'HEAD'
]);

/**
 * @const
 */
gadgets.io.ContentType = gadgets.util.makeEnum([
  'TEXT', 'DOM', 'JSON', 'FEED'
]);

/**
 * @const
 */
gadgets.io.AuthorizationType = gadgets.util.makeEnum([
  'NONE', 'SIGNED', 'OAUTH'
]);
;

/* [end] feature=core.io */

/* [start] feature=core.prefs */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview
 *
 * Provides access to user prefs, module dimensions, and messages.
 *
 * <p>Clients can access their preferences by constructing an instance of
 * gadgets.Prefs and passing in their module id.  Example:
 *
 * <pre>
 *   var prefs = new gadgets.Prefs();
 *   var name = prefs.getString("name");
 *   var lang = prefs.getLang();
 * </pre>
 *
 * <p>Modules with type=url can also use this library to parse arguments passed
 * by URL, but this is not the common case:
 *
 *   &lt;script src="http://apache.org/shindig/prefs.js"&gt;&lt;/script&gt;
 *   &lt;script&gt;
 *   gadgets.Prefs.parseUrl();
 *   var prefs = new gadgets.Prefs();
 *   var name = prefs.getString("name");
 *   &lt;/script&gt;
 */

(function() {

  var instance = null;
  var prefs = {};
  var esc = gadgets.util.escapeString;
  var messages = {};
  var defaultPrefs = {};
  var language = 'en';
  var country = 'US';
  var moduleId = 0;

  /**
 * Parses all parameters from the url and stores them
 * for later use when creating a new gadgets.Prefs object.
 */
  function parseUrl() {
    var params = gadgets.util.getUrlParameters();
    for (var i in params) {
      if (params.hasOwnProperty(i)) {
        if (i.indexOf('up_') === 0 && i.length > 3) {
          prefs[i.substr(3)] = String(params[i]);
        } else if (i === 'country') {
          country = params[i];
        } else if (i === 'lang') {
          language = params[i];
        } else if (i === 'mid') {
          moduleId = params[i];
        }
      }
    }
  }

  /**
 * Sets default pref values for values left unspecified in the
 * rendering call, but with default_value provided in the spec.
 */
  function mergeDefaults() {
    for (var name in defaultPrefs) {
      if (typeof prefs[name] === 'undefined') {
        prefs[name] = defaultPrefs[name];
      }
    }
  }

  /**
 * @class
 * Provides access to user preferences, module dimensions, and messages.
 *
 * Clients can access their preferences by constructing an instance of
 * gadgets.Prefs and passing in their module id.  Example:
 *
<pre>var prefs = new gadgets.Prefs();
var name = prefs.getString("name");
var lang = prefs.getLang();</pre>
 *
 * @description Creates a new Prefs object.
 *
 * Note: this is actually a singleton. All prefs are linked. If you're wondering
 * why this is a singleton and not just a collection of package functions, the
 * simple answer is that it's how the spec is written. The spec is written this
 * way for legacy compatibility with igoogle.
 */
  gadgets.Prefs = function() {
    if (!instance) {
      parseUrl();
      mergeDefaults();
      instance = this;
    }
    return instance;
  };

  /**
 * Sets internal values
 * @return {boolean} True if the prefs is modified.
 */
  gadgets.Prefs.setInternal_ = function(key, value) {
    var wasModified = false;
    if (typeof key === 'string') {
      if (!prefs.hasOwnProperty(key) || prefs[key] !== value) {
        wasModified = true;
      }
      prefs[key] = value;
    } else {
      for (var k in key) {
        if (key.hasOwnProperty(k)) {
          var v = key[k];
          if (!prefs.hasOwnProperty(k) || prefs[k] !== v) {
            wasModified = true;
          }
          prefs[k] = v;
        }
      }
    }
    return wasModified;
  };

  /**
 * Initializes message bundles.
 */
  gadgets.Prefs.setMessages_ = function(msgs) {
    messages = msgs;
  };

  /**
 * Initializes default user prefs values.
 */
  gadgets.Prefs.setDefaultPrefs_ = function(defprefs) {
    defaultPrefs = defprefs;
  };

  /**
 * Retrieves a preference as a string.
 * Returned value will be html entity escaped.
 *
 * @param {string} key The preference to fetch.
 * @return {string} The preference; if not set, an empty string.
 */
  gadgets.Prefs.prototype.getString = function(key) {
    if (key === '.lang') { key = 'lang'; }
    return prefs[key] ? esc(prefs[key]) : '';
  };

  /*
 * Indicates not to escape string values when retrieving them.
 * This is an internal detail used by _IG_Prefs for backward compatibility.
 */
  gadgets.Prefs.prototype.setDontEscape_ = function() {
    esc = function(k) { return k; };
  };

  /**
 * Retrieves a preference as an integer.
 * @param {string} key The preference to fetch.
 * @return {number} The preference; if not set, 0.
 */
  gadgets.Prefs.prototype.getInt = function(key) {
    var val = parseInt(prefs[key], 10);
    return isNaN(val) ? 0 : val;
  };

  /**
 * Retrieves a preference as a floating-point value.
 * @param {string} key The preference to fetch.
 * @return {number} The preference; if not set, 0.
 */
  gadgets.Prefs.prototype.getFloat = function(key) {
    var val = parseFloat(prefs[key]);
    return isNaN(val) ? 0 : val;
  };

  /**
 * Retrieves a preference as a boolean.
 * @param {string} key The preference to fetch.
 * @return {boolean} The preference; if not set, false.
 */
  gadgets.Prefs.prototype.getBool = function(key) {
    var val = prefs[key];
    if (val) {
      return val === 'true' || val === true || !!parseInt(val, 10);
    }
    return false;
  };

  /**
 * Stores a preference.
 * To use this call,
 * the gadget must require the feature setprefs.
 *
 * <p class="note">
 * <b>Note:</b>
 * If the gadget needs to store an Array it should use setArray instead of
 * this call.
 * </p>
 *
 * @param {string} key The pref to store.
 * @param {Object} val The values to store.
 */
  gadgets.Prefs.prototype.set = function(key, value) {
    throw new Error('setprefs feature required to make this call.');
  };

  /**
 * Retrieves a preference as an array.
 * UserPref values that were not declared as lists are treated as
 * one-element arrays.
 *
 * @param {string} key The preference to fetch.
 * @return {Array.<string>} The preference; if not set, an empty array.
 */
  gadgets.Prefs.prototype.getArray = function(key) {
    var val = prefs[key];
    if (val) {
      var arr = val.split('|');
      // Decode pipe characters.
      for (var i = 0, j = arr.length; i < j; ++i) {
        arr[i] = esc(arr[i].replace(/%7C/g, '|'));
      }
      return arr;
    }
    return [];
  };

  /**
 * Stores an array preference.
 * To use this call,
 * the gadget must require the feature setprefs.
 *
 * @param {string} key The pref to store.
 * @param {Array} val The values to store.
 */
  gadgets.Prefs.prototype.setArray = function(key, val) {
    throw new Error('setprefs feature required to make this call.');
  };

  /**
 * Fetches an unformatted message.
 * @param {string} key The message to fetch.
 * @return {string} The message.
 */
  gadgets.Prefs.prototype.getMsg = function(key) {
    return messages[key] || '';
  };

  /**
 * Gets the current country, returned as ISO 3166-1 alpha-2 code.
 *
 * @return {string} The country for this module instance.
 */
  gadgets.Prefs.prototype.getCountry = function() {
    return country;
  };

  /**
 * Gets the current language the gadget should use when rendering, returned as a
 * ISO 639-1 language code.
 *
 * @return {string} The language for this module instance.
 */
  gadgets.Prefs.prototype.getLang = function() {
    return language;
  };

  /**
 * Gets the module id for the current instance.
 *
 * @return {string | number} The module id for this module instance.
 */
  gadgets.Prefs.prototype.getModuleId = function() {
    return moduleId;
  };

})();
;

/* [end] feature=core.prefs */

/* [start] feature=core.legacy */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview All functions in this file should be treated as deprecated legacy routines.
 * Gadget authors are explicitly discouraged from using any of them.
 */

var JSON = window.JSON || gadgets.json;

/**
 * @deprecated
 */
var _IG_Prefs = (function() {
  var instance = null;

  var _IG_Prefs = function() {
    if (!instance) {
      instance = new gadgets.Prefs();
      instance.setDontEscape_();
    }
    return instance;
  };

  _IG_Prefs._parseURL = gadgets.Prefs.parseUrl;

  return _IG_Prefs;
})();

function _IG_Fetch_wrapper(callback, obj) {
  callback(obj.data ? obj.data : '');
}

/**
 * @deprecated
 */
function _IG_FetchContent(url, callback, opt_params) {
  var params = opt_params || {};
  // This is really the only legacy parameter documented
  // at http://code.google.com/apis/gadgets/docs/remote-content.html#Params
  if (params.refreshInterval) {
    params['REFRESH_INTERVAL'] = params.refreshInterval;
  } else {
    params['REFRESH_INTERVAL'] = 3600;
  }
  // Other params, such as POST_DATA, were supported in lower case.
  // Upper-case all param keys as a convenience, since all valid values
  // are uppercased.
  for (var param in params) {
    var pvalue = params[param];
    delete params[param];
    params[param.toUpperCase()] = pvalue;
  }
  var cb = gadgets.util.makeClosure(null, _IG_Fetch_wrapper, callback);
  gadgets.io.makeRequest(url, cb, params);
}

/**
 * @deprecated
 */
function _IG_FetchXmlContent(url, callback, opt_params) {
  var params = opt_params || {};
  if (params.refreshInterval) {
    params['REFRESH_INTERVAL'] = params.refreshInterval;
  } else {
    params['REFRESH_INTERVAL'] = 3600;
  }
  params.CONTENT_TYPE = 'DOM';
  var cb = gadgets.util.makeClosure(null, _IG_Fetch_wrapper, callback);
  gadgets.io.makeRequest(url, cb, params);
}


/**
 * @deprecated
 */
function _IG_FetchFeedAsJSON(url, callback, numItems, getDescriptions,
                             opt_params) {
  var params = opt_params || {};
  params.CONTENT_TYPE = 'FEED';
  params.NUM_ENTRIES = numItems;
  params.GET_SUMMARIES = getDescriptions;
  gadgets.io.makeRequest(url,
      function(resp) {
        // special case error reporting for back-compatibility
        // see http://code.google.com/apis/gadgets/docs/legacy/remote-content.html#Fetch_JSON
        resp.data = resp.data || {};
        if (resp.errors && resp.errors.length > 0) {
          resp.data.ErrorMsg = resp.errors[0];
        }
        if (resp.data.link) {
          resp.data.URL = url;
        }
        if (resp.data.title) {
          resp.data.Title = resp.data.title;
        }
        if (resp.data.description) {
          resp.data.Description = resp.data.description;
        }
        if (resp.data.link) {
          resp.data.Link = resp.data.link;
        }
        if (resp.data.items && resp.data.items.length > 0) {
          resp.data.Entry = resp.data.items;
          for (var index = 0; index < resp.data.Entry.length; ++index) {
            var entry = resp.data.Entry[index];
            entry.Title = entry.title;
            entry.Link = entry.link;
            entry.Summary = entry.summary || entry.description;
            entry.Date = entry.pubDate;
          }
        }
        for (var ix = 0; ix < resp.data.Entry.length; ++ix) {
          var entry = resp.data.Entry[ix];
          entry.Date = (entry.Date / 1000);  // response in sec, not ms
        }
        // for Gadgets back-compatibility, return the feed obj directly
        callback(resp.data);
      }, params);
}

/**
 * @param {string} url
 * @param {Object=} opt_params
 * @deprecated
 */
function _IG_GetCachedUrl(url, opt_params) {
  var params = opt_params || {};
  params['REFRESH_INTERVAL'] = 3600;
  if (params.refreshInterval) {
    params['REFRESH_INTERVAL'] = params.refreshInterval;
  }
  return gadgets.io.getProxyUrl(url, params);
}
/**
 * @param {string} url
 * @param {Object=} opt_params
 * @deprecated
 */
function _IG_GetImageUrl(url, opt_params) {
  return _IG_GetCachedUrl(url, opt_params);
}

/**
 * @param {string} url
 * @return {Element}
 * @deprecated
 */
function _IG_GetImage(url) {
  var img = document.createElement('img');
  img.src = _IG_GetCachedUrl(url);
  return img;
}


/**
 * @deprecated
 */
function _IG_RegisterOnloadHandler(callback) {
  gadgets.util.registerOnLoadHandler(callback);
}

/**
 * _IG_Callback takes the arguments in the scope the callback is executed and
 * places them first in the argument array. MakeClosure takes the arguments
 * from the scope at callback construction and pushes them first in the array
 *
 * @deprecated
 */
function _IG_Callback(handler_func, var_args) {
  var orig_args = arguments;
  return function() {
    var combined_args = Array.prototype.slice.call(arguments);
    // call the handler with all args combined
    handler_func.apply(null,
        combined_args.concat(Array.prototype.slice.call(orig_args, 1)));
  };
}

var _args = gadgets.util.getUrlParameters;

/**
 * Fetches an object by document id.
 *
 * @param {string | Object} el The element you wish to fetch. You may pass
 *     an object in which allows this to be called regardless of whether or
 *     not the type of the input is known.
 * @return {HTMLElement} The element, if it exists in the document, or null.
 * @deprecated
 */
function _gel(el) {
  return document.getElementById ? document.getElementById(el) : null;
}

/**
 * Fetches elements by tag name.
 * This is functionally identical to document.getElementsByTagName()
 *
 * @param {string} tag The tag to match elements against.
 * @return {Array.<HTMLElement>} All elements of this tag type.
 * @deprecated
 */
function _gelstn(tag) {
  if (tag === '*' && document.all) {
    return document.all;
  }
  return document.getElementsByTagName ?
         document.getElementsByTagName(tag) : [];
}

/**
 * Fetches elements with ids matching a given regular expression.
 *
 * @param {string} tagName The tag to match elements against.
 * @param {RegEx} regex The expression to match.
 * @return {Array.<HTMLElement>} All elements of this tag type that match
 *     regex.
 * @deprecated
 */
function _gelsbyregex(tagName, regex) {
  var matchingTags = _gelstn(tagName);
  var matchingRegex = [];
  for (var i = 0, j = matchingTags.length; i < j; ++i) {
    if (regex.test(matchingTags[i].id)) {
      matchingRegex.push(matchingTags[i]);
    }
  }
  return matchingRegex;
}

/**
 * URI escapes the given string.
 * @param {string} str The string to escape.
 * @return {string} The escaped string.
 * @deprecated
 */
function _esc(str) {
  return window.encodeURIComponent ? encodeURIComponent(str) : escape(str);
}

/**
 * URI unescapes the given string.
 * @param {string} str The string to unescape.
 * @return {string} The unescaped string.
 * @deprecated
 */
function _unesc(str) {
  return window.decodeURIComponent ? decodeURIComponent(str) : unescape(str);
}

/**
 * Encodes HTML entities such as <, " and >.
 *
 * @param {string} str The string to escape.
 * @return {string} The escaped string.
 * @deprecated
 */
function _hesc(str) {
  return gadgets.util.escapeString(str);
}

/**
 * Removes HTML tags from the given input string.
 *
 * @param {string} str The string to strip.
 * @return {string} The stripped string.
 * @deprecated
 */
function _striptags(str) {
  return str.replace(/<\/?[^>]+>/g, '');
}

/**
 * Trims leading & trailing whitespace from the given string.
 *
 * @param {string} str The string to trim.
 * @return {string} The trimmed string.
 * @deprecated
 */
function _trim(str) {
  return str.replace(/^\s+|\s+$/g, '');
}

/**
 * Toggles the given element between being shown and block-style display.
 *
 * @param {string | HTMLElement} el The element to toggle.
 * @deprecated
 */
function _toggle(el) {
  el = (typeof el === 'string') ? _gel(el) : el;
  if (el !== null) {
    if (el.style.display.length === 0 || el.style.display === 'block') {
      el.style.display = 'none';
    } else if (el.style.display === 'none') {
      el.style.display = 'block';
    }
  }
}


var _uid = (function() {
  /**
   * @type {number} A counter used by uniqueId().
   */
  var _legacy_uidCounter = 0;

  /**
   * @return {number} a unique number.
   * @deprecated
   */
  return function() {
    return _legacy_uidCounter++;
  };
})();

/**
 * @param {number} a
 * @param {number} b
 * @return {number} The lesser of a or b.
 * @deprecated
 */
function _min(a, b) {
  return (a < b ? a : b);
}

/**
 * @param {number} a
 * @param {number} b
 * @return {number} The greater of a or b.
 * @deprecated
 */
function _max(a, b) {
  return (a > b ? a : b);
}

/**
 * @param {string} name
 * @param {Array.<string | Object>} sym
 * @deprecated
 */
function _exportSymbols(name, sym) {
  var attach = window;
  var parts = name.split('.');
  for (var i = 0, j = parts.length; i < j; i++) {
    var part = parts[i];
    attach[part] = attach[part] || {};
    attach = attach[part];
  }
  for (var k = 0, l = sym.length; k < l; k += 2) {
    attach[sym[k]] = sym[k + 1];
  }
}

/**
 * @deprecated
 * @param {Object} src
 * @param {string} etype
 * @param {function()} func
 * TODO - implement.
 */
function _IG_AddDOMEventHandler(src, etype, func) {
  gadgets.warn('_IG_AddDOMEventHandler not implemented - see SHINDIG-198');
}

;

/* [end] feature=core.legacy */

/* [start] feature=core.tracking */
// Input 0
(function() {
  function errorHandler(msg, url, line) {
    var error = {msg:msg || "", line:line || 0, jsUrl:0 == window.location.href.indexOf(url) ? "-top-" : url, fullUrl:window.location};
    try {
      sendError(error)
    }catch(e) {
    }
  }
  function sendError(error) {
    ++window["_varz_numerrors"];
    if(gadgets && !(3 < errorsSent)) {
      var params = {};
      params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
      var escFn = encodeURIComponent || escape, urlParams = gadgets.util.getUrlParameters(), containerParam = urlParams.container || urlParams.synd, gadgetParam = urlParams.gadget || urlParams.url, errorTarget = ["/gadgets/evthdlr?t=err&gadget=", escFn(gadgetParam), "&container=", escFn(containerParam), "&jsurl=", escFn(error.jsUrl), "&line=", escFn(error.line), "&session=", SESSION_ID, "&count=", errorsSent, "&msg=", escFn(error.msg)];
      gadgets.io.makeNonProxiedRequest(errorTarget.join(""), null, params);
      errorsSent++
    }
  }
  window["_varz_numerrors"] = 0;
  var SESSION_ID = (new Date).getTime(), errorsSent = 0;
  -1 == window.location.href.indexOf("&debug=1") && -1 == window.location.href.indexOf("?debug=1") && (window.onerror = errorHandler)
})();

;

/* [end] feature=core.tracking */

/* [start] feature=core */
{var html4={},html,html_sanitize;html4.atype={'NONE':0,'URI':1,'URI_FRAGMENT':11,'SCRIPT':2,'STYLE':3,'ID':4,'IDREF':5,'IDREFS':6,'GLOBAL_NAME':7,'LOCAL_NAME':8,'CLASSES':9,'FRAME_TARGET':10},html4.ATTRIBS={'*::class':9,'*::dir':0,'*::id':4,'*::lang':0,'*::onclick':2,'*::ondblclick':2,'*::onkeydown':2,'*::onkeypress':2,'*::onkeyup':2,'*::onload':2,'*::onmousedown':2,'*::onmousemove':2,'*::onmouseout':2,'*::onmouseover':2,'*::onmouseup':2,'*::style':3,'*::title':0,'a::accesskey':0,'a::coords':0,'a::href':1,'a::hreflang':0,'a::name':7,'a::onblur':2,'a::onfocus':2,'a::rel':0,'a::rev':0,'a::shape':0,'a::tabindex':0,'a::target':10,'a::type':0,'area::accesskey':0,'area::alt':0,'area::coords':0,'area::href':1,'area::nohref':0,'area::onblur':2,'area::onfocus':2,'area::shape':0,'area::tabindex':0,'area::target':10,'bdo::dir':0,'blockquote::cite':1,'br::clear':0,'button::accesskey':0,'button::disabled':0,'button::name':8,'button::onblur':2,'button::onfocus':2,'button::tabindex':0,'button::type':0,'button::value':0,'canvas::height':0,'canvas::width':0,'caption::align':0,'col::align':0,'col::char':0,'col::charoff':0,'col::span':0,'col::valign':0,'col::width':0,'colgroup::align':0,'colgroup::char':0,'colgroup::charoff':0,'colgroup::span':0,'colgroup::valign':0,'colgroup::width':0,'del::cite':1,'del::datetime':0,'dir::compact':0,'div::align':0,'dl::compact':0,'font::color':0,'font::face':0,'font::size':0,'form::accept':0,'form::action':1,'form::autocomplete':0,'form::enctype':0,'form::method':0,'form::name':7,'form::onreset':2,'form::onsubmit':2,'form::target':10,'h1::align':0,'h2::align':0,'h3::align':0,'h4::align':0,'h5::align':0,'h6::align':0,'hr::align':0,'hr::noshade':0,'hr::size':0,'hr::width':0,'iframe::align':0,'iframe::frameborder':0,'iframe::height':0,'iframe::marginheight':0,'iframe::marginwidth':0,'iframe::width':0,'img::align':0,'img::alt':0,'img::border':0,'img::height':0,'img::hspace':0,'img::ismap':0,'img::name':7,'img::src':1,'img::usemap':11,'img::vspace':0,'img::width':0,'input::accept':0,'input::accesskey':0,'input::align':0,'input::alt':0,'input::autocomplete':0,'input::checked':0,'input::disabled':0,'input::ismap':0,'input::maxlength':0,'input::name':8,'input::onblur':2,'input::onchange':2,'input::onfocus':2,'input::onselect':2,'input::readonly':0,'input::size':0,'input::src':1,'input::tabindex':0,'input::type':0,'input::usemap':11,'input::value':0,'ins::cite':1,'ins::datetime':0,'label::accesskey':0,'label::for':5,'label::onblur':2,'label::onfocus':2,'legend::accesskey':0,'legend::align':0,'li::type':0,'li::value':0,'map::name':7,'menu::compact':0,'ol::compact':0,'ol::start':0,'ol::type':0,'optgroup::disabled':0,'optgroup::label':0,'option::disabled':0,'option::label':0,'option::selected':0,'option::value':0,'p::align':0,'pre::width':0,'q::cite':1,'select::disabled':0,'select::multiple':0,'select::name':8,'select::onblur':2,'select::onchange':2,'select::onfocus':2,'select::size':0,'select::tabindex':0,'table::align':0,'table::bgcolor':0,'table::border':0,'table::cellpadding':0,'table::cellspacing':0,'table::frame':0,'table::rules':0,'table::summary':0,'table::width':0,'tbody::align':0,'tbody::char':0,'tbody::charoff':0,'tbody::valign':0,'td::abbr':0,'td::align':0,'td::axis':0,'td::bgcolor':0,'td::char':0,'td::charoff':0,'td::colspan':0,'td::headers':6,'td::height':0,'td::nowrap':0,'td::rowspan':0,'td::scope':0,'td::valign':0,'td::width':0,'textarea::accesskey':0,'textarea::cols':0,'textarea::disabled':0,'textarea::name':8,'textarea::onblur':2,'textarea::onchange':2,'textarea::onfocus':2,'textarea::onselect':2,'textarea::readonly':0,'textarea::rows':0,'textarea::tabindex':0,'tfoot::align':0,'tfoot::char':0,'tfoot::charoff':0,'tfoot::valign':0,'th::abbr':0,'th::align':0,'th::axis':0,'th::bgcolor':0,'th::char':0,'th::charoff':0,'th::colspan':0,'th::headers':6,'th::height':0,'th::nowrap':0,'th::rowspan':0,'th::scope':0,'th::valign':0,'th::width':0,'thead::align':0,'thead::char':0,'thead::charoff':0,'thead::valign':0,'tr::align':0,'tr::bgcolor':0,'tr::char':0,'tr::charoff':0,'tr::valign':0,'ul::compact':0,'ul::type':0},html4.eflags={'OPTIONAL_ENDTAG':1,'EMPTY':2,'CDATA':4,'RCDATA':8,'UNSAFE':16,'FOLDABLE':32,'SCRIPT':64,'STYLE':128},html4.ELEMENTS={'a':0,'abbr':0,'acronym':0,'address':0,'applet':16,'area':2,'b':0,'base':18,'basefont':18,'bdo':0,'big':0,'blockquote':0,'body':49,'br':2,'button':0,'canvas':0,'caption':0,'center':0,'cite':0,'code':0,'col':2,'colgroup':1,'dd':1,'del':0,'dfn':0,'dir':0,'div':0,'dl':0,'dt':1,'em':0,'fieldset':0,'font':0,'form':0,'frame':18,'frameset':16,'h1':0,'h2':0,'h3':0,'h4':0,'h5':0,'h6':0,'head':49,'hr':2,'html':49,'i':0,'iframe':4,'img':2,'input':2,'ins':0,'isindex':18,'kbd':0,'label':0,'legend':0,'li':1,'link':18,'map':0,'menu':0,'meta':18,'nobr':0,'noembed':4,'noframes':20,'noscript':20,'object':16,'ol':0,'optgroup':0,'option':1,'p':1,'param':18,'pre':0,'q':0,'s':0,'samp':0,'script':84,'select':0,'small':0,'span':0,'strike':0,'strong':0,'style':148,'sub':0,'sup':0,'table':0,'tbody':1,'td':1,'textarea':8,'tfoot':1,'th':1,'thead':1,'title':24,'tr':1,'tt':0,'u':0,'ul':0,'var':0},html4.ueffects={'NOT_LOADED':0,'SAME_DOCUMENT':1,'NEW_DOCUMENT':2},html4.URIEFFECTS={'a::href':2,'area::href':2,'blockquote::cite':0,'body::background':1,'del::cite':0,'form::action':2,'img::src':1,'input::src':1,'ins::cite':0,'q::cite':0},html4.ltypes={'UNSANDBOXED':2,'SANDBOXED':1,'DATA':0},html4.LOADERTYPES={'a::href':2,'area::href':2,'blockquote::cite':2,'body::background':1,'del::cite':2,'form::action':2,'img::src':1,'input::src':1,'ins::cite':2,'q::cite':2},html=(function(html4){var
ENTITIES,INSIDE_TAG_TOKEN,OUTSIDE_TAG_TOKEN,URI_SCHEME_RE,WHITELISTED_SCHEMES,ampRe,decimalEscapeRe,entityRe,gtRe,hexEscapeRe,lcase,looseAmpRe,ltRe,nulRe,quotRe;'script'==='SCRIPT'.toLowerCase()?(lcase=function(s){return s.toLowerCase()}):(lcase=function(s){return s.replace(/[A-Z]/g,function(ch){return String.fromCharCode(ch.charCodeAt(0)|32)})}),ENTITIES={'lt':'<','gt':'>','amp':'&','nbsp':'\xa0','quot':'\"','apos':'\''},WHITELISTED_SCHEMES=/^(?:https?|mailto)$/i,decimalEscapeRe=/^#(\d+)$/,hexEscapeRe=/^#x([0-9A-Fa-f]+)$/;function
lookupEntity(name){var m;return name=lcase(name),ENTITIES.hasOwnProperty(name)?ENTITIES[name]:(m=name.match(decimalEscapeRe),m?String.fromCharCode(parseInt(m[1],10)):(m=name.match(hexEscapeRe))?String.fromCharCode(parseInt(m[1],16)):'')}function
decodeOneEntity(_,name){return lookupEntity(name)}nulRe=/\0/g;function stripNULs(s){return s.replace(nulRe,'')}entityRe=/&(#\d+|#x[0-9A-Fa-f]+|\w+);/g;function
unescapeEntities(s){return s.replace(entityRe,decodeOneEntity)}ampRe=/&/g,looseAmpRe=/&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi,ltRe=/</g,gtRe=/>/g,quotRe=/\"/g;function
escapeAttrib(s){return(''+s).replace(ampRe,'&amp;').replace(ltRe,'&lt;').replace(gtRe,'&gt;').replace(quotRe,'&#34;')}function
normalizeRCData(rcdata){return rcdata.replace(looseAmpRe,'&amp;$1').replace(ltRe,'&lt;').replace(gtRe,'&gt;')}INSIDE_TAG_TOKEN=new
RegExp('^\\s*(?:(?:([a-z][a-z-]*)(\\s*=\\s*(\"[^\"]*\"|\'[^\']*\'|(?=[a-z][a-z-]*\\s*=)|[^>\"\'\\s]*))?)|(/?>)|[\\s\\S][^a-z\\s>]*)','i'),OUTSIDE_TAG_TOKEN=new
RegExp('^(?:&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);|<!--[\\s\\S]*?-->|<!\\w[^>]*>|<\\?[^>*]*>|<(/)?([a-z][a-z0-9]*)|([^<&>]+)|([<&>]))','i');function
makeSaxParser(handler){return function parse(htmlText,param){var attribName,attribs,dataEnd,decodedValue,eflags,encodedValue,htmlLower,inTag,m,openTag,tagName;htmlText=String(htmlText),htmlLower=null,inTag=false,attribs=[],tagName=void
0,eflags=void 0,openTag=void 0,handler.startDoc&&handler.startDoc(param);while(htmlText){m=htmlText.match(inTag?INSIDE_TAG_TOKEN:OUTSIDE_TAG_TOKEN),htmlText=htmlText.substring(m[0].length);if(inTag){if(m[1]){attribName=lcase(m[1]);if(m[2]){encodedValue=m[3];switch(encodedValue.charCodeAt(0)){case
34:case 39:encodedValue=encodedValue.substring(1,encodedValue.length-1)}decodedValue=unescapeEntities(stripNULs(encodedValue))}else
decodedValue=attribName;attribs.push(attribName,decodedValue)}else if(m[4])eflags!==void
0&&(openTag?handler.startTag&&handler.startTag(tagName,attribs,param):handler.endTag&&handler.endTag(tagName,param)),openTag&&eflags&(html4.eflags.CDATA|html4.eflags.RCDATA)&&(htmlLower===null?(htmlLower=lcase(htmlText)):(htmlLower=htmlLower.substring(htmlLower.length-htmlText.length)),dataEnd=htmlLower.indexOf('</'+tagName),dataEnd<0&&(dataEnd=htmlText.length),dataEnd&&(eflags&html4.eflags.CDATA?handler.cdata&&handler.cdata(htmlText.substring(0,dataEnd),param):handler.rcdata&&handler.rcdata(normalizeRCData(htmlText.substring(0,dataEnd)),param),htmlText=htmlText.substring(dataEnd))),tagName=eflags=openTag=void
0,attribs.length=0,inTag=false}else if(m[1])handler.pcdata&&handler.pcdata(m[0],param);else
if(m[3])openTag=!m[2],inTag=true,tagName=lcase(m[3]),eflags=html4.ELEMENTS.hasOwnProperty(tagName)?html4.ELEMENTS[tagName]:void
0;else if(m[4])handler.pcdata&&handler.pcdata(m[4],param);else if(m[5]){if(handler.pcdata)switch(m[5]){case'<':handler.pcdata('&lt;',param);break;case'>':handler.pcdata('&gt;',param);break;case'&':handler.pcdata('&amp;',param)}}}handler.endDoc&&handler.endDoc(param)}}function
makeHtmlSanitizer(tagPolicy){var ignoring,stack;return makeSaxParser({'startDoc':function(_){stack=[],ignoring=false},'startTag':function(tagName,attribs,out){var
attribName,eflags,i,n,value;if(ignoring)return;if(!html4.ELEMENTS.hasOwnProperty(tagName))return;eflags=html4.ELEMENTS[tagName];if(eflags&html4.eflags.FOLDABLE)return;attribs=tagPolicy(tagName,attribs);if(!attribs)return ignoring=!(eflags&html4.eflags.EMPTY),void
0;eflags&html4.eflags.EMPTY||stack.push(tagName),out.push('<',tagName);for(i=0,n=attribs.length;i<n;i+=2)attribName=attribs[i],value=attribs[i+1],value!==null&&value!==void
0&&out.push(' ',attribName,'=\"',escapeAttrib(value),'\"');out.push('>')},'endTag':function(tagName,out){var
eflags,i,index,stackEl;if(ignoring)return ignoring=false,void 0;if(!html4.ELEMENTS.hasOwnProperty(tagName))return;eflags=html4.ELEMENTS[tagName];if(!(eflags&(html4.eflags.EMPTY|html4.eflags.FOLDABLE))){if(eflags&html4.eflags.OPTIONAL_ENDTAG)for(index=stack.length;--index>=0;){stackEl=stack[index];if(stackEl===tagName)break;if(!(html4.ELEMENTS[stackEl]&html4.eflags.OPTIONAL_ENDTAG))return}else
for(index=stack.length;--index>=0;)if(stack[index]===tagName)break;if(index<0)return;for(i=stack.length;--i>index;)stackEl=stack[i],html4.ELEMENTS[stackEl]&html4.eflags.OPTIONAL_ENDTAG||out.push('</',stackEl,'>');stack.length=index,out.push('</',tagName,'>')}},'pcdata':function(text,out){ignoring||out.push(text)},'rcdata':function(text,out){ignoring||out.push(text)},'cdata':function(text,out){ignoring||out.push(text)},'endDoc':function(out){var
i;for(i=stack.length;--i>=0;)out.push('</',stack[i],'>');stack.length=0}})}URI_SCHEME_RE=new
RegExp('^(?:([^:/?#]+):)?');function sanitizeAttribs(tagName,attribs,opt_uriPolicy,opt_nmTokenPolicy){var
attribKey,attribName,atype,i,parsedUri,value;for(i=0;i<attribs.length;i+=2){attribName=attribs[i],value=attribs[i+1],atype=null,((attribKey=tagName+'::'+attribName,html4.ATTRIBS.hasOwnProperty(attribKey))||(attribKey='*::'+attribName,html4.ATTRIBS.hasOwnProperty(attribKey)))&&(atype=html4.ATTRIBS[attribKey]);if(atype!==null)switch(atype){case
html4.atype.NONE:break;case html4.atype.SCRIPT:case html4.atype.STYLE:value=null;break;case
html4.atype.ID:case html4.atype.IDREF:case html4.atype.IDREFS:case html4.atype.GLOBAL_NAME:case
html4.atype.LOCAL_NAME:case html4.atype.CLASSES:value=opt_nmTokenPolicy?opt_nmTokenPolicy(value):value;break;case
html4.atype.URI:parsedUri=(''+value).match(URI_SCHEME_RE),parsedUri?!parsedUri[1]||WHITELISTED_SCHEMES.test(parsedUri[1])?(value=opt_uriPolicy?opt_uriPolicy(value):null):(value=null):(value=null);break;case
html4.atype.URI_FRAGMENT:value&&'#'===value.charAt(0)?(value=value.substring(1),value=opt_nmTokenPolicy?opt_nmTokenPolicy(value):value,value!==null&&value!==void
0&&(value='#'+value)):(value=null);break;default:value=null}else value=null;attribs[i+1]=value}return attribs}function
makeTagPolicy(opt_uriPolicy,opt_nmTokenPolicy){return function(tagName,attribs){if(!(html4.ELEMENTS[tagName]&html4.eflags.UNSAFE))return sanitizeAttribs(tagName,attribs,opt_uriPolicy,opt_nmTokenPolicy)}}function
sanitizeWithPolicy(inputHtml,tagPolicy){var outputArray=[];return makeHtmlSanitizer(tagPolicy)(inputHtml,outputArray),outputArray.join('')}function
sanitize(inputHtml,opt_uriPolicy,opt_nmTokenPolicy){var tagPolicy=makeTagPolicy(opt_uriPolicy,opt_nmTokenPolicy);return sanitizeWithPolicy(inputHtml,tagPolicy)}return{'escapeAttrib':escapeAttrib,'makeHtmlSanitizer':makeHtmlSanitizer,'makeSaxParser':makeSaxParser,'makeTagPolicy':makeTagPolicy,'normalizeRCData':normalizeRCData,'sanitize':sanitize,'sanitizeAttribs':sanitizeAttribs,'sanitizeWithPolicy':sanitizeWithPolicy,'unescapeEntities':unescapeEntities}})(html4),html_sanitize=html.sanitize,typeof
window!=='undefined'&&(window.html=html,window.html_sanitize=html_sanitize)};

/* [end] feature=core */

/* [start] feature=gapi.util-globals */
// Input 0
gapi.util = window["gapi"] && window["gapi"]["util"] || {};

;

/* [end] feature=gapi.util-globals */

/* [start] feature=gapi.util.getOrigin */
// Input 0
gapi.util.getOrigin = function(url) {
  if(!url) {
    return""
  }
  url = url.split("#")[0].split("?")[0];
  url = url.toLowerCase();
  0 == url.indexOf("//") && (url = window.location.protocol + url);
  0 > url.indexOf("://") && (url = window.location.protocol + "//" + url);
  var host = url.substring(url.indexOf("://") + 3), slashPos = host.indexOf("/");
  -1 != slashPos && (host = host.substring(0, slashPos));
  var protocol = url.substring(0, url.indexOf("://")), portStr = "", portPos = host.indexOf(":");
  if(-1 != portPos) {
    var port = host.substring(portPos + 1), host = host.substring(0, portPos);
    if("http" === protocol && "80" !== port || "https" === protocol && "443" !== port) {
      portStr = ":" + port
    }
  }
  return protocol + "://" + host + portStr
};

;

/* [end] feature=gapi.util.getOrigin */

/* [start] feature=rpc */
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

gadgets.rpctx = gadgets.rpctx || {};

/**
 * Transport for browsers that support native messaging (various implementations
 * of the HTML5 postMessage method). 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 4+
 *      - Chrome 2+
 *      - Webkit nightlies
 *      - Firefox 3+
 *      - Opera 9+
 */
if (!gadgets.rpctx.wpm) {  // make lib resilient to double-inclusion

  gadgets.rpctx.wpm = function() {
    var process, ready;
    var forceSecure = true;

    function attachBrowserEvent(eventName, callback, useCapture) {
      if (typeof window.addEventListener != 'undefined') {
        window.addEventListener(eventName, callback, useCapture);
      } else if (typeof window.attachEvent != 'undefined') {
        window.attachEvent('on' + eventName, callback);
      }
    }

    function removeBrowserEvent(eventName, callback, useCapture) {
      if (window.removeEventListener) {
        window.removeEventListener(eventName, callback, useCapture);
      } else if (window.detachEvent) {
        window.detachEvent('on' + eventName, callback);
      }
    }

    function onmessage(packet) {
      var rpc = gadgets.json.parse(packet.data);
      if (!rpc || !rpc['f']) {
        return;
      }

      // for security, check origin against expected value
      var origin = gadgets.rpc.getTargetOrigin(rpc['f']);

      // Opera's "message" event does not have an "origin" property (at least,
      // it doesn't in version 9.64;  presumably, it will in version 10).  If
      // event.origin does not exist, use event.domain.  The other difference is that
      // while event.origin looks like <scheme>://<hostname>:<port>, event.domain
      // consists only of <hostname>.
      if (forceSecure && (typeof packet.origin !== "undefined"
          ? packet.origin !== origin
          : packet.domain !== /^.+:\/\/([^:]+).*/.exec(origin)[1])) {
        return;
      }

      // IE can not access packet members in a closure.
      process(rpc, packet.origin);
    }

    return {
      getCode: function() {
        return 'wpm';
      },

      isParentVerifiable: function() {
        return true;
      },

      init: function(processFn, readyFn) {
        function configure(config) {
          var cfg = config && config['rpc'] || {};
          if (String(cfg['disableForceSecure']) === 'true') {
            forceSecure = false;
          }
        }
        gadgets.config.register('rpc', null, configure);

        process = processFn;
        ready = readyFn;

        // Set up native postMessage handler.
        attachBrowserEvent('message', onmessage, false);

        ready('..', true);  // Immediately ready to send to parent.
        return true;
      },

      setup: function(receiverId, token) {
        // Indicate that we're ready to send to the given receiver.
        ready(receiverId, true);
        return true;
      },

      call: function(targetId, from, rpc) {
        // targetOrigin = canonicalized relay URL
        var origin = gadgets.rpc.getTargetOrigin(targetId);
        var targetWin = gadgets.rpc._getTargetWin(targetId);
        if (origin) {
          window.setTimeout(function() {
            targetWin.postMessage(gadgets.json.stringify(rpc), origin);
          }, 0);
        } else {
          gadgets.error('No relay set (used as window.postMessage targetOrigin)' +
              ', cannot send cross-domain message');
        }
        return true;
      }
    };
  }();

} // !end of double-inclusion guard
;
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

gadgets.rpctx = gadgets.rpctx || {};

/*
 * 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.
 *
 *   fe: Gecko-specific frameElement trick.
 *      - Firefox 1+
 */
if (!gadgets.rpctx.frameElement) {  // make lib resilient to double-inclusion

  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 true;
          }
        } 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 true;
          }
        }
      } catch (e) {
      }
      return false;
    }

    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) {
        return callFrameElement(targetId, from, rpc);
      }

    };
  }();

} // !end of double-inclusion guard
;
;
;
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

gadgets.rpctx = gadgets.rpctx || {};

/**
 * 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
 */
if (!gadgets.rpctx.nix) {  // make lib resilient to double-inclusion

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;
    }
  };
}();

} // !end of double-inclusion guard
;
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

gadgets.rpctx = gadgets.rpctx || {};

/*
 * For older WebKit-based browsers, the security model does not allow for any
 * known "native" hacks for conducting cross browser communication. However,
 * a variation of the IFPC (see below) can be used, entitled "RMR". RMR is
 * a technique that uses the resize event of the iframe to indicate that a
 * message was sent (instead of the much slower/performance heavy polling
 * technique used when a defined relay page is not avaliable). Simply put,
 * RMR uses the same "pass the message by the URL hash" trick that IFPC
 * uses to send a message, but instead of having an active relay page that
 * runs a piece of code when it is loaded, RMR merely changes the URL
 * of the relay page (which does not even have to exist on the domain)
 * and then notifies the other party by resizing the relay iframe. RMR
 * exploits the fact that iframes in the dom of page A can be resized
 * by page A while the onresize event will be fired in the DOM of page B,
 * thus providing a single bit channel indicating "message sent to you".
 * This method has the added benefit that the relay need not be active,
 * nor even exist: a 404 suffices just as well. Note that the technique
 * doesn't actually strictly require WebKit; it just so happens that these
 * browsers have no known alternatives (but are very ill-used right now).
 * The technique's implementation accounts for timing issues through
 * a packet-ack'ing protocol, so should work on just about any browser.
 * This may be of value in scenarios where neither wpm nor Flash are
 * available for some reason.
 *
 *   rmr: Resizing trick, works particularly well on WebKit.
 *      - Safari 2+
 *      - Chrome 1
 */
if (!gadgets.rpctx.rmr) {  // make lib resilient to double-inclusion

  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 parentParam = gadgets.util.getUrlParameters()['parent'];

    var process;
    var ready;

    /**
   * Append an RMR relay frame to the document. This allows the receiver
   * to start receiving messages.
   *
   * @param {Node} channelFrame Relay frame to add to the DOM body.
   * @param {string} relayUri Base URI for the frame.
   * @param {string} data to pass along to the frame.
   * @param {string=} opt_frameId ID of frame for which relay is being appended (optional).
   */
    function appendRmrFrame(channelFrame, relayUri, data, opt_frameId) {
      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';
        if (opt_frameId) {
          // Process the initial sent payload (typically sent by container to
          // child/gadget) only when the relay frame has finished loading. We
          // do this to ensure that, in processRmrData(...), the ACK sent due
          // to processing can actually be sent. Before this time, the frame's
          // contentWindow is null, making it impossible to do so.
          channelFrame.onload = function() {
            processRmrData(opt_frameId);
          };
        }
        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(); });
      }
    }

    /**
   * Sets up the RMR transport frame for the given frameId. For gadgets
   * calling containers, the frameId should be '..'.
   *
   * @param {string} frameId The ID of the frame.
   */
    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;

      // Use the explicitly set relay, if one exists. Otherwise,
      // Construct one using the parent parameter plus robots.txt
      // as a synthetic relay. This works since browsers using RMR
      // treat 404s as legitimate for the purposes of cross domain
      // communication.
      var relayUri = gadgets.rpc.getRelayUrl(frameId);
      var relayOrigin = gadgets.rpc.getOrigin(parentParam);
      if (!relayUri) {
        relayUri = relayOrigin + '/robots.txt';
      }

      rmr_channels[frameId] = {
        frame: channelFrame,
        receiveWindow: null,
        relayUri: relayUri,
        relayOrigin: relayOrigin,
        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,

        // Token sent to target to verify domain.
        // TODO: switch to shindig.random()
        verifySendToken: String(Math.random()),

        // Token received from target during handshake. Stored in
        // order to send back to the caller for verification.
        verifyRecvToken: null,
        originVerified: false
      };

      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);
    }

    /**
   * Searches for a relay frame, created by the sender referenced by
   * frameId, with which this context receives messages. Once
   * found with proper permissions, attaches a resize handler which
   * signals messages to be sent.
   *
   * @param {string} frameId Frame ID of the prospective sender.
   */
    function conductRmrSearch(frameId) {
      var channelWindow = null;

      // Increment the search counter.
      rmr_channels[frameId].searchCounter++;

      try {
        var targetWin = gadgets.rpc._getTargetWin(frameId);
        if (frameId === '..') {
          // We are a gadget.
          channelWindow = targetWin.frames['rmrtransport-' + gadgets.rpc.RPC_ID];
        } else {
          // We are a container.
          channelWindow = targetWin.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);
      }
    }

    /**
   * Attempts to conduct an RPC call to the specified
   * target with the specified data via the RMR
   * 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} rpc The RPC data for this call.
   */
    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;
    }

    /**
   * Returns as a string the data to be appended to an RMR relay frame,
   * constructed from the current request queue plus an ACK message indicating
   * the currently latest-processed message ID.
   *
   * @param {string} toFrameId Frame whose sendable queued data to retrieve.
   */
    function getRmrData(toFrameId) {
      var channel = rmr_channels[toFrameId];
      var rmrData = {id: channel.sendId};
      if (channel) {
        rmrData['d'] = Array.prototype.slice.call(channel.queue, 0);
        var ackPacket = { 's': gadgets.rpc.ACK, 'id': channel.recvId };
        if (!channel.originVerified) {
          ackPacket['sendToken'] = channel.verifySendToken;
        }
        if (channel.verifyRecvToken) {
          ackPacket['recvToken'] = channel.verifyRecvToken;
        }
        rmrData['d'].push(ackPacket);
      }
      return gadgets.json.stringify(rmrData);
    }

    /**
   * Retrieve data from the channel keyed by the given frameId,
   * processing it as a batch. All processed data is assumed to have been
   * generated by getRmrData(...), pairing that method with this.
   *
   * @param {string} fromFrameId Frame from which data is being retrieved.
   */
    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);

          // Store sendToken if challenge was passed.
          // This will cause the token to be sent back to the sender
          // to prove origin verification.
          channel.verifyRecvToken = rpc['sendToken'];

          // If a recvToken came back, check to see if it matches the
          // sendToken originally sent as a challenge. If so, mark
          // origin as having been verified.
          if (!channel.originVerified && rpc['recvToken'] &&
              String(rpc['recvToken']) == String(channel.verifySendToken)) {
            channel.originVerified = 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;

        // Send along the origin if it's been verified during handshake.
        // In either case, dispatch the message.
        process(rpc, channel.originVerified ? channel.relayOrigin : undefined);
      }

      // 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 === '..') ? gadgets.rpc.RPC_ID : '..';
        callRmr(fromFrameId, gadgets.rpc.ACK, from, {'ackAlone': nonAckReceived});
      }
    }

    /**
   * Registers the RMR channel handler for the given frameId and associated
   * channel window.
   *
   * @param {string} frameId The ID of the frame for which this channel is being
   *   registered.
   * @param {Object} channelWindow The window of the receive frame for this
   *   channel, if any.
   *
   * @return {boolean} True if the frame was setup successfully, false
   *   otherwise.
   */
    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), frameId);
      } else {
        // Process messages that the gadget sent in its initial relay payload.
        // We can do this immediately because the container has already appended
        // and loaded a relay frame that can be used to ACK the messages the gadget
        // sent. In the preceding if-block, however, the processRmrData(...) call
        // must wait. That's because appendRmrFrame may not actually append the
        // frame - in the context of a gadget, this code may be running in the
        // head element, so it cannot be appended to body. As a result, the
        // gadget cannot ACK the container for messages it received.
        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);
      }
    };
  }();

} // !end of double-inclusion guard
;
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

gadgets.rpctx = gadgets.rpctx || {};

/*
 * 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.
 *      - No known major browsers still use this method, but it remains
 *        useful as a catch-all fallback for the time being.
 */
if (!gadgets.rpctx.ifpc) {  // make lib resilient to double-inclusion

  gadgets.rpctx.ifpc = function() {
    var iframePool = [];
    var callId = 0;
    var ready;

    var URL_LIMIT = 2000;
    var messagesIn = {};

    /**
   * Encodes arguments for the legacy IFPC wire format.
   *
   * @param {Object} args
   * @return {string} the encoded args.
   */
    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('&');
    }

    /**
   * 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;
      window.setTimeout(function() { document.body.appendChild(iframe); }, 0);
    }

    function isMessageComplete(arr, total) {
      for (var i = total - 1; i >= 0; --i) {
        if (typeof arr[i] === 'undefined') {
          return false;
        }
      }
      return true;
    }

    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 false;
        }

        // The RPC mechanism supports two formats for IFPC (legacy and current).
        var src = null,
            queueOut = [];
        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('');
          queueOut.push(src);
        } else {
          // Format: #targetId & sourceId@callId & packetNum & packetId & packetData
          src = [relay, '#', targetId, '&', from, '@', callId, '&'].join('');
          var message = encodeURIComponent(gadgets.json.stringify(rpc)),
              payloadLength = URL_LIMIT - src.length,
              numPackets = Math.ceil(message.length/payloadLength),
              packetIdx = 0,
              part;
          while (message.length > 0) {
            part = message.substring(0, payloadLength);
            message = message.substring(payloadLength);
            queueOut.push([src, numPackets, '&', packetIdx, '&', part].join(''));
            packetIdx += 1;
          }
        }

        // Conduct the IFPC call by creating the Iframe with
        // the relay URL and appended message.
        do {
          emitInvisibleIframe(queueOut.shift());
        } while(queueOut.length > 0);
        return true;
      },

      /** Process message from invisible iframe, merging message parts if necessary. */
      _receiveMessage: function(fragment, process) {
        var from = fragment[1],   // in the form of "<from>@<callid>"
            numPackets = parseInt(fragment[2], 10),
            packetIdx = parseInt(fragment[3], 10),
            payload = fragment[fragment.length - 1],
            completed = numPackets === 1;

        // if message is multi-part, store parts in the proper order
        if (numPackets > 1) {
          if (!messagesIn[from]) {
            messagesIn[from] = [];
          }
          messagesIn[from][packetIdx] = payload;
          // check if all parts have been sent
          if (isMessageComplete(messagesIn[from], numPackets)) {
            payload = messagesIn[from].join('');
            delete messagesIn[from];
            completed = true;
          }
        }

        // complete message sent
        if (completed) {
          process(gadgets.json.parse(decodeURIComponent(payload)));
        }
      }
    };
  }();

} // !end of double inclusion guard
;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

/**
 * @fileoverview Remote procedure call library for gadget-to-container,
 * container-to-gadget, and gadget-to-gadget (thru container) communication.
 */

/**
 * 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.
 *
 * 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.
 */

if (!window['gadgets'] || !window['gadgets']['rpc']) { // make lib resilient to double-inclusion

  /**
   * @static
   * @namespace Provides operations for making rpc calls.
   * @name gadgets.rpc
   */
  gadgets.rpc = function() {
    /**
     * @const
     * @private
     */
    var CALLBACK_NAME = '__cb';

    /**
     * @const
     * @private
     */
    var DEFAULT_NAME = '';

    /** Exported constant, for use by transports only.
     * @const
     * @type {string}
     * @member gadgets.rpc
     */
    var ACK = '__ack';

    /**
     * Timeout and number of attempts made to setup a transport receiver.
     * @const
     * @private
     */
    var SETUP_FRAME_TIMEOUT = 500;

    /**
     * @const
     * @private
     */
    var SETUP_FRAME_MAX_TRIES = 10;

    /**
     * @const
     * @private
     */
    var ID_ORIGIN_DELIMITER = '|';

    /**
     * @const
     * @private
     */
    var RPC_KEY_CALLBACK = 'callback';

    /**
     * @const
     * @private
     */
    var RPC_KEY_ORIGIN = 'origin';
    var RPC_KEY_REFERRER = 'referer';

    // Prefix to indicate service is called from gadgets.rpc.
    var LEGACY_SERVICE_PREFIX = 'legacy__'; // TODO(abc): b/4972750

    var services = {};
    var relayUrl = {};
    var useLegacyProtocol = {};
    var authToken = {};
    var callId = 0;
    var callbacks = {};
    var setup = {};
    var params = {};
    var receiverTx = {};
    var earlyRpcQueue = {};
    var passReferrerDirection = null;
    var passReferrerContents = null;

    // isGadget =~ isChild for the purposes of rpc (used only in setup).
    var isChild = (window.top !== window.self);

    // Set the current rpc ID from window.name immediately, to prevent
    // shadowing of window.name by a "var name" declaration, or similar.
    var rpcId = window.name;

    var securityCallback = function() {};
    var LOAD_TIMEOUT = 0;
    var FRAME_PHISH = 1;
    var FORGED_MSG = 2;

    // 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 console = window['console'];
    var clog = console && console.log && function(msg) { console.log(msg); } || function(){};
    var fallbackTransport = (function() {
      function logFn(name) {
        return function() {
          clog(name + ': call ignored');
        };
      }
      return {
        'getCode': function() { return 'noop'; },
        // Not really, but prevents transport assignment to IFPC.
        'isParentVerifiable': function() { return true; },
        '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();
    }

    /**
     * Return a transport representing the best available cross-domain
     * message-passing mechanism available to the browser.
     *
     * <p>Transports are selected on a cascading basis determined by browser
     * capability and other checks. The order of preference is:
     * <ol>
     * <li> wpm: Uses window.postMessage standard.
     * <li> dpm: Uses document.postMessage, similar to wpm but pre-standard.
     * <li> nix: Uses IE-specific browser hacks.
     * <li> rmr: Signals message passing using relay file's onresize handler.
     * <li> fe: Uses FF2-specific window.frameElement hack.
     * <li> ifpc: Sends messages via active load of a relay file.
     * </ol>
     * <p>See each transport's commentary/documentation for details.
     * @return {Object}
     * @member gadgets.rpc
     */
    function getTransport() {
      if (params['rpctx'] == 'flash') return gadgets.rpctx.flash;
      if (params['rpctx'] == 'rmr') return gadgets.rpctx.rmr;
      return typeof window.postMessage === 'function' ? gadgets.rpctx.wpm :
          typeof window.postMessage === 'object' ? gadgets.rpctx.wpm :
          window.ActiveXObject ? (gadgets.rpctx.flash ? gadgets.rpctx.flash :
            (gadgets.rpctx.nix ? gadgets.rpctx.nix : gadgets.rpctx.ifpc)) :
          navigator.userAgent.indexOf('WebKit') > 0 ? gadgets.rpctx.rmr :
          navigator.product === 'Gecko' ? gadgets.rpctx.frameElement :
          gadgets.rpctx.ifpc;
    }

    /**
     * Function passed to, and called by, a transport indicating it's ready to
     * send and receive messages.
     */
    function transportReady(receiverId, readySuccess) {
      if (receiverTx[receiverId]) return;
      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.
      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'] = getAuthToken(receiverId);
        tx.call(receiverId, rpc['f'], rpc);
      }

      // Clear the queue so it won't be sent again.
      earlyRpcQueue[receiverId] = [];
    }

    //  Track when this main page is closed or navigated to a different location
    // ("unload" event).
    //  NOTE: The use of the "unload" handler here and for the relay iframe
    // prevents the use of the in-memory page cache in modern browsers.
    // See: https://developer.mozilla.org/en/using_firefox_1.5_caching
    // See: http://webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
    var mainPageUnloading = false,
        hookedUnload = false;

    function hookMainPageUnload() {
      if (hookedUnload) {
        return;
      }
      function onunload() {
        mainPageUnloading = true;
      }

      // TODO: use common helper
      if (typeof window.addEventListener != 'undefined') {
        window.addEventListener('unload', onunload, false);
      } else if (typeof window.attachEvent != 'undefined') {
        window.attachEvent('onunload', onunload);
      }

      hookedUnload = true;
    }

    function relayOnload(targetId, sourceId, token, data, relayWindow) {
      // Validate auth token.
      if (!authToken[sourceId] || authToken[sourceId] !== token) {
        gadgets.error('Invalid auth token. ' + authToken[sourceId] + ' vs ' + token);
        securityCallback(sourceId, FORGED_MSG);
      }

      relayWindow.onunload = function() {
        if (setup[sourceId] && !mainPageUnloading) {
          securityCallback(sourceId, FRAME_PHISH);
          gadgets.rpc.removeReceiver(sourceId);
        }
      };
      hookMainPageUnload();

      data = gadgets.json.parse(decodeURIComponent(data));
    }

    /**
     * Helper function that performs actual processing of an RPC request.
     * Origin is passed in separately to ensure that it cannot be spoofed,
     * and guard code in the method ensures the same before dispatching
     * any service handler.
     * @param {Object} rpc RPC request object.
     * @param {string=} opt_sender RPC sender, if available and with a verified origin piece.
     * @private
     */
    function process(rpc, opt_sender) {
      //
      // 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 setupReceiver(...).
          if (authToken[rpc['f']] !== rpc['t']) {
            gadgets.error('Invalid auth token. ' + authToken[rpc['f']] + ' vs ' + rpc['t']);
            securityCallback(rpc['f'], FORGED_MSG);
          }
        }

        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[RPC_KEY_CALLBACK] = function(result) {
            // TODO(abc): b/4972750, remove this prefixing.
            var prefix = rpc['g'] ? LEGACY_SERVICE_PREFIX : '';
            gadgets.rpc.call(rpc['f'], prefix + CALLBACK_NAME, null, rpc['c'], result);
          };
        }

        // Set the requestor origin.
        // If not passed by the transport, then this simply sets to undefined.
        if (opt_sender) {
          var origin = getOrigin(opt_sender);
          rpc[RPC_KEY_ORIGIN] = opt_sender;
          var referrer = rpc['r'];
          if (!referrer || getOrigin(referrer) != origin) {
            // Transports send along as much info as they can about the sender
            // of the message; 'origin' is the origin component alone, while
            // 'referrer' is a best-effort field set from available information.
            // The second clause simply verifies that referrer is valid.
            referrer = opt_sender;
          }
          rpc[RPC_KEY_REFERRER] = referrer;
        }

        // 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);
        }
      }
    }

    /**
     * Helper method returning a canonicalized protocol://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.
     * @memberOf gadgets.rpc
     */
    function getOrigin(url) {
      if (!url) {
        return '';
      }
      // Remove fragment and query as they might otherwise confuse parsing
      url = ((url.split('#'))[0].split('?'))[0];
      url = url.toLowerCase();
      if (url.indexOf('//') == 0) {
        url = window.location.protocol + url;
      }
      if (url.indexOf('://') == -1) {
        // 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;
    }

    /*
     * Makes a sibling id in the format of "/<siblingFrameId>|<siblingOrigin>".
     */
    function makeSiblingId(id, opt_origin) {
      return '/' + id + (opt_origin ? ID_ORIGIN_DELIMITER + opt_origin : '');
    }

    /*
     * Parses an iframe id.  Returns null if not a sibling id or
     *   {id: <siblingId>, origin: <siblingOrigin>} otherwise.
     */
    function parseSiblingId(id) {
      if (id.charAt(0) == '/') {
        var delimiter = id.indexOf(ID_ORIGIN_DELIMITER);
        var siblingId = delimiter > 0 ? id.substring(1, delimiter) : id.substring(1);
        var origin = delimiter > 0 ? id.substring(delimiter + 1) : null;
        return {id: siblingId, origin: origin};
      } else {
        return null;
      }
    }

    function getTargetWin(id) {
      if (typeof id === 'undefined' ||
          id === '..') {
        return window.parent;
      }

      var siblingId = parseSiblingId(id);
      if (siblingId) {
        return window.top.frames[siblingId.id];
      }

      // Cast to a String to avoid an index lookup.
      id = String(id);

      // Try window.frames first
      var target = window.frames[id];
      if (target) {
        return target;
      }

      // Fall back to getElementById()
      target = document.getElementById(id);
      if (target && target.contentWindow) {
        return target.contentWindow;
      }

      return null;
    }

    function getTargetOrigin(id) {
      var targetRelay = null;
      var relayUrl = getRelayUrl(id);
      if (relayUrl) {
        targetRelay = relayUrl;
      } else {
        var siblingId = parseSiblingId(id);
        if (siblingId) {
          // sibling
          targetRelay = siblingId.origin;
        } else if (id == '..') {
          // parent
          targetRelay = params['parent'];
        } else {
          // child
          targetRelay = document.getElementById(id).src;
        }
      }

      return getOrigin(targetRelay);
    }

    // Pick the most efficient RPC relay mechanism.
    var transport = getTransport();

    // Create the Default RPC handler.
    services[DEFAULT_NAME] = function() {
      clog('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.call(this, result);
      }
    };

    /**
     * Conducts any frame-specific work necessary to setup
     * the channel type chosen. This method is called when
     * the container page first registers the gadget in the
     * RPC mechanism. Gadgets, in turn, will complete the setup
     * of the channel once they send their first messages.
     */
    function setupFrame(frameId, token) {
      if (setup[frameId] === true) {
        return;
      }

      if (typeof setup[frameId] === 'undefined') {
        setup[frameId] = 0;
      }

      var tgtFrame = getTargetWin(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 for this gadget.
        receiverTx[frameId] = fallbackTransport;
        setup[frameId] = true;
      }
    }

    /**
     * Gets the relay URL of a target frame.
     * @param {string} targetId Name of the target frame.
     * @return {string|undefined} Relay URL of the target frame.
     *
     * @member gadgets.rpc
     */
    function getRelayUrl(targetId) {
      var url = relayUrl[targetId];
      // Some RPC methods (wpm, for one) are unhappy with schemeless URLs.
      if (url && url.substring(0, 1) === '/') {
        if (url.substring(1, 2) === '/') {    // starts with '//'
          url = document.location.protocol + url;
        } else {    // relative URL, starts with '/'
          url = document.location.protocol + '//' + document.location.host + url;
        }
      }
      return url;
    }

    /**
     * Sets the relay URL of a target frame.
     * If url is empty, messages will not be sent.
     * @param {string} targetId Name of the target frame.
     * @param {string} url Full relay URL of the target frame.
     *
     * @member gadgets.rpc
     * @deprecated
     */
    function setRelayUrl(targetId, url, opt_useLegacy) {
      // Make URL absolute if necessary
      if (url && !/http(s)?:\/\/.+/.test(url)) {
        if (url.indexOf('//') == 0) {
          url = window.location.protocol + url;
        } else if (url.charAt(0) == '/') {
          url = window.location.protocol + '//' + window.location.host + url;
        } else if (url.indexOf('://') == -1) {
          // Assumed to be schemaless. Default to current protocol.
          url = window.location.protocol + '//' + url;
        }
      }
      relayUrl[targetId] = url;
      if (typeof opt_useLegacy !== 'undefined') {
        useLegacyProtocol[targetId] = !!opt_useLegacy;
      }
    }

    /**
     * Helper method to retrieve the authToken for a given gadget.
     * Not to be used directly.
     * @member gadgets.rpc
     * @return {string}
     */
    function getAuthToken(targetId) {
      return authToken[targetId];
    }

    /**
     * Sets the auth token of a target frame.
     * @param {string} targetId Name of the target frame.
     * @param {string} token The authentication token to use for all
     *     calls to or from this target id.
     *
     * @member gadgets.rpc
     * @deprecated
     */
    function setAuthToken(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);
    }

    function setReferrerConfig(cfg) {
      var passReferrer = cfg['passReferrer'] || "";
      var prParts = passReferrer.split(":", 2);
      passReferrerDirection = prParts[0] || "none";
      passReferrerContents = prParts[1] || "origin";
    }

    function setLegacyProtocolConfig(cfg) {
      if (isLegacyProtocolConfig(cfg)) {
        transport = gadgets.rpctx.ifpc;
        transport.init(process, transportReady);
      }
    }

    function isLegacyProtocolConfig(cfg) {
      return String(cfg['useLegacyProtocol']) === 'true';
    }

    function setupContainedContext(rpctoken, opt_parent) {
      function init(config) {
        var cfg = config && config['rpc'] || {};
        setReferrerConfig(cfg);

        // Parent-relative only.
        var parentRelayUrl = cfg['parentRelayUrl'] || '';
        parentRelayUrl = getOrigin(params['parent'] || opt_parent) + parentRelayUrl;
        setRelayUrl('..', parentRelayUrl, isLegacyProtocolConfig(cfg));

        setLegacyProtocolConfig(cfg);
        setAuthToken('..', rpctoken);
      }

      // Check to see if we know the parent yet.
      // In almost all cases we will, since the parent param is provided.
      // However, it's possible that the lib doesn't yet know, but is
      // initialized in forced fashion later.
      if (!params['parent'] && opt_parent) {
        // Handles the forced initialization case.
        init({});
        return;
      }

      // Handles the standard gadgets.config.init() case.
      gadgets.config.register('rpc', null, init);
    }

    function setupChildIframe(gadgetId, opt_frameurl, opt_authtoken) {
      var childIframe = null;
      if (gadgetId.charAt(0) != '/') {
        // only set up child (and not sibling) iframe
        if (!gadgets.util) {
          return;
        }
        childIframe = document.getElementById(gadgetId);
        if (!childIframe) {
          throw new Error('Cannot set up gadgets.rpc receiver with ID: ' + gadgetId +
              ', element not found.');
        }
      }

      // The "relay URL" can either be explicitly specified or is set as
      // the child IFRAME URL's origin
      var childSrc = childIframe && childIframe.src;
      var relayUrl = opt_frameurl || gadgets.rpc.getOrigin(childSrc);
      setRelayUrl(gadgetId, relayUrl);

      // The auth token is parsed from child params (rpctoken) or overridden.
      var childParams = gadgets.util.getUrlParameters(childSrc);
      var rpctoken = opt_authtoken || childParams['rpctoken'];
      setAuthToken(gadgetId, rpctoken);
    }

    /**
     * Sets up the gadgets.rpc library to communicate with the receiver.
     * <p>This method replaces setRelayUrl(...) and setAuthToken(...)
     *
     * <p>Simplified instructions - highly recommended:
     * <ol>
     * <li> Generate &lt;iframe id="&lt;ID&gt;" src="...#parent=&lt;PARENTURL&gt;&rpctoken=&lt;RANDOM&gt;"/&gt;
     *      and add to DOM.
     * <li> Call gadgets.rpc.setupReceiver("&lt;ID>");
     *      <p>All parent/child communication initializes automatically from here.
     *         Naturally, both sides need to include the library.
     * </ol>
     *
     * <p>Detailed container/parent instructions:
     * <ol>
     * <li> Create the target IFRAME (eg. gadget) with a given &lt;ID> and params
     *    rpctoken=<token> (eg. #rpctoken=1234), which is a random/unguessbable
     *    string, and parent=&lt;url>, where &lt;url> is the URL of the container.
     * <li> Append IFRAME to the document.
     * <li> Call gadgets.rpc.setupReceiver(&lt;ID>)
     * <p>[Optional]. Strictly speaking, you may omit rpctoken and parent. This
     *             practice earns little but is occasionally useful for testing.
     *             If you omit parent, you MUST pass your container URL as the 2nd
     *             parameter to this method.
     * </ol>
     *
     * <p>Detailed gadget/child IFRAME instructions:
     * <ol>
     * <li> If your container/parent passed parent and rpctoken params (query string
     *    or fragment are both OK), you needn't do anything. The library will self-
     *    initialize.
     * <li> If "parent" is omitted, you MUST call this method with targetId '..'
     *    and the second param set to the parent URL.
     * <li> If "rpctoken" is omitted, but the container set an authToken manually
     *    for this frame, you MUST pass that ID (however acquired) as the 2nd param
     *    to this method.
     * </ol>
     *
     * @member gadgets.rpc
     * @param {string} targetId
     * @param {string=} opt_receiverurl
     * @param {string=} opt_authtoken
     */
    function setupReceiver(targetId, opt_receiverurl, opt_authtoken) {
      if (targetId === '..') {
        // Gadget/IFRAME to container.
        var rpctoken = opt_authtoken || params['rpctoken'] || params['ifpctok'] || '';
        setupContainedContext(rpctoken, opt_receiverurl);
      } else {
        // Container to child.
        setupChildIframe(targetId, opt_receiverurl, opt_authtoken);
      }
    }

    function getReferrer(targetId) {
      if (passReferrerDirection === "bidir" ||
          (passReferrerDirection === "c2p" && targetId === "..") ||
          (passReferrerDirection === "p2c" && targetId !== "..")) {
        var href = window.location.href;
        var lopOff = "?";  // default = origin
        if (passReferrerContents === "query") {
          lopOff = "#";
        } else if (passReferrerContents === "hash") {
          return href;
        }
        var lastIx = href.lastIndexOf(lopOff);
        lastIx = lastIx === -1 ? href.length : lastIx;
        return href.substring(0, lastIx);
      }
      return null;
    }

    return /** @scope gadgets.rpc */ {
      config: function(config) {
        if (typeof config.securityCallback === 'function') {
          securityCallback = config.securityCallback;
        }
      },

      /**
       * Registers an RPC service.
       * @param {string} serviceName Service name to register.
       * @param {function(Object,Object)} handler Service handler.
       *
       * @member gadgets.rpc
       */
      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;
      },

      /**
       * Unregisters an RPC service.
       * @param {string} serviceName Service name to unregister.
       *
       * @member gadgets.rpc
       */
      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];
      },

      /**
       * Registers a default service handler to processes all unknown
       * RPC calls which raise an exception by default.
       * @param {function(Object,Object)} handler Service handler.
       *
       * @member gadgets.rpc
       */
      registerDefault: function(handler) {
        services[DEFAULT_NAME] = handler;
      },

      /**
       * Unregisters the default service handler. Future unknown RPC
       * calls will fail silently.
       *
       * @member gadgets.rpc
       */
      unregisterDefault: function() {
        delete services[DEFAULT_NAME];
      },

      /**
       * Forces all subsequent calls to be made by a transport
       * method that allows the caller to verify the message receiver
       * (by way of the parent parameter, through getRelayUrl(...)).
       * At present this means IFPC or WPM.
       * @member gadgets.rpc
       */
      forceParentVerifiable: function() {
        if (!transport.isParentVerifiable()) {
          transport = gadgets.rpctx.ifpc;
        }
      },

      /**
       * Calls an RPC service.
       * @param {string} targetId Module Id of the RPC service provider.
       *                          Empty if calling the parent container.
       * @param {string} serviceName Service name to call.
       * @param {function()|null} callback Callback function (if any) to process
       *                                 the return value of the RPC request.
       * @param {*} var_args Parameters for the RPC request.
       *
       * @member gadgets.rpc
       */
      call: function(targetId, serviceName, callback, var_args) {
        targetId = targetId || '..';
        // Default to the container calling.
        var from = '..';

        if (targetId === '..') {
          from = rpcId;
        } else if (targetId.charAt(0) == '/') {
          // sending to sibling
          from = makeSiblingId(rpcId, gadgets.rpc.getOrigin(window.location.href));
        }

        ++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]
        };

        var referrer = getReferrer(targetId);
        if (referrer) {
          rpc['r'] = referrer;
        }

        if (targetId !== '..' &&
            parseSiblingId(targetId) == null &&  // sibling never in the document
            !document.getElementById(targetId)) {
          // The target has been removed from the DOM. Don't even try.
          return;
        }

        // Attempt to make call via a cross-domain transport.
        // Retrieve the transport for the given target - if one
        // target is misconfigured, it won't affect the others.
        // In the case of a sibling relay, channel is not found
        // in the receiverTx map but in the transport itself.
        var channel = receiverTx[targetId];
        if (!channel && parseSiblingId(targetId) !== null) {
          // Sibling-to-sibling communication; use default trasport
          // (in practice, wpm) despite not being ready()-indicated.
          channel = transport;
        }

        // TODO(abc): b/4972750, remove start here
        if (serviceName.indexOf(LEGACY_SERVICE_PREFIX) === 0) {
          // sandboxing only work for wpm
          channel = transport;
          // normalize the service name
          rpc['s'] = serviceName.substring(LEGACY_SERVICE_PREFIX.length);
          // ensure callback id exist
          rpc['c'] = rpc['c'] ? rpc['c'] : callId;
        }
        // newly introduced for sandboxing, indicates this comes from legacy
        rpc['g'] = true;
        // added, used by new rpc for correct callback target
        rpc['r'] = from;
        // TODO(abc): b/4972750, remove up to here

        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.
          receiverTx[targetId] = fallbackTransport;
          transport.call(targetId, from, rpc);
        }
      },

      getRelayUrl: getRelayUrl,
      setRelayUrl: setRelayUrl,
      setAuthToken: setAuthToken,
      setupReceiver: setupReceiver,
      getAuthToken: getAuthToken,

      // Note: Does not delete iframe
      removeReceiver: function(receiverId) {
        delete relayUrl[receiverId];
        delete useLegacyProtocol[receiverId];
        delete authToken[receiverId];
        delete setup[receiverId];
        delete receiverTx[receiverId];
      },

      /**
       * Gets the RPC relay mechanism.
       * @return {string} RPC relay mechanism. See above for
       *   a list of supported types.
       *
       * @member gadgets.rpc
       */
      getRelayChannel: function() {
        return transport.getCode();
      },

      /**
       * 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
       *        JSON-encoded and URI escaped packet data.
       *
       * @member gadgets.rpc
       * @deprecated
       */
      receive: function(fragment, otherWindow) {
        if (fragment.length > 4) {
          transport._receiveMessage(fragment, process);
        } else {
          relayOnload.apply(null, fragment.concat(otherWindow));
        }
      },

      /**
       * Receives and processes an RPC request sent via the same domain.
       * (Not to be used directly). Converts the inbound rpc object's
       * Array into a local Array to pass the process() Array test.
       * @param {Object} rpc RPC object containing all request params.
       * @member gadgets.rpc
       */
      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);
      },

      // Helper method to get the protocol://host:port of an input URL.
      // see docs above
      getOrigin: getOrigin,
      getTargetOrigin: getTargetOrigin,

      /**
       * Internal-only method used to initialize gadgets.rpc.
       * @member 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;
        }
        if (isChild) {
          setupReceiver('..');
        } else {
          gadgets.config.register('rpc', null, function(config) {
            var cfg = config['rpc'] || {};
            setReferrerConfig(cfg);
            setLegacyProtocolConfig(cfg);
          });
        }
      },

      /** Returns the window keyed by the ID. null/".." for parent, else child */
      _getTargetWin: getTargetWin,

      /** Parses a sibling id into {id: <siblingId>, origin: <siblingOrigin>} */
      _parseSiblingId: parseSiblingId,

      ACK: ACK,

      RPC_ID: rpcId || "..",

      SEC_ERROR_LOAD_TIMEOUT: LOAD_TIMEOUT,
      SEC_ERROR_FRAME_PHISH: FRAME_PHISH,
      SEC_ERROR_FORGED_MSG: FORGED_MSG
    };
  }();

  // Initialize library/transport.
  gadgets.rpc.init();

} else if (typeof gadgets.rpc == 'undefined' || !gadgets.rpc) {
  // TODO(xiangtian): remove this temporary fix for b/4463948
  gadgets.rpc = window['gadgets']['rpc'];
  gadgets.rpc.config = gadgets.rpc['config'];
  gadgets.rpc.register = gadgets.rpc['register'];
  gadgets.rpc.unregister = gadgets.rpc['unregister'];
  gadgets.rpc.registerDefault = gadgets.rpc['registerDefault'];
  gadgets.rpc.unregisterDefault = gadgets.rpc['unregisterDefault'];
  gadgets.rpc.forceParentVerifiable = gadgets.rpc['forceParentVerifiable'];
  gadgets.rpc.call = gadgets.rpc['call'];
  gadgets.rpc.getRelayUrl = gadgets.rpc['getRelayUrl'];
  gadgets.rpc.setRelayUrl = gadgets.rpc['setRelayUrl'];
  gadgets.rpc.setAuthToken = gadgets.rpc['setAuthToken'];
  gadgets.rpc.setupReceiver = gadgets.rpc['setupReceiver'];
  gadgets.rpc.getAuthToken = gadgets.rpc['getAuthToken'];
  gadgets.rpc.removeReceiver = gadgets.rpc['removeReceiver'];
  gadgets.rpc.getRelayChannel = gadgets.rpc['getRelayChannel'];
  gadgets.rpc.receive = gadgets.rpc['receive'];
  gadgets.rpc.receiveSameDomain = gadgets.rpc['receiveSameDomain'];
  gadgets.rpc.getOrigin = gadgets.rpc['getOrigin'];
  gadgets.rpc.getTargetOrigin = gadgets.rpc['getTargetOrigin'];
  gadgets.rpc._getTargetWin = gadgets.rpc['_getTargetWin'];
  gadgets.rpc._parseSiblingId = gadgets.rpc['_parseSiblingId'];
} // !end of double-inclusion guard
;
;

/* [end] feature=rpc */
(function(){var j=window['___jsl'];if(j['c']){j['c']();delete j['c'];}})();
