You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by jo...@apache.org on 2010/04/01 05:26:30 UTC

svn commit: r929794 - in /shindig/trunk: features/ features/src/main/javascript/features/ features/src/main/javascript/features/core.io/ features/src/main/javascript/features/opensocial-templates/ features/src/main/javascript/features/xhrwrapper/ featu...

Author: johnh
Date: Thu Apr  1 03:26:28 2010
New Revision: 929794

URL: http://svn.apache.org/viewvc?rev=929794&view=rev
Log:
This feature allows to turn a type=url gadget which uses XMLHttpRequest into a
type=html/href= gadget, without having to modify it to use
gadgets.io.makeRequest.

Patch supplied by Jacobo Tarrio.

Taming to be done in a follow-up.


Added:
    shindig/trunk/features/src/main/javascript/features/xhrwrapper/
    shindig/trunk/features/src/main/javascript/features/xhrwrapper/feature.xml
    shindig/trunk/features/src/main/javascript/features/xhrwrapper/xhrwrapper.js
    shindig/trunk/features/src/test/javascript/features/xhrwrapper/
    shindig/trunk/features/src/test/javascript/features/xhrwrapper/xhrwrappertest.js
Modified:
    shindig/trunk/features/pom.xml
    shindig/trunk/features/src/main/javascript/features/core.io/io.js
    shindig/trunk/features/src/main/javascript/features/features.txt
    shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js
    shindig/trunk/features/src/test/javascript/features/alltests.js
    shindig/trunk/features/src/test/javascript/features/core.io/iotest.js
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java

Modified: shindig/trunk/features/pom.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/pom.xml?rev=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/features/pom.xml (original)
+++ shindig/trunk/features/pom.xml Thu Apr  1 03:26:28 2010
@@ -121,6 +121,7 @@
                 <source>i18n/numberformat.js</source>
                 <source>setprefs/setprefs.js</source>
                 <source>views/views.js</source>
+                <source>xhrwrapper/xhrwrapper.js</source>
                 <source>xmlutil/xmlutil.js</source>
                 <source>opensocial-data-context/datacontext.js</source>
                 <source>opensocial-data/data.js</source>

