You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2010/05/27 03:03:11 UTC

svn commit: r948646 - in /shindig/trunk/features/src/main/javascript/features: ./ container/ shindig.container-1.0/

Author: lindner
Date: Thu May 27 01:03:10 2010
New Revision: 948646

URL: http://svn.apache.org/viewvc?rev=948646&view=rev
Log:
Patch from Michael Hermanto | The next generation in shindig containers!

Added:
    shindig/trunk/features/src/main/javascript/features/container/
    shindig/trunk/features/src/main/javascript/features/container/container.js
    shindig/trunk/features/src/main/javascript/features/container/feature.xml
    shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js
    shindig/trunk/features/src/main/javascript/features/container/gadget_site.js
    shindig/trunk/features/src/main/javascript/features/container/init.js
    shindig/trunk/features/src/main/javascript/features/container/service.js
    shindig/trunk/features/src/main/javascript/features/container/util.js
    shindig/trunk/features/src/main/javascript/features/shindig.container-1.0/
    shindig/trunk/features/src/main/javascript/features/shindig.container-1.0/feature.xml
Modified:
    shindig/trunk/features/src/main/javascript/features/features.txt

Added: shindig/trunk/features/src/main/javascript/features/container/container.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/container.js?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/container.js (added)
+++ shindig/trunk/features/src/main/javascript/features/container/container.js Thu May 27 01:03:10 2010
@@ -0,0 +1,374 @@
+/*
+ * 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 This represents the container for the current window or create
+ * the container if none already exists.
+ */
+var shindig = shindig || {};
+shindig.container = shindig.container || {};
+
+
+/**
+ * @param {Object=} opt_config Configuration JSON.
+ * @constructor
+ */
+shindig.container.Container = function(opt_config) {
+  var config = opt_config || {};
+
+  /**
+   * A JSON list of preloaded gadget URLs.
+   * @type {Object}
+   */
+  this.preloadedGadgetUrls_ = {};
+
+  /**
+   * @type {Object}
+   */
+  this.sites_ = {};
+
+  /**
+   * @type {boolean}
+   */
+  this.renderDebug_ = shindig.container.util.getSafeJsonValue(config,
+      shindig.container.ContainerConfig.RENDER_DEBUG, false);
+
+  /**
+   * @type {boolean}
+   */
+  this.renderTest_ = shindig.container.util.getSafeJsonValue(config,
+      shindig.container.ContainerConfig.RENDER_TEST, false);
+
+  /**
+   * @type {boolean}
+   */
+  this.sameDomain_ = shindig.container.util.getSafeJsonValue(config,
+      shindig.container.ContainerConfig.SAME_DOMAIN, true);
+
+  /**
+   * @type {number}
+   */
+  this.tokenRefreshInterval_ = shindig.container.util.getSafeJsonValue(config,
+      shindig.container.ContainerConfig.TOKEN_REFRESH_INTERVAL,
+      30 * 60 * 1000);
+
+  /**
+   * @type {shindig.container.Service}
+   */
+  this.service_ = new shindig.container.Service(config);
+
+  /**
+   * Security token refresh interval (in ms) for debugging.
+   * @type {Object}
+   */
+  this.tokenRefreshTimer_ = null;
+
+  this.registerRpcServices_();
+
+  this.onConstructed(config);
+};
+
+
+/**
+ * Create a new gadget site.
+ * @param {Element} gadgetEl HTML element into which to render
+ * @param {Element=} opt_bufferEl Optional HTML element for double buffering.
+ * @return {shindig.container.GadgetSite} site created for application to hold to.
+ */
+shindig.container.Container.prototype.newGadgetSite = function(
+    gadgetEl, opt_bufferEl) {
+  var site = new shindig.container.GadgetSite(
+      this.service_, gadgetEl, opt_bufferEl);
+  this.sites_[site.getId()] = site;
+  return site;
+};
+
+
+/**
+ * @param {string} id Iframe ID of gadget site to get.
+ * @return {shindig.container.GadgetSite} The gadget site with given holder's iframeId.
+ */
+shindig.container.Container.prototype.getGadgetSite = function(id) {
+  // TODO: Support getting only the loading/active gadget in 2x buffers.
+  for (var siteId in this.sites_) {
+    var site = this.sites_[siteId];
+    if (site.getGadgetHolder(id)) {
+      return site;
+    }
+  }
+  return null;
+};
+
+
+/**
+ * @param {string} id Iframe ID of gadget holder to get.
+ * @return {shindig.container.GadgetHolder} The gadget holder with iframe id.
+ */
+shindig.container.Container.prototype.getGadgetHolder = function(id) {
+  var site = this.getGadgetSite(id);
+  if (site) {
+    return site.getGadgetHolder(id);
+  } else {
+    return null;
+  }
+};
+
+
+/**
+ * Called when gadget is navigated.
+ *
+ * @param {shindig.container.GadgetSite} site the site where navigation is ocurring
+ * @param {string} gadgetUrl The URI of the gadget
+ * @param {Object} gadgetParams view params for the gadget
+ * @param {Object} renderParams render parameters, including the view
+ * @param {Function=} opt_callback Callback that occurs after gadget is loaded
+ */
+shindig.container.Container.prototype.navigateGadget = function(
+    site, gadgetUrl, gadgetParams, renderParams, opt_callback) {
+  if (this.renderDebug_) {
+    renderParams['nocache'] = true;
+    renderParams['debug'] = true;
+  }
+  if (this.renderTest_) {
+    renderParams['testmode'] = true;
+  }
+  var self = this;
+  var callback = function(response) {
+    // TODO: Navigate to error screen on primary gadget load failure
+    // TODO: Should display error without doing a standard navigate.
+    // TODO: Bad if the error gadget fails to load.
+    if (!response.error) {
+      self.scheduleRefreshTokens_();
+    }
+    if (opt_callback) {
+      opt_callback(response);
+    }
+  };
+  // TODO: Lifecycle, add ability for current gadget to cancel nav.
+  site.navigateTo(gadgetUrl, gadgetParams, renderParams, callback);
+};
+
+
+/**
+ * Called when gadget is closed. This may stop refreshing of tokens.
+ * @param {shindig.container.GadgetSite} site the site where navigation occurred.
+ */
+shindig.container.Container.prototype.closeGadget = function(site) {
+  var id = site.getId();
+  var el = this.siteEls_[id];
+  site.close();
+  if (el) {
+    el.parentNode.removeChild(el);
+  }
+  delete this.sites_[id];
+  this.unscheduleRefreshTokens_();
+};
+
+
+/**
+ * Pre-load gadgets metadata information. This is done by priming the cache,
+ * and making an immediate call to fetch metadata of gadgets fully specified at
+ * gadgetUrls. This will not render, and do additional callback operations.
+ * @param {Object} request JSON containing ids of gadgets URIs to preload.
+ */
+shindig.container.Container.prototype.preloadGadgets = function(request) {
+  var metadataRequest = {
+      'container' : window.__CONTAINER,
+      'ids' : request['ids']
+  };
+  var self = this;
+  this.service_.getGadgetMetadata(metadataRequest, function(response) {
+    if (!response.error) {
+      var data = response.data;
+      var ids = shindig.container.util.toArrayOfJsonKeys(data);
+      for (var i = 0; i < ids.length; i++) {
+        self.addPreloadedGadgetUrl_(ids[i]);
+      }
+      self.scheduleRefreshTokens_();
+    }
+  });
+};
+
+/**
+ * Callback that occurs after instantiation/construction of this. Override to
+ * provide your specific functionalities.
+ * @param {Object=} opt_config Configuration JSON.
+ */
+shindig.container.Container.prototype.onConstructed = function(opt_config) {};
+
+
+// -----------------------------------------------------------------------------
+// Valid JSON keys.
+// -----------------------------------------------------------------------------
+
+/**
+ * Enumeation of configuration keys for this container. This is specified in
+ * JSON to provide extensible configuration. These enum values are for
+ * documentation purposes only, it is expected that clients use the string
+ * values.
+ * @enum {string}
+ */
+shindig.container.ContainerConfig = {};
+// Whether debug mode is turned on.
+shindig.container.ContainerConfig.RENDER_DEBUG = 'renderDebug';
+// Whether test mode is turned on.
+shindig.container.ContainerConfig.RENDER_TEST = 'renderTest';
+// Toggle to render gadgets in the same domain.
+shindig.container.ContainerConfig.SAME_DOMAIN = 'sameDomain';
+// Security token refresh interval (in ms) for debugging.
+shindig.container.ContainerConfig.TOKEN_REFRESH_INTERVAL = 'tokenRefreshInterval';
+
+
+/**
+ * Enum keys for gadget rendering params. Gadget rendering params affect which
+ * view of a gadget of displayed and how the gadget site is rendered, and are
+ * not passed on to the actual gadget. These enum values are for documentation
+ * purposes only, it is expected that clients use the string values.
+ * @enum {string}
+ */
+shindig.container.ContainerRender = {};
+// Style class to associate to iframe.
+shindig.container.ContainerRender.CLASS = 'class';
+// Whether to turn off debugging.
+shindig.container.ContainerRender.DEBUG = 'debug';
+// The starting/default gadget iframe height (in pixels).
+shindig.container.ContainerRender.HEIGHT ='height';
+// Whether to turn off debugging.
+shindig.container.ContainerRender.TEST = 'test';
+// The gadget view name.
+shindig.container.ContainerRender.VIEW = 'view';
+// The starting/default gadget iframe width (in pixels).
+shindig.container.ContainerRender.WIDTH = 'width';
+
+
+// -----------------------------------------------------------------------------
+// Private variables and methods.
+// -----------------------------------------------------------------------------
+
+
+/**
+ * Start to schedule refreshing of tokens.
+ * @private
+ */
+shindig.container.Container.prototype.scheduleRefreshTokens_ = function() {
+  // TODO: Obtain the interval time by taking the minimum of expiry time of
+  // token in all preloaded- and navigated-to- gadgets. This should be obtained
+  // from the server. For now, constant on 50% of long-lived tokens (1 hour),
+  // which is 30 minutes.
+  if (!this.tokenRefreshTimer_) {
+    var self = this;
+    this.tokenRefreshTimer_ = window.setInterval(function() {
+        self.refreshTokens_();
+    }, this.tokenRefreshInterval_);
+  }
+};
+
+
+/**
+ * Stop already-scheduled refreshing of tokens.
+ * @private
+ */
+shindig.container.Container.prototype.unscheduleRefreshTokens_ = function() {
+  if (this.tokenRefreshTimer_) {
+    var urls = this.getTokenRefreshableGadgetUrls_();
+    if (urls.length <= 0) {
+      window.clearInterval(this.tokenRefreshTimer_);
+      this.tokenRefreshTimer_ = null;
+    }
+  }
+};
+
+
+/**
+ * Register standard RPC services
+ * @private
+ */
+shindig.container.Container.prototype.registerRpcServices_ = function() {
+  var self = this;
+  gadgets.rpc.register('resize_iframe', function(height) {
+    // this['f'] is set by calling iframe via gadgets.rpc.
+    var site = self.getGadgetSite(this['f']);
+    site.setHeight(height);
+  });
+};
+
+
+/**
+ * Keep track of preloaded gadget URLs. These gadgets will have their tokens
+ * refreshed as part of batched token fetch.
+ * @param {string} gadgetUrl URL of preloaded gadget.
+ * @private
+ */
+shindig.container.Container.prototype.addPreloadedGadgetUrl_ = function(gadgetUrl) {
+  this.preloadedGadgetUrls_[gadgetUrl] = null;
+};
+
+
+/**
+ * Collect all URLs of gadgets that require tokens refresh. This comes from both
+ * preloaded gadgets and navigated-to gadgets.
+ * @return {array} An array of URLs of gadgets.
+ * @private
+ */
+shindig.container.Container.prototype.getTokenRefreshableGadgetUrls_ = function() {
+  // Uses a JSON to ensure uniqueness. Collect all preloaded gadget URLs.
+  var result = shindig.container.util.mergeJsons({}, this.preloadedGadgetUrls_);
+
+  // Collect all current gadget URLs.
+  for (var siteIndex in this.sites_) {
+    var holder = this.sites_[siteIndex].getActiveGadget();
+    if (holder) {
+      result[holder.getUrl()] = null;
+    }
+  }
+
+  return shindig.container.util.toArrayOfJsonKeys(result);
+};
+
+
+/**
+ * Refresh security tokens immediately. This will fetch gadget metadata, along
+ * with its token and have the token cache updated.
+ * @private
+ */
+shindig.container.Container.prototype.refreshTokens_ = function() {
+  var ids = this.getTokenRefreshableGadgetUrls_();
+  var request = {
+    'container' : window.__CONTAINER,
+    'ids' : ids
+  };
+
+  var self = this;
+  osapi.gadgets.getToken(request, function(response) {
+    if (!response.error) {
+      // Update current (visible) gadgets with new tokens (already stored in
+      // cache). Do not need to update pre-loaded gadgets, since new tokens will
+      // take effect when they are navigated to.
+      var data = response.data;
+      for (var key in self.sites_) {
+        var holder = self.sites_[key].getActiveGadget();
+        if (holder) {
+          var token = data[holder.getUrl()]['token'];
+          gadgets.rpc.call(holder.getIframeId(), 'update_security_token', null,
+              token);
+        }
+      }
+    }
+    // TODO: Tokens will be stale, but error should not be ignored.
+  });
+};