Modified: shindig/trunk/features/src/main/javascript/features/core.io/io.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/core.io/io.js?rev=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/core.io/io.js (original)
+++ shindig/trunk/features/src/main/javascript/features/core.io/io.js Thu Apr  1 03:26:28 2010
@@ -47,7 +47,11 @@ gadgets.io = function() {
    */
   function makeXhr() {
     var x; 
-    if (window.ActiveXObject) {
+    if (typeof shindig != 'undefined' &&
+        shindig.xhrwrapper &&
+        shindig.xhrwrapper.createXHR) {
+      return shindig.xhrwrapper.createXHR();
+    } else if (window.ActiveXObject) {
       x = new ActiveXObject("Msxml2.XMLHTTP");
       if (!x) {
         x = new ActiveXObject("Microsoft.XMLHTTP");

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=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/features.txt (original)
+++ shindig/trunk/features/src/main/javascript/features/features.txt Thu Apr  1 03:26:28 2010
@@ -63,4 +63,5 @@ features/skins/feature.xml
 features/swfobject/feature.xml
 features/tabs/feature.xml
 features/views/feature.xml
+features/xhrwrapper/feature.xml
 features/xmlutil/feature.xml

Modified: shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js?rev=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js (original)
+++ shindig/trunk/features/src/main/javascript/features/opensocial-templates/loader.js Thu Apr  1 03:26:28 2010
@@ -83,7 +83,11 @@ os.Loader.requestUrlXHR_ = function(url,
     return;
   }
   var req = null;
-  if (typeof(XMLHttpRequest) != "undefined") {
+  if (typeof shindig != 'undefined' &&
+      shindig.xhrwrapper &&
+      shindig.xhrwrapper.createXHR) {
+    req = shindig.xhrwrapper.createXHR();
+  } else if (typeof XMLHttpRequest != "undefined") {
     req = new XMLHttpRequest();
   } else {
     req = new ActiveXObject("MSXML2.XMLHTTP");

Added: shindig/trunk/features/src/main/javascript/features/xhrwrapper/feature.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/xhrwrapper/feature.xml?rev=929794&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/xhrwrapper/feature.xml (added)
+++ shindig/trunk/features/src/main/javascript/features/xhrwrapper/feature.xml Thu Apr  1 03:26:28 2010
@@ -0,0 +1,26 @@
+<?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>xhrwrapper</name>
+  <dependency>core.io</dependency>
+  <dependency>xmlutil</dependency>
+  <gadget>
+    <script src="xhrwrapper.js"/>
+  </gadget>
+</feature>

Added: shindig/trunk/features/src/main/javascript/features/xhrwrapper/xhrwrapper.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/xhrwrapper/xhrwrapper.js?rev=929794&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/xhrwrapper/xhrwrapper.js (added)
+++ shindig/trunk/features/src/main/javascript/features/xhrwrapper/xhrwrapper.js Thu Apr  1 03:26:28 2010
@@ -0,0 +1,391 @@
+/*
+ * 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 Emulate XMLHttpRequest using gadgets.io.makeRequest.
+ * 
+ * This is not a complete implementation of XMLHttpRequest:
+ * - synchronous send() is unsupported;
+ * - the callback function will not get full header information, as makeRequest
+ *   only provides the Set-Cookie and Location headers.
+ */
+
+shindig.xhrwrapper = shindig.xhrwrapper || {};
+
+(function () {
+
+  // Save the browser's XMLHttpRequest and ActiveXObject constructors.
+  var RealXMLHttpRequest = window.XMLHttpRequest;
+  var RealActiveXObject = window.ActiveXObject;
+
+  /**
+   * Creates a real XMLHttpRequest object.
+   * 
+   * This function is to be used by code that needs access to the browser's
+   * XMLHttpRequest functionality, such as the code that implements
+   * gadgets.io.makeRequest itself.
+   * 
+   * @return {Object|undefined} A XMLHttpRequest object, if one could
+   *     be created.
+   */
+  shindig.xhrwrapper.createXHR = function() {
+    var activeXIdents =
+        ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
+    if (typeof RealActiveXObject != 'undefined') {
+      for (var i = 0; i < activeXIdents.length; i++) {
+        try {
+          return new RealActiveXObject(activeXIdents[i]);
+        } catch (x) {
+          // do nothing, if none exists we'll do something later
+        }
+      }
+    }
+    if (typeof RealXMLHttpRequest != 'undefined') {
+      return new RealXMLHttpRequest();
+    }
+    return undefined;
+  };
+
+  /**
+   * @class XhrWrapper class.
+   * 
+   * @constructor
+   * @description Implements the XMLHttpRequest interface, using
+   *     gadgets.io.makeRequest to make the actual network accesses.
+   */
+  shindig.xhrwrapper.XhrWrapper = function() {
+    this.config_ = gadgets.config.get('shindig.xhrwrapper');
+
+    // XMLHttpRequest event listeners
+    this.onreadystatechange = null;
+
+    // XMLHttpRequest properties
+    this.readyState = 0;
+  };
+
+  /**
+   * Aborts the request if it has already been sent.
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.abort = function() {
+    this.aborted_ = true;
+  };
+
+  /**
+   * Returns all response headers as a string.
+   * 
+   * @return {string?} The text of all response headers, or null if no response
+   *     has been received.
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.getAllResponseHeaders = function() {
+    if (!this.responseHeaders_) {
+      return null;
+    }
+
+    var allHeaders = '';
+    for (var header in this.responseHeaders_) {
+      allHeaders += header + ': ' + this.responseHeaders_[header] + '\n';
+    }
+    return allHeaders;
+  };
+
+  /**
+   * Returns the value of a particular response header.
+   * 
+   * @param {string} The name of the header to return.
+   * @return {string?} The value of the header, or null if no response has
+   *     been received or the header doesn't exist in the response.
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.getResponseHeader = function(header) {
+    if (!this.responseHeaders_) {
+      return null;
+    }
+
+    var value = this.responseHeaders_[header.toLowerCase()];
+    return value ? value : null;
+  };
+
+  /**
+   * Initializes a request.
+   * 
+   * @param {string} method The HTTP method to use ('POST' or 'GET').
+   * @param {string} url The URL to which to send the request.
+   * @param {boolean=} opt_async Whether to perform the operation
+   *     asynchronously (defaults to true). Synchronous operations are not
+   *     supported, so it must presently be omitted or true.
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.open =
+      function(method, url, opt_async) {
+    this.method_ = method;
+    this.url_ = new Url(url);
+    this.aborted_ = false;
+    this.requestHeaders_ = {};
+    this.responseHeaders_ = {};
+
+    this.baseUrl_ = new Url(this.config_.contentUrl);
+
+    this.fixRequestUrl_();
+
+    if (!this.baseUrl_.hasSameOrigin(this.url_)) {
+      throw new Error('A gadget at ' + this.config_.contentUrl +
+                      ' tried to access ' + url + ' via XMLHttpRequest.');
+    }
+
+    if (opt_async === false) {
+      throw new Error('xhrwrapper does not support synchronous XHR.');
+    }
+
+    // XMLHttpRequest properties
+    this.multipart = false;
+    this.readyState = 1;
+    this.responseText = null;
+    this.responseXML = null;
+    this.status = 0;
+    this.statusText = null;
+  };
+
+  /**
+   * Sends the request.
+   * 
+   * @param {string=} opt_data The data used to populate the body of a POST
+   *     request.
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.send = function(opt_data) {
+    this.aborted_ = false;
+    var that = this;
+    var params = {};
+    params[gadgets.io.RequestParameters.METHOD] = this.method_;
+    params[gadgets.io.RequestParameters.HEADERS] = this.requestHeaders_;
+    params[gadgets.io.RequestParameters.POST_DATA] = opt_data;
+    gadgets.io.makeRequest(this.url_.toString(),
+                           function(response) { that.callback_(response); },
+                           params);
+  };
+
+  /**
+   * Sets the value of an HTTP request header.
+   * 
+   * @param {string} header The name of the header to set.
+   * @param {string} value The value for the header.
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.setRequestHeader =
+      function(header, value) {
+    this.requestHeaders_[header] = value;
+  };
+
+  /**
+   * Processes the results from makeRequest and calls onreadystatechange.
+   * 
+   * @param {Object} response The response from makeRequest.
+   * @private
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.callback_ = function(response) {
+    if (this.aborted_) {
+      return;
+    }
+    this.readyState = 4;
+    this.responseHeaders_ = response.headers;
+    this.responseText = response.text;
+    try {
+      this.responseXML = opensocial.xmlutil.parseXML(response.text);
+    } catch (x) {
+      this.responseXML = null;
+    }
+    this.status = response.rc;
+    if (response.errors) {
+      this.statusText = response.errors[0];
+    }
+    if (this.onreadystatechange) {
+      var event = {};
+      event.type = 'readystatechange';
+      event.srcElement = this;
+      event.target = this;
+      this.onreadystatechange(event);
+    }
+  };
+
+  /**
+   * Points the request URL to the correct server.
+   * 
+   * If the URL is pointing to the gadget server, this function assumes the
+   * gadget's author wanted to point to the gadget contents location and
+   * changes it so that it points to the right place.
+   * 
+   * For example, if the gadget is rendered in https://shindig/gadgets/ifr
+   * and the gadget's contents are at http://foo.com/bar/baz.html:
+   * 
+   * - foo.xml gets turned into http://foo.com/bar/foo.xml
+   * - /foo/bar.xml gets turned into http://foo.com/foo/bar.xml
+   * - //foo.com/bar.xml gets turned into http://foo.com/bar.xml
+   * - http://foo.com/bar.xml is untouched
+   * - https://shindig/bar.xml is turned into http://foo.com/bar.xml
+   * - https://shindig/gadgets/bar.xml is turned into
+   *     http://foo.com/bar/bar.xml
+   */
+  shindig.xhrwrapper.XhrWrapper.prototype.fixRequestUrl_ = function() {
+    this.url_.fullyQualify(this.baseUrl_);
+    var loc = new Url(window.location.href);
+    if (this.url_.hasSameOrigin(loc)) {
+      this.url_.schema = this.baseUrl_.schema;
+      this.url_.authority = this.baseUrl_.authority;
+      var pathLen = loc.path.length;
+      if (this.url_.path.substr(0, pathLen) == loc.path) {
+        this.url_.path = this.baseUrl_.path + this.url_.path.substr(pathLen);
+      }
+    }
+  };
+
+  /**
+   * @class A class for processing URLs.
+   * 
+   * @constructor
+   * @description Pries apart the components of a URL, so it can be sliced
+   * and diced and combined with other URLs as needed.
+   */
+  function Url(url) {
+    this.schema = "";
+    this.authority = "";
+    this.path = "";
+    this.filename = "";
+    this.query = "";
+    this.fragment = "";
+
+    var parse = url;
+    var sharp = parse.indexOf('#');
+    if (sharp != -1) {
+      this.fragment = parse.substr(sharp);
+      parse = parse.substr(0, sharp);
+    }
+    var question = parse.indexOf('?');
+    if (question != -1) {
+      this.query = parse.substr(question);
+      parse = parse.substr(0, question);
+    }
+    var doubleSlash = parse.indexOf('//');
+    if (doubleSlash != -1) {
+      this.schema = parse.substr(0, doubleSlash);
+      parse = parse.substr(doubleSlash + 2);
+      var firstSlash = parse.indexOf('/');
+      if (firstSlash != -1) {
+        this.authority = parse.substr(0, firstSlash);
+        parse = parse.substr(firstSlash);
+      } else {
+        this.authority = parse;
+        parse = '';
+      }
+    }
+    var lastSlash = parse.lastIndexOf('/');
+    if (lastSlash != -1) {
+      this.path = parse.substr(0, lastSlash + 1);
+      parse = parse.substr(lastSlash + 1);
+    }
+    this.filename = parse;
+  };
+
+  /**
+   * Checks that a URL has the same origin as this URL.
+   *
+   * Two URLs have the same origin if they point to the same schema, server
+   * and port.
+   *
+   * @param {Url} other The URL to compare to this URL.
+   * @return {boolean} Whether the URLs have the same origin.
+   */
+  Url.prototype.hasSameOrigin = function(other) {
+    return this.schema == other.schema && this.authority == other.authority;
+  };
+
+  /**
+   * Fully qualifies this URL if it is relative, using a given base URL.
+   * 
+   * @param {Url} base The base URL.
+   */
+  Url.prototype.fullyQualify = function(base) {
+    if (this.schema == '') {
+      this.schema = base.schema;
+    }
+    if (this.authority == '') {
+      this.authority = base.authority;
+      if (this.path == '' || this.path[0] != '/') {
+        this.path = base.path + this.path;
+      }
+    }
+  };
+
+  /**
+   * Returns a readable representation of the URL.
+   * 
+   * @return {string} A readable URL.
+   */
+  Url.prototype.toString = function() {
+    var url = "";
+    if (this.schema) {
+      url += this.schema;
+    }
+    if (this.authority) {
+      url += '//' + this.authority;
+    }
+    if (this.path) {
+      url += this.path;
+    }
+    if (this.filename) {
+      url += this.filename;
+    }
+    if (this.query) {
+      url += this.query;
+    }
+    if (this.fragment) {
+      url += this.fragment;
+    }
+    return url;
+  };
+
+  /**
+   * Acts as a drop-in replacement for IE's ActiveXObject.
+   * @param {string} className The name of the class to create.
+   */
+  function ActiveXObjectReplacement(className) {
+    var obj;
+    if (typeof className == 'string' &&
+        (className.substr(0, 14).toLowerCase() == 'msxml2.xmlhttp' ||
+         className.toLowerCase() == 'microsoft.xmlhttp')) {
+      obj = new shindig.xhrwrapper.XhrWrapper();
+    } else {
+      obj = new RealActiveXObject(className);
+    }
+    for (var f in obj) {
+      this[f] = obj[f];
+    }
+  };
+
+  // Replace the browser's XMLHttpRequest and ActiveXObject constructors with
+  // xhrwrapper's.
+  if (window.XMLHttpRequest) {
+    window.XMLHttpRequest = shindig.xhrwrapper.XhrWrapper;
+  }
+  if (window.ActiveXObject) {
+    window.ActiveXObject = ActiveXObjectReplacement;
+  }
+
+  var config = {
+    contentUrl: gadgets.config.NonEmptyStringValidator
+  };
+  gadgets.config.register('shindig.xhrwrapper', config);
+
+})();
+

Modified: shindig/trunk/features/src/test/javascript/features/alltests.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/alltests.js?rev=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/features/src/test/javascript/features/alltests.js (original)
+++ shindig/trunk/features/src/test/javascript/features/alltests.js Thu Apr  1 03:26:28 2010
@@ -43,6 +43,7 @@ if (!this.JsUtil) {
   eval(JsUtil.prototype.include(srcDir + '/core/log.js'));
   eval(JsUtil.prototype.include(srcDir + '/core.io/io.js'));
   eval(JsUtil.prototype.include(srcDir + '/views/views.js'));
+  eval(JsUtil.prototype.include(srcDir + '/xhrwrapper/xhrwrapper.js'));
   eval(JsUtil.prototype.include(srcDir + '/opensocial-reference/opensocial.js'));
   eval(JsUtil.prototype.include(srcDir + '/opensocial-reference/activity.js'));
   eval(JsUtil.prototype.include(srcDir + '/opensocial-reference/address.js'));
@@ -91,6 +92,7 @@ if (!this.JsUtil) {
   eval(JsUtil.prototype.include(testSrcDir + "/osapi/batchtest.js"));
   eval(JsUtil.prototype.include(testSrcDir + "/osapi/jsonrpctransporttest.js"));
   eval(JsUtil.prototype.include(testSrcDir + "/views/urltemplatetest.js"));
+  eval(JsUtil.prototype.include(testSrcDir + "/xhrwrapper/xhrwrappertest.js"));
 }
 
 function AllTests() {

Modified: shindig/trunk/features/src/test/javascript/features/core.io/iotest.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/core.io/iotest.js?rev=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/features/src/test/javascript/features/core.io/iotest.js (original)
+++ shindig/trunk/features/src/test/javascript/features/core.io/iotest.js Thu Apr  1 03:26:28 2010
@@ -34,7 +34,9 @@ IoTest.prototype.setUp = function() {
 
   this.fakeXhrs = new fakeXhr.Factory(this);
   this.oldXMLHttpRequest = window.XMLHTTPRequest;
+  this.oldXhrWrapper = shindig.xhrwrapper;
   window.XMLHttpRequest = this.fakeXhrs.getXhrConstructor();
+  shindig.xhrwrapper = undefined;
 
   gadgets.config.init({ "core.io" : {
       "proxyUrl" : "http://example.com/proxy?url=%url%&refresh=%refresh%&g=%gadget%&c=%container%",
@@ -52,6 +54,7 @@ IoTest.prototype.setSchemaless = functio
 IoTest.prototype.tearDown = function() {
   gadgets.util.getUrlParameters = this.oldGetUrlParameters;
   window.XMLHttpRequest = this.oldXMLHTTPRequest;
+  shindig.xhrwrapper = this.oldXhrWrapper;
 };
 
 IoTest.prototype.testGetProxyUrl = function() {

Added: shindig/trunk/features/src/test/javascript/features/xhrwrapper/xhrwrappertest.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/xhrwrapper/xhrwrappertest.js?rev=929794&view=auto
==============================================================================
--- shindig/trunk/features/src/test/javascript/features/xhrwrapper/xhrwrappertest.js (added)
+++ shindig/trunk/features/src/test/javascript/features/xhrwrapper/xhrwrappertest.js Thu Apr  1 03:26:28 2010
@@ -0,0 +1,191 @@
+/*
+ * 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
+ *
+ * Unittests for the xhrwrapper feature.
+ */
+
+var shindig = shindig || {};
+
+function XhrWrapperTest(name) {
+  TestCase.call(this, name);
+}
+XhrWrapperTest.inherits(TestCase);
+
+XhrWrapperTest.prototype.setUp = function() {
+  // prepare mocks
+  gadgets.io = gadgets.io || {};
+  window.location = window.location || {};
+  opensocial.xmlutil = opensocial.xmlutil || {};
+  this.oldMakeRequest = gadgets.io.makeRequest;
+  this.oldWindowLocation = window.location;
+  this.oldParseXML = opensocial.xmlutil.parseXML;
+  this.madeRequest = {};
+  gadgets.io.makeRequest = this.mockMakeRequest(this.madeRequest);
+  window.location = { 'href': 'http://shindig/gadgets/ifr?url=blah' };
+  opensocial.xmlutil.parseXML = XhrWrapperTest.mockParseXML;
+  gadgets.config.init(
+    {"shindig.xhrwrapper": {"contentUrl": "http://foo.bar/baz/bax.html"}});
+};
+
+XhrWrapperTest.prototype.tearDown = function() {
+  // remove mocks
+  gadgets.io.makeRequest = this.oldMakeRequest;
+  window.location = this.oldWindowLocation;
+  opensocial.xmlutil.parseXML = this.oldParseXML;
+};
+
+XhrWrapperTest.prototype.testBasicWorking = function() {
+  var that = this;
+  var calledCallback = false;
+  var xhr = new shindig.xhrwrapper.XhrWrapper();
+  xhr.open('GET', 'http://foo.bar');
+  xhr.onreadystatechange = function(e) {
+    that.assertEquals('readystatechange', e.type);
+    that.assertEquals(xhr, e.target);
+    calledCallback = true;
+  };
+  xhr.send();
+
+  this.madeRequest.doCallback();
+
+  this.checkRequest('GET', 'http://foo.bar');
+  this.assertTrue(calledCallback);
+  this.assertEquals(4, xhr.readyState);
+  this.assertEquals('some text', xhr.responseText);
+  this.assertEquals('this would normally be XML', xhr.responseXML);
+  this.assertEquals(200, xhr.status);
+  this.assertEquals('no error', xhr.statusText);
+  this.assertEquals('v1', xhr.getResponseHeader('h1'));
+  this.assertEquals('v2', xhr.getResponseHeader('h2'));
+  this.assertEquals('h1: v1\nh2: v2\n', xhr.getAllResponseHeaders());
+};
+
+XhrWrapperTest.prototype.testAddRequestHeaders = function() {
+  var xhr = new shindig.xhrwrapper.XhrWrapper();
+  xhr.open('GET', 'http://foo.bar');
+  xhr.setRequestHeader('header', 'value');
+  xhr.send();
+
+  this.assertEquals('value', this.madeRequest.params.HEADERS['header']);
+};
+
+XhrWrapperTest.prototype.testSameOriginViolation = function() {
+  var thrown;
+  var xhr;
+
+  // Different schema
+  gadgets.config.init(
+    {"shindig.xhrwrapper": {"contentUrl": "https://foo.bar/baz/bax.html"}});  
+  xhr = new shindig.xhrwrapper.XhrWrapper();
+  try {
+    xhr.open('GET', 'http://foo.bar/thing');
+    thrown = false;
+  } catch (x) {
+    thrown = true;
+  }
+  this.assertTrue('Should have thrown an error.', thrown);
+
+  // Different authority
+  gadgets.config.init(
+    {"shindig.xhrwrapper": {"contentUrl": "http://baw.net/bax.html"}});  
+  xhr = new shindig.xhrwrapper.XhrWrapper();
+  try {
+    xhr.open('GET', 'http://foo.bar/thing');
+    thrown = false;
+  } catch (x) {
+    thrown = true;
+  }
+  this.assertTrue('Should have thrown an error.', thrown);
+
+  // Same schema and authority
+  gadgets.config.init(
+    {"shindig.xhrwrapper": {"contentUrl": "http://foo.bar/some/bax.html"}});
+  xhr = new shindig.xhrwrapper.XhrWrapper();
+  try {
+    xhr.open('GET', 'http://foo.bar/thing');
+    thrown = false;
+  } catch (x) {
+    thrown = true;
+  }
+  this.assertFalse('Should not have thrown an error.', thrown);
+};
+
+XhrWrapperTest.prototype.testResolveRelativeUrl = function() {
+  var xhr;
+
+  // Only path provided
+  xhr = new shindig.xhrwrapper.XhrWrapper();
+  xhr.open('GET', '/foo/bar/baz.xml');
+  xhr.send();
+  this.checkRequest('GET', 'http://foo.bar/foo/bar/baz.xml');
+
+  // Schema missing
+  xhr = new shindig.xhrwrapper.XhrWrapper();
+  xhr.open('GET', '//foo.bar/baz.xml');
+  xhr.send();
+  this.checkRequest('GET', 'http://foo.bar/baz.xml');
+};
+
+XhrWrapperTest.prototype.testRepointWrongUrls = function() {
+  var xhr;
+
+  // Only schema and hostname match
+  xhr = new shindig.xhrwrapper.XhrWrapper();
+  xhr.open('GET', 'http://shindig/foo/bar/baz.xml');
+  xhr.send();
+  this.checkRequest('GET', 'http://foo.bar/foo/bar/baz.xml');
+
+  // Schema, hostname and first part of path match
+  xhr = new shindig.xhrwrapper.XhrWrapper();
+  xhr.open('GET', 'http://shindig/gadgets/foo/bar/baz.xml');
+  xhr.send();
+  this.checkRequest('GET', 'http://foo.bar/baz/foo/bar/baz.xml');
+};
+
+XhrWrapperTest.prototype.mockMakeRequest = function(info) {
+  var that = this;
+  return function(url, callback, opt_params) {
+    info.url = url;
+    info.callback = callback;
+    info.params = opt_params;
+    info.doCallback = function() {
+      var response = {
+        data: 'some data',
+        errors: [ 'no error' ],
+        headers: { h1: 'v1', h2: 'v2' },
+        rc: 200,
+        text: 'some text'
+      };
+      info.callback.call(null, response);
+    };
+  };
+};
+
+XhrWrapperTest.mockParseXML = function(t) {
+  return 'this would normally be XML';
+};
+
+XhrWrapperTest.prototype.checkRequest = function(method, url) {
+  this.assertEquals(method, this.madeRequest.params['METHOD']);
+  this.assertEquals(url, this.madeRequest.url);
+};
+

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java?rev=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriter.java Thu Apr  1 03:26:28 2010
@@ -379,9 +379,20 @@ public class RenderingGadgetRewriter imp
     addHasFeatureConfig(gadget, config);
     addOsapiSystemListMethodsConfig(context.getContainer(), config, context.getHost());
     addSecurityTokenConfig(context, config);
+    addXhrWrapperConfig(gadget, config);
     return "gadgets.config.init(" + JsonSerializer.serialize(config) + ");\n";
   }
 
+  private void addXhrWrapperConfig(Gadget gadget, Map<String, Object> config) {
+    boolean isUsingXhrWrapper = gadget.getAllFeatures().contains("xhrwrapper");
+    if (isUsingXhrWrapper) {
+      Map<String, String> xhrWrapperConfig = Maps.newHashMapWithExpectedSize(2);
+      Uri contentsUri = gadget.getCurrentView().getHref();
+      xhrWrapperConfig.put("contentUrl", contentsUri == null ? "" : contentsUri.toString());
+      config.put("shindig.xhrwrapper", xhrWrapperConfig);
+    }
+  }
+
   private void addSecurityTokenConfig(GadgetContext context, Map<String, Object> config) {
     SecurityToken authToken = context.getToken();
     if (authToken != null) {

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java?rev=929794&r1=929793&r2=929794&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingGadgetRewriterTest.java Thu Apr  1 03:26:28 2010
@@ -117,7 +117,8 @@ public class RenderingGadgetRewriterTest
     Gadget gadget = new Gadget()
         .setContext(context)
         .setPreloads(ImmutableList.<PreloadedData>of())
-        .setSpec(spec);
+        .setSpec(spec)
+        .setGadgetFeatureRegistry(featureRegistry);
     // Convenience: by default expect no features requested, by gadget or extern.
     // expectFeatureCalls(...) resets featureRegistry if called again.
     expectFeatureCalls(gadget,
@@ -617,6 +618,41 @@ public class RenderingGadgetRewriterTest
     assertEquals("", defaultsJson.get("pref_two"));
   }
 
+  @Test
+  public void xhrWrapperConfigurationInjected() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='xhrwrapper' />" +
+      "</ModulePrefs>" +
+      "<Content type='html' href='http://foo.com/bar/baz.html' />" +
+      "</Module>";
+    
+    Gadget gadget = makeGadgetWithSpec(gadgetXml);
+    gadget.setCurrentView(gadget.getSpec().getView("default"));
+    
+    String rewritten = rewrite(gadget, BODY_CONTENT);
+    
+    boolean containsConfig =
+        rewritten.contains("\"shindig.xhrwrapper\":{\"contentUrl\":\"http://foo.com/bar/baz.html\"}");
+    assertTrue("No shindig.xhrwrapper configuration present in rewritten HTML.", containsConfig);
+  }
+
+  @Test
+  public void xhrWrapperConfigurationNotInjectedIfUnnecessary() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title='' />" +
+      "<Content type='html' href='http://foo.com/bar/baz.html' />" +
+      "</Module>";
+    
+    Gadget gadget = makeGadgetWithSpec(gadgetXml);
+    gadget.setCurrentView(gadget.getSpec().getView("default"));
+    
+    String rewritten = rewrite(gadget, BODY_CONTENT);
+    
+    boolean containsConfig = rewritten.contains("\"shindig.xhrwrapper\"");
+    assertFalse("shindig.xhrwrapper configuration present in rewritten HTML.", containsConfig);
+  }
+
   @Test(expected = RewritingException.class)
   public void unsupportedFeatureThrows() throws Exception {
     String gadgetXml =
@@ -683,6 +719,8 @@ public class RenderingGadgetRewriterTest
         .andReturn(ImmutableList.<FeatureResource>of());
     expect(featureRegistry.getFeatures(eq(ImmutableList.of("core", "bar"))))
         .andReturn(ImmutableList.of("core"));
+    expect(featureRegistry.getFeatures(eq(ImmutableList.of("core"))))
+        .andReturn(ImmutableList.of("core"));
     replay(featureRegistry);
     
     rewrite(gadget, "");
@@ -843,7 +881,8 @@ public class RenderingGadgetRewriterTest
     GadgetContext gadgetContext = gadget.getContext();
     List<String> gadgetFeatures = Lists.newArrayList(gadget.getDirectFeatureDeps());
     List<String> allFeatures = Lists.newArrayList(gadgetFeatures);
-    allFeatures.addAll(externLibs);
+    List<String> allFeaturesAndLibs = Lists.newArrayList(gadgetFeatures);
+    allFeaturesAndLibs.addAll(externLibs);
     List<String> emptyList = Lists.newArrayList();
     expect(featureRegistry.getFeatureResources(same(gadgetContext), eq(externLibs), eq(emptyList)))
         .andReturn(externResources);
@@ -851,6 +890,8 @@ public class RenderingGadgetRewriterTest
         .andReturn(gadgetResources);
     expect(featureRegistry.getFeatures(eq(allFeatures)))
         .andReturn(allFeatures);
+    expect(featureRegistry.getFeatures(eq(allFeaturesAndLibs)))
+        .andReturn(allFeaturesAndLibs);
     replay(featureRegistry);
   }