Added: shindig/trunk/features/src/main/javascript/features/container/feature.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/feature.xml?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/feature.xml (added)
+++ shindig/trunk/features/src/main/javascript/features/container/feature.xml Thu May 27 01:03:10 2010
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+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.
+-->
+<feature>
+  <name>container</name>
+  <dependency>core</dependency>
+  <dependency>osapi</dependency>
+  <dependency>rpc</dependency>
+  <container>
+    <script src="util.js"/>
+    <script src="service.js"/>
+    <script src="gadget_holder.js"/>
+    <script src="gadget_site.js"/>
+    <script src="container.js"/>
+    <script src="init.js"/>
+  </container>
+</feature>

Added: shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js (added)
+++ shindig/trunk/features/src/main/javascript/features/container/gadget_holder.js Thu May 27 01:03:10 2010
@@ -0,0 +1,386 @@
+/*
+ * 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 This represents an HTML element and the associated gadget.
+ */
+var shindig = shindig || {};
+shindig.container = shindig.container || {};
+
+
+/**
+ * @param {number} siteId The id of site containing this holder.
+ * @param {Element} el The element to render gadgets in.
+ * @constructor
+ */
+shindig.container.GadgetHolder = function(siteId, el) {
+  /**
+   * Unique numeric gadget ID. Should be the same as siteId.
+   * @type {number}
+   * @private
+   */
+  this.siteId_ = siteId;
+
+  /**
+   * The element into which the gadget is rendered.
+   * @type {Element}
+   * @private
+   */
+  this.el_ = el;
+
+  /**
+   * JSON metadata for gadget
+   * @type {Object}
+   * @private
+   */
+  this.gadgetInfo_ = null;
+
+  /**
+   * View parameters to pass to gadget.
+   * @type {Object}
+   * @private
+   */
+  this.gadgetParams_ = null;
+
+  /**
+   * Whether there are any view parameters.
+   * @type {boolean}
+   * @private
+   */
+  this.hasGadgetParams_ = false;
+
+  /**
+   * Gadget rendering parameters
+   * @type {Object}
+   * @private
+   */
+  this.renderParams_ = null;
+
+  /**
+   * Unique string gadget ID. Used for DOM IDs/names.
+   * @type {string}
+   * @private
+   */
+  this.iframeId_ = null;
+
+  /**
+   * Name of current view being rendered.
+   * @type {string}
+   * @private
+   */
+  this.view_ = null;
+
+  /**
+   * JSON metadata about current view being rendered.
+   * @type {Object}
+   * @private
+   */
+  this.viewInfo_ = null;
+
+  /**
+   * A dynamically set social/security token.
+   * Social tokens are sent with original view URLs but may need
+   * to be refreshed for long lived gadgets.
+   * @type {string}
+   * @private
+   */
+  this.securityToken_ = null;
+
+  this.onConstructed();
+};
+
+
+/**
+ * Callback that occurs after instantiation/construction of this. Override to
+ * provide your specific functionalities.
+ */
+shindig.container.GadgetHolder.prototype.onConstructed = function() {};
+
+
+/**
+ * @return {Element} The HTML element containing the rendered gadget.
+ */
+shindig.container.GadgetHolder.prototype.getElement = function() {
+  return this.el_;
+};
+
+
+/**
+ * @return {string} The unique string ID for gadget iframe.
+ */
+shindig.container.GadgetHolder.prototype.getIframeId = function() {
+  return this.iframeId_;
+};
+
+
+/**
+ * @return {Object} The metadata of gadget.
+ */
+shindig.container.GadgetHolder.prototype.getGadgetInfo = function() {
+  return this.gadgetInfo_;
+};
+
+
+/**
+ * Remove the gadget from this.
+ */
+shindig.container.GadgetHolder.prototype.dispose = function() {
+  this.gadgetInfo_ = null;
+};
+
+
+/**
+ * @return {string} The URL of current gadget.
+ */
+shindig.container.GadgetHolder.prototype.getUrl = function() {
+  return (this.gadgetInfo_) ? this.gadgetInfo_['url'] : null;
+};
+
+
+/**
+ * @return {string} The view of current gadget.
+ */
+shindig.container.GadgetHolder.prototype.getView = function() {
+  return this.view_;
+};
+
+
+/**
+ * @return {string} The iframe element containing gadget.
+ */
+shindig.container.GadgetHolder.prototype.getIframeElement = function() {
+  return this.el_.firstChild;
+};
+
+
+/**
+ * @param {string} value The value to set this social/security token to.
+ * @return {shindig.container.GadgetHolder}
+ */
+shindig.container.GadgetHolder.prototype.setSecurityToken = function(value) {
+  this.securityToken_ = value;
+  return this;
+};
+
+
+/**
+ * Render a gadget into the element.
+ * @param {Object} gadgetInfo the JSON gadget description.
+ * @param {Object} gadgetParams View parameters for the gadget.
+ * @param {Object} renderParams Render parameters for the gadget, including: view, width, height.
+ */
+shindig.container.GadgetHolder.prototype.render = function(gadgetInfo, gadgetParams, renderParams) {
+  this.iframeId_ = shindig.container.GadgetHolder.IFRAME_ID_PREFIX_ + this.siteId_;
+  this.gadgetInfo_ = gadgetInfo;
+  this.gadgetParams_ = gadgetParams;
+  this.hasGadgetParams_ = false;
+  for (var key in this.gadgetParams_) {
+    this.hasGadgetParams_ = true;
+    break;
+  }
+  this.renderParams_ = renderParams;
+  this.view_ = renderParams['view'];
+  this.viewInfo_ = this.gadgetInfo_['views'][this.view_];
+  if (!this.viewInfo_) {
+    throw 'View ' + this.view_ + ' unsupported in ' + this.gadgetInfo_['url'];
+  }
+
+  this.el_.innerHTML = this.getIframeHtml_();
+
+  // Set up RPC channel. RPC relay url is on gmodules, relative to base of the
+  // container. Assumes container has set up forwarding to gmodules at /gadgets.
+  gadgets.rpc.setRelayUrl(this.iframeId_, this.viewInfo_['iframeHost'] +
+      '/gadgets/files/container/rpc_relay.html');
+  // Pull RPC token from gadget URI
+  gadgets.rpc.setAuthToken(this.iframeId_, this.getRpcToken_());
+};
+
+
+// -----------------------------------------------------------------------------
+// Private variables and methods.
+// -----------------------------------------------------------------------------
+
+
+/**
+ * Prefix for gadget HTML IDs/names.
+ * @type {string}
+ * @private
+ */
+shindig.container.GadgetHolder.IFRAME_ID_PREFIX_ = '__gadget_';
+
+
+/**
+ * Get HTML text content that can be used to render the gadget IFRAME
+ * @return {string} The HTML content of this gadget that can be rendered.
+ * @private
+ */
+shindig.container.GadgetHolder.prototype.getIframeHtml_ = function() {
+  var iframeParams = {
+      'id': this.iframeId_,
+      'name': this.iframeId_,
+      'src': this.getIframeUrl_(),
+      'scrolling': 'no',
+      'marginwidth': '0',
+      'marginheight': '0',
+      'frameborder': '0',
+      'vspace': '0',
+      'hspace': '0',
+      'class': this.renderParams_['class'],
+      'height': this.renderParams_['height'],
+      'width': this.renderParams_['width']
+  };
+
+  // Do not use DOM API (createElement(), setAttribute()), since it is slower,
+  // requires more code, and creating an element with it results in a click
+  // sound in IE (unconfirmed), setAttribute('class') may need browser-specific
+  // variants.
+  var out = [];
+  out.push('<iframe ');
+  for (var key in iframeParams) {
+    var value = iframeParams[key];
+    if (value) {
+      out.push(key + '="' + value + '" ');
+    }
+  }
+  out.push('></iframe>');
+
+  return out.join('');
+};
+
+
+/**
+ * Get the rendering iframe URL.
+ * @private
+ */
+shindig.container.GadgetHolder.prototype.getIframeUrl_ = function() {
+  var iframeHost = this.viewInfo_['iframeHost'] || '';
+  var uri = iframeHost + this.viewInfo_['iframePath'];
+  uri = this.updateBooleanParam_(uri, 'debug');
+  uri = this.updateBooleanParam_(uri, 'nocache');
+  uri = this.updateBooleanParam_(uri, 'testmode');
+  uri = this.updateUserPrefParams_(uri);
+
+  // TODO: Share this base container logic
+  // TODO: Two SD base URIs - one for container, one for gadgets
+  // Need to add parent at end of query due to gadgets parsing bug
+  uri = this.addQueryParam_(uri, 'parent', shindig.container.util.parseOrigin(
+      document.location.href));
+
+  // Remove existing social token if we have a new one
+  if (this.securityToken_) {
+    var securityTokenMatch = uri.match(/([&#?])(st=[^&#]*)/);
+    if (securityTokenMatch) {
+      // TODO: Should we move the token to the hash?
+      uri = uri.replace(securityTokenMatch[0], securityTokenMatch[1] +
+          'st=' + this.securityToken_);
+    }
+  }
+
+  uri = this.addHashParam_(uri, 'mid', this.siteId_);
+
+  if (this.hasGadgetParams_) {
+    var gadgetParamText = gadgets.json.stringify(this.gadgetParams_);
+    uri = this.addHashParam_(uri, 'view-params',
+        encodeURIComponent(gadgetParamText));
+  }
+  return uri;
+};
+
+
+/**
+ * Updates query params of interest.
+ * @param {string} uri The URL to append query param to.
+ * @param {string} param The query param to update uri with.
+ * @return {string} The URL with param append to.
+ * @private
+ */
+shindig.container.GadgetHolder.prototype.updateBooleanParam_ = function(uri, param) {
+  if (this.renderParams_[param]) {
+    uri = this.addQueryParam_(uri, param, 1);
+  }
+  return uri;
+};
+
+
+/**
+ * Replace user prefs specified in url with only those specified. This will
+ * maintain each user prefs existence (or lack of), order (from left to right)
+ * and its appearance (in query params or fragment).
+ * @param {string} url The URL possibly containing user preferences parameters
+ *     prefixed by up_.
+ * @return {string} The URL with up_ replaced by those specified in userPrefs.
+ * @private
+ */
+shindig.container.GadgetHolder.prototype.updateUserPrefParams_ = function(uri) {
+  var userPrefs = this.renderParams_['userPrefs'];
+  if (userPrefs) {
+    for (var up in userPrefs) {
+      // Maybe more efficient to have a pre-compiled regex that looks for
+      // up_ANY_TEXT and match all instances.
+      var re = new RegExp('([&#?])up_' + up + '[^&#]*');
+      if (re) {
+        var key = encodeURIComponent('up_' + up);
+        var val = userPrefs[up];
+        if (val instanceof Array) {
+          val = val.join('|');
+        }
+        val = encodeURIComponent(val);
+        uri = uri.replace(re, '$1' + key + '=' + val);
+      }
+    }
+  }
+  return uri;
+};
+
+
+/**
+ * @return {string} The current RPC token.
+ * @private
+ */
+shindig.container.GadgetHolder.prototype.getRpcToken_ = function() {
+  return this.viewInfo_['iframePath'].match(/rpctoken=([^&]+)/)[1];
+};
+
+
+/**
+ * Adds a hash parameter to a URI.
+ * @param {string} uri The URI.
+ * @param {string} key The param key.
+ * @param {string} value The param value.
+ * @return {string} The new URI.
+ * @private
+ */
+shindig.container.GadgetHolder.prototype.addHashParam_ = function(uri, key, value) {
+  return uri + ((uri.indexOf('#') == -1) ? '#' : '&') + key + '=' + value;
+};
+
+
+/**
+ * Adds a query parameter to a URI.
+ * @param {string} uri The URI.
+ * @param {string} key The param key.
+ * @param {string} value The param value.
+ * @return {string} The new URI.
+ * @private
+ */
+shindig.container.GadgetHolder.prototype.addQueryParam_ = function(uri, key, value) {
+  var hasQuery = uri.indexOf('?') != -1;
+  var insertPos = (uri.indexOf('#') != -1) ? uri.indexOf('#') : uri.length;
+  return uri.substring(0, insertPos) + (hasQuery ? '&' : '?') +
+      key + '=' + value + uri.substring(insertPos);
+};

Added: shindig/trunk/features/src/main/javascript/features/container/gadget_site.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/gadget_site.js?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/gadget_site.js (added)
+++ shindig/trunk/features/src/main/javascript/features/container/gadget_site.js Thu May 27 01:03:10 2010
@@ -0,0 +1,404 @@
+/*
+ * 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 This manages rendering of gadgets in a place holder, within an
+ * HTML element in the container. The API for this is low-level. Use the
+ * container APIs to work with gadget sites.
+ */
+var shindig = shindig || {};
+shindig.container = shindig.container || {};
+
+
+/**
+ * @param {shindig.container.Service} service To fetch gadgets metadata, token.
+ * @param {Element} gadgetEl Element into which to render the gadget
+ * @param {Element=} opt_bufferEl Optional element for double buffering.
+ * @constructor
+ */
+shindig.container.GadgetSite = function(service, gadgetEl, opt_bufferEl) {
+  /**
+   * Service to fetch gadgets metadata and token.
+   * @type {shindig.container.Service}
+   * @private
+   */
+  this.service_ = service;
+
+  /**
+   * Element holding the current gadget.
+   * @type {Element}
+   * @private
+   */
+  this.curGadgetEl_ = gadgetEl;
+
+  /**
+   * Element holding the loading gadget for 2x buffering.
+   * @type {Element}
+   * @private
+   */
+  this.loadingGadgetEl_ = opt_bufferEl;
+
+  /**
+   * Unique ID of this site.
+   * @type {number}
+   * @private
+   */
+  this.id_ = shindig.container.GadgetSite.nextUniqueId_++;
+
+  /**
+   * ID of parent gadget.
+   * @type {string}
+   * @private
+   */
+  this.parentId_ = null;
+
+  /**
+   * Information about the currently visible gadget.
+   * @type {shindig.container.GadgetHolder}
+   * @private
+   */
+  this.curGadget_ = null;
+
+  /**
+   * Information about the currently loading gadget.
+   * @type {shindig.container.GadgetHolder}
+   * @private
+   */
+  this.loadingGadget_ = null;
+
+  this.onConstructed();
+};
+
+
+/**
+ * Callback that occurs after instantiation/construction of this. Override to
+ * provide your specific functionalities.
+ */
+shindig.container.GadgetSite.prototype.onConstructed = function() {};
+
+
+/**
+ * Set the height of the gadget iframe.
+ * @param {number} value The new height.
+ * @return {shindig.container.GadgetSite} This instance.
+ */
+shindig.container.GadgetSite.prototype.setHeight = function(value) {
+  var activeGadget = this.getActiveGadget();
+  if (activeGadget) {
+    var iframeEl = activeGadget.getIframeElement();
+    if (iframeEl) {
+      iframeEl.style.height = value + 'px';
+    }
+  }
+  return this;
+};
+
+
+/**
+ * Set the width of the gadget iframe.
+ * @param {number} value The new width.
+ * @return {shindig.container.GadgetSite} This instance.
+ */
+shindig.container.GadgetSite.prototype.setWidth = function(value) {
+  var activeGadget = this.getActiveGadget();
+  if (activeGadget) {
+    var iframeEl = activeGadget.getIframeElement();
+    if (iframeEl) {
+      iframeEl.style.width = value + 'px';
+    }
+  }
+  return this;
+};
+
+
+/**
+ * @param {string} value ID of parent element containing this site.
+ * @return {shindig.container.GadgetSite} This instance.
+ */
+shindig.container.GadgetSite.prototype.setParentId = function(value) {
+  this.parentId_ = value;
+  return this;
+};
+
+
+/**
+ * @return {number} The ID of this gadget site.
+ */
+shindig.container.GadgetSite.prototype.getId = function() {
+  return this.id_;
+};
+
+
+/**
+ * Returns the currently-active gadget, the loading gadget if a gadget is
+ * loading, or the currently visible gadget.
+ * @return {shindig.container.GadgetHolder} The gadget holder.
+ */
+shindig.container.GadgetSite.prototype.getActiveGadget = function() {
+  return this.loadingGadget_ || this.curGadget_;
+};
+
+
+/**
+ * Returns configuration of a feature with a given name. Defaults to current
+ * loading or visible gadget if no metadata is passed in.
+ * @param {string} name Name of the feature.
+ * @param {Object} opt_gadgetInfo Optional gadget info.
+ * @return {Object} JSON representing the feature.
+ */
+shindig.container.GadgetSite.prototype.getFeature = function(name, opt_gadgetInfo) {
+  var gadgetInfo = opt_gadgetInfo || this.getActiveGadget().getGadgetInfo();
+  return gadgetInfo['features'] && gadgetInfo['features'][name];
+};
+
+
+/**
+ * Returns the loading or visible gadget with the given ID.
+ * @param {string} id The iframe ID of gadget to return.
+ * @return {shindig.container.GadgetHolder} The gadget. Null, if not exist.
+ */
+shindig.container.GadgetSite.prototype.getGadgetHolder = function(id) {
+  if (this.curGadget_ && this.curGadget_.getIframeId() == id) {
+    return this.curGadget_;
+  }
+  if (this.loadingGadget_ && this.loadingGadget_.getIframeId() == id) {
+    return this.loadingGadget_;
+  }
+  return null;
+};
+
+
+/**
+ * @return {string} ID parent element containing this site.
+ */
+shindig.container.GadgetSite.prototype.getParentId = function() {
+  return this.parentId_;
+};
+
+
+/**
+ * Render a gadget in the site, by URI of the gadget XML.
+ * @param {string} gadgetUrl The absolute URL to gadget.
+ * @param {Object} gadgetParams View parameters for the gadget.
+ * @param {Object} renderParams. Render parameters for the gadget, including:
+ *     view, width, height.
+ * @param {Function=} opt_callback Function called with gadget info after
+ *     navigation has occurred.
+ */
+shindig.container.GadgetSite.prototype.navigateTo = function(gadgetUrl, gadgetParams,
+    renderParams, opt_callback) {
+  var callback = opt_callback || function() {};
+  var gadgetInfo = osapi.gadgets.getCachedMetadataInfo(gadgetUrl);
+
+  // If metadata has been loaded/cached.
+  if (gadgetInfo) {
+    this.render(gadgetInfo, gadgetParams, renderParams);
+    callback(gadgetInfo);
+
+  // Otherwise, fetch gadget metadata.
+  } else {
+    var request = {
+      'container': window.__CONTAINER,
+      'ids': [ gadgetUrl ]
+    };
+    var self = this;
+    this.service_.getGadgetMetadata(request, function(response) {
+      if (!response.error) {
+        var data = response.data;
+        var gadgetInfo = data[gadgetUrl];
+        self.render(gadgetInfo, gadgetParams, renderParams);
+        callback(response);
+      }
+    });
+  }
+};
+
+
+/**
+ * Render a gadget in this site, using a JSON gadget description.
+ * @param {Object} gadgetInfo the JSON gadget description.
+ * @param {Object} gadgetParams View parameters for the gadget.
+ * @param {Object} renderParams. Render parameters for the gadget, including:
+ *     view, width, height.
+ */
+shindig.container.GadgetSite.prototype.render = function(gadgetInfo, gadgetParams,
+    renderParams) {
+  var curUrl = this.curGadget_ ? this.curGadget_.getUrl() : null;
+
+  var previousView = null;
+  if (curUrl == gadgetInfo['url']) {
+    previousView = this.curGadget_.getView();
+  }
+
+  // Load into the double-buffer if there is one
+  var el = this.loadingGadgetEl_ || this.curGadgetEl_;
+  this.loadingGadget_ = new shindig.container.GadgetHolder(this.id_, el);
+
+  var view = renderParams['view'] || gadgetParams['view'] || previousView
+      || 'default';
+  var viewInfo = gadgetInfo['views'][view];
+
+  var delayLoad = this.getFeature('loadstate', gadgetInfo) ||
+      this.getFeature('shell', gadgetInfo);
+
+  var localRenderParams = {};
+  for (var key in renderParams) {
+    localRenderParams[key] = renderParams[key];
+  }
+
+  // Delay load for now means we autosize.
+  if (delayLoad) {
+    localRenderParams['height'] = '0';
+  }
+  localRenderParams['view'] = view;
+  localRenderParams['width'] = localRenderParams['width'] ||
+      viewInfo['preferredWidth'] || null;
+  localRenderParams['height'] = localRenderParams['height'] ||
+      viewInfo['preferredHeight'] || '150';
+
+  this.updateSecurityToken_(gadgetInfo, localRenderParams);
+
+  this.loadingGadget_.render(gadgetInfo, gadgetParams, localRenderParams);
+
+  this.loaded_ = false;
+
+  // Resize on load only if load is delayed. If immediate, height is 0
+  this.resizeOnLoad_ = delayLoad;
+
+  if (!delayLoad) {
+    this.setLoadState_('loaded');
+  }
+};
+
+
+/**
+ * Sends RPC call to the current/visible gadget.
+ * @param {string} serviceName RPC service name to call.
+ * @param {Function} callback Function to call upon RPC completion.
+ * @param {...number} var_args payload to pass to the recipient.
+ */
+shindig.container.GadgetSite.prototype.rpcCall = function(serviceName, callback, var_args) {
+  if (this.curGadget_) {
+    gadgets.rpc.call(this.curGadget_.getIframeId(), serviceName, callback, var_args);
+  }
+};
+
+
+/**
+ * If token has been fetched at least once, set the token to the most recent
+ * one. Otherwise, leave it.
+ * @param {Object} gadgetInfo The gadgetInfo used to update security token.
+ * @param {Object} renderParams. Render parameters for the gadget, including:
+ *     view, width, and height.
+ */
+shindig.container.GadgetSite.prototype.updateSecurityToken_
+    = function(gadgetInfo, renderParams) {
+  var tokenInfo = osapi.gadgets.getCachedTokenInfo(gadgetInfo['url']);
+  if (tokenInfo) {
+    var token = tokenInfo['token'];
+    this.loadingGadget_.setSecurityToken(token);
+  }
+};
+
+
+/**
+ * Close the gadget in this site. Removes the gadget elements from the
+ * containing document. Clients should only call this if they know it is OK
+ * for removal.
+ */
+shindig.container.GadgetSite.prototype.close = function() {
+  // Only remove the element (iframe) created by this, not by the container.
+  if (this.loadingGadgetEl_) {
+    this.loadingGadgetEl_.removeChild(this.loadingGadgetEl_.firstChild);
+  }
+  if (this.curGadgetEl_) {
+    this.curGadgetEl_.removeChild(this.curGadgetEl_.firstChild);
+  }
+  if (this.loadingGadget_) {
+    this.loadingGadget_.dispose();
+  }
+  if (this.curGadget_) {
+    this.curGadget_.dispose();
+  }
+};
+
+
+/**
+ * Unique ID of gadget site
+ * @type {number}
+ * @private
+ */
+shindig.container.GadgetSite.nextUniqueId_ = 0;
+
+
+/**
+ * Sets the load state of the currently loading / visible gadget.
+ * @param {string} state The current state.
+ * @private
+ */
+shindig.container.GadgetSite.prototype.setLoadState_ = function(state) {
+  if (!this.loaded_ && state == 'loaded') {
+    this.onload_();
+  }
+};
+
+
+/**
+ * Called when a gadget loads in the site. Uses double buffer, if present.
+ * @private
+ */
+shindig.container.GadgetSite.prototype.onload_ = function() {
+  this.loaded_ = true;
+  try {
+    gadgets.rpc.call(this.loadingGadget_.getIframeId(), 'onLoad', null);
+    if (this.resizeOnLoad_) {
+      this.setHeight();
+    }
+  } catch (e) {
+    // This can throw for same domain, although it shouldn't
+    gadgets.log(e);
+  }
+
+  this.swapBuffers_();
+
+  if (this.curGadget_) {
+    this.curGadget_.dispose();
+  }
+
+  this.curGadget_ = this.loadingGadget_;
+  this.loadingGadget_ = null;
+};
+
+
+/**
+ * Swap the double buffer elements, if there is a double buffer.
+ */
+shindig.container.GadgetSite.prototype.swapBuffers_ = function() {
+  // Only process double buffering if loading gadget exists
+  if (this.loadingGadgetEl_) {
+    this.loadingGadgetEl_.style.left = '';
+    this.loadingGadgetEl_.style.position = '';
+    this.curGadgetEl_.style.position = 'absolute';
+    this.curGadgetEl_.style.left = '-2000px';
+
+    // Swap references;  cur_ will now again be what's visible
+    var oldCur = this.curGadgetEl_;
+    this.curGadgetEl_ = this.loadingGadgetEl_;
+    this.loadingGadgetEl_ = oldCur;
+  }
+};

Added: shindig/trunk/features/src/main/javascript/features/container/init.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/init.js?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/init.js (added)
+++ shindig/trunk/features/src/main/javascript/features/container/init.js Thu May 27 01:03:10 2010
@@ -0,0 +1,78 @@
+/*
+ * 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 Initial configuration/boot-strapping work for common container
+ * to operate. This includes setting up gadgets config and global environment
+ * variables.
+ */
+(function() {
+
+  function initializeGadgetsConfig() {
+    gadgets.config.init({
+        'rpc': {
+          parentRelayUrl: ''
+        },
+        'core.io': {
+          jsonProxyUrl: 'http://%host%/gadgets/makeRequest',
+          proxyUrl: 'http://%host%/gadgets/proxy?refresh=%refresh%&container=%container%%rewriteMime%&gadget=%gadget%/%rawurl%'
+        }
+    });
+  }
+
+  function initializeGlobalVars() {
+    var scriptSrc = getLastScriptSrc();
+    if (scriptSrc) {
+      window.__API_HOST = shindig.container.util.parseOrigin(scriptSrc);
+      window.__API_PREFIX_PATH = shindig.container.util.parsePrefixPath(
+          scriptSrc, '/gadgets/js/container.js');
+      window.__CONTAINER = shindig.container.util.getParamValue(
+          scriptSrc, 'container');
+      window.__CONTAINER_HOST = shindig.container.util.parseOrigin(
+          document.location.href);
+    }
+  }
+
+  /**
+   * Call a callback function if specified in &onload= query param. This is
+   * required for dynamic source script inclusion, which is asynchronous.
+   */
+  function runOnloadCallback() {
+    var scriptSrc = getLastScriptSrc();
+    if (scriptSrc) {
+      var onload = shindig.container.util.getParamValue(scriptSrc, 'onload');
+      if (onload) {
+        var re = /(^[A-Za-z0-9_]+$)/;
+        if (re.test(onload) && (typeof window[onload] === "function")) {
+          window[onload]();
+        }
+      }
+    }
+  }
+
+  function getLastScriptSrc() {
+    var scriptEls = document.getElementsByTagName('script');
+    return (scriptEls.length > 0)
+        ? scriptEls[scriptEls.length - 1].src
+        : null;
+  }
+
+  initializeGadgetsConfig();
+  initializeGlobalVars();
+  runOnloadCallback();
+})();

Added: shindig/trunk/features/src/main/javascript/features/container/service.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/service.js?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/service.js (added)
+++ shindig/trunk/features/src/main/javascript/features/container/service.js Thu May 27 01:03:10 2010
@@ -0,0 +1,104 @@
+/*
+ * 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 This represents the service layer that talks to OSAPI
+ * endpoints. All RPC requests should go into this class.
+ */
+var shindig = shindig || {};
+shindig.container = shindig.container || {};
+
+
+/**
+ * @param {Object=} opt_config. Configuration JSON.
+ * @constructor
+ */
+shindig.container.Service = function(opt_config) {
+  var config = opt_config || {};
+
+  /**
+   * @type {boolean}
+   */
+  this.sameDomain_ = shindig.container.util.getSafeJsonValue(config,
+      shindig.container.ServiceConfig.SAME_DOMAIN, true);
+
+  this.onConstructed(config);
+};
+
+
+/**
+ * Callback that occurs after instantiation/construction of this. Override to
+ * provide your specific functionalities.
+ * @param {Object=} opt_config. Configuration JSON.
+ */
+shindig.container.Service.prototype.onConstructed = function(opt_config) {};
+
+
+/**
+ * Do an immediate fetch of gadgets metadata for gadgets in request.ids, for
+ * container request.container, with its results mutated by
+ * request.sameDomain and request.aspDomain. The appropriate optional
+ * callback opt_callback will be called, after a response is received.
+ * @param {Object} request JSON object representing the request.
+ * @param {Function=} opt_callback function to call upon data receive.
+ */
+shindig.container.Service.prototype.getGadgetMetadata = function(
+    request, opt_callback) {
+  var callback = opt_callback || function() {};
+  var self = this;
+  osapi.gadgets.getMetadata(request, function(response) {
+    if (response.error) {
+      // This hides internal server error.
+      callback({
+          error : 'Failed to retrieve gadget.',
+          errorCode : 'NOLOAD'
+      });
+    } else {
+      var data = response.data;
+      var gadgetUrls = shindig.container.util.toArrayOfJsonKeys(data);
+      for (var i = 0; i < gadgetUrls.length; i++) {
+        var gadgetInfo = data[gadgetUrls[i]];
+        self.processSameDomain_(gadgetInfo);
+      }
+      callback(response);
+    }
+  });
+};
+
+
+/**
+ * @param {Object} gadgetInfo
+ * @private
+ */
+shindig.container.Service.prototype.processSameDomain_ = function(gadgetInfo) {
+  gadgetInfo['sameDomain'] = this.sameDomain_;
+};
+
+
+// -----------------------------------------------------------------------------
+// Configuration
+// -----------------------------------------------------------------------------
+
+/**
+ * Enumeation of configuration keys for this service. This is specified in
+ * JSON to provide extensible configuration.
+ * @enum {string}
+ */
+shindig.container.ServiceConfig = {};
+//Toggle to render gadgets in the same domain.
+shindig.container.ServiceConfig.SAME_DOMAIN = 'sameDomain';

Added: shindig/trunk/features/src/main/javascript/features/container/util.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/util.js?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/util.js (added)
+++ shindig/trunk/features/src/main/javascript/features/container/util.js Thu May 27 01:03:10 2010
@@ -0,0 +1,138 @@
+/*
+ * 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 Utility methods common container.
+ */
+var shindig = shindig || {};
+shindig.container = shindig.container || {};
+shindig.container.util = shindig.container.util || {};
+
+
+/**
+ * Extract protocol and domain of container page. Valid values:
+ * http://www.cnn.com, chrome-extension://www.cnn.com
+ * @param {string} url The URL to extract path domain from.
+ * @return {string} The protocol and domain of container page.
+ */
+shindig.container.util.parseOrigin = function(uri) {
+  var indexAtStartOfAuthority = uri.indexOf('//') + 2;
+  var indexAtEndOfAuthority = uri.indexOf('/', indexAtStartOfAuthority);
+  return uri.substring(0, indexAtEndOfAuthority);
+};
+
+
+/**
+ * Extract prefix path of a URL, not including opt_postfixPath.
+ * @param {string} url The URL to extract path from.
+ * @param {string=} opt_postfixPath The URL postfix to avoid extracting.
+ * @return {string} The path in URL, before postfixPath.
+ */
+shindig.container.util.parsePrefixPath = function(uri, opt_postfixPath) {
+  var path = shindig.container.util.parsePath(uri);
+  if (path && opt_postfixPath) {
+    var endIndex = path.length - opt_postfixPath.length;
+    if (path.lastIndexOf(opt_postfixPath) == endIndex) {
+      return path.substring(0, endIndex);
+    }
+  }
+  return path;
+};
+
+/**
+ * Extract path of a URL.
+ * @param {string} url The URL to extract path from.
+ * @return {string} The path in URL.
+ */
+shindig.container.util.parsePath = function(uri) {
+  var match = uri.match(new RegExp("//[^/]+(/[^?#]*)"));
+  return match ? match[1] : null;
+};
+
+
+/**
+ * Extract the parameter value in path with name paramName.
+ * @param {string} path The path to extract parameter from.
+ * @param {string} paramName The name of parameter to exact value from.
+ * @return {string} The value of the parameter. Null otherwise.
+ */
+shindig.container.util.getParamValue = function(path, paramName) {
+  var match = path.match(new RegExp("[?&]" + paramName + "=([^&#]+)"));
+  return match ? match[1] : null;
+};
+
+
+/**
+ * Return value of json at key, if valid. Otherwise, return defaultValue.
+ * @param {Object} json The JSON to look up key param from.
+ * @param {string} key Key in config.
+ * @param {Object?} defaultValue The default value to return.
+ * @return {Object?}
+ */
+shindig.container.util.getSafeJsonValue = function(json, key, defaultValue) {
+  return (json[key] != undefined && json[key] != null)
+      ? json[key] : defaultValue;
+};
+
+
+/**
+ * Merge two JSON together. Keys in json2 will replace than in json1.
+ * @param {Object} json1 JSON to start merge with.
+ * @param {Object} json2 JSON to append/replace json1.
+ * @return {Object} the resulting JSON.
+ */
+shindig.container.util.mergeJsons = function(json1, json2) {
+  var result = {};
+  for (var key in json1) {
+    result[key] = json1[key];
+  }
+  for (var key in json2) {
+    result[key] = json2[key];
+  }
+  return result;
+};
+
+
+/**
+ * Extract keys from a JSON to an array.
+ * @param {Object} json to extract keys from.
+ * @return {array} keys in the json.
+ */
+shindig.container.util.toArrayOfJsonKeys = function(json) {
+  var result = [];
+  for (var key in json) {
+    result.push(key);
+  }
+  return result;
+};
+
+
+/**
+ * Count the number of own/self properties in json.
+ * @param {Object} json the JSON to act on.
+ * @return {number} Number of elements in json.
+ */
+shindig.container.util.countProperties = function(json) {
+  var count = 0;
+  for (var key in json) {
+    if (json.hasOwnProperty(key)) {
+      count++;
+    }
+  }
+  return count;
+};

Modified: shindig/trunk/features/src/main/javascript/features/features.txt
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/features.txt?rev=948646&r1=948645&r2=948646&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/features.txt (original)
+++ shindig/trunk/features/src/main/javascript/features/features.txt Thu May 27 01:03:10 2010
@@ -22,6 +22,7 @@ features/globals/feature.xml
 features/analytics/feature.xml
 features/auth-refresh/feature.xml
 features/caja/feature.xml
+features/container/feature.xml
 features/content-rewrite/feature.xml
 features/core.auth/feature.xml
 features/core.config/feature.xml
@@ -60,6 +61,7 @@ features/security-token/feature.xml
 features/setprefs/feature.xml
 features/settitle/feature.xml
 features/shindig.container/feature.xml
+features/shindig.container-1.0/feature.xml
 features/skins/feature.xml
 features/swfobject/feature.xml
 features/tabs/feature.xml

Added: shindig/trunk/features/src/main/javascript/features/shindig.container-1.0/feature.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/shindig.container-1.0/feature.xml?rev=948646&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/shindig.container-1.0/feature.xml (added)
+++ shindig/trunk/features/src/main/javascript/features/shindig.container-1.0/feature.xml Thu May 27 01:03:10 2010
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<feature>
+  <name>shindig.container-1.0</name>
+  <dependency>container</dependency>
+</feature>