--- /dev/null
+// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
+//
+// Licensed 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.
+
+goog.provide('webdriver.http.CorsClient');
+
+goog.require('goog.json');
+goog.require('webdriver.http.Client');
+goog.require('webdriver.http.Response');
+
+
+
+/**
+ * Communicates with a WebDriver server, which may be on a different domain,
+ * using the <a href="http://www.w3.org/TR/cors/">cross-origin resource sharing
+ * </a> (CORS) extension to WebDriver's JSON wire protocol.
+ *
+ * <p>Each command from the standard JSON protocol will be encoded in a
+ * JSON object with the following form:
+ * {method:string, path:string, data:!Object}
+ *
+ * <p>The encoded command is then sent as a POST request to the server's /xdrpc
+ * endpoint. The server will decode the command, re-route it to the appropriate
+ * handler, and then return the command's response as a standard JSON response
+ * object. The JSON responses will <em>always</em> be returned with a 200
+ * response from the server; clients must rely on the response's "status" field
+ * to determine whether the command succeeded.
+ *
+ * <p>This client cannot be used with the standard wire protocol due to
+ * limitations in the various browser implementations of the CORS specification:
+ * <ul>
+ * <li>IE's <a href="http://goo.gl/6l3kA">XDomainRequest</a> object is only
+ * capable of generating the types of requests that may be generated through
+ * a standard <a href="http://goo.gl/vgzAU">HTML form</a> - it can not send
+ * DELETE requests, as is required in the wire protocol.
+ * <li>WebKit's implementation of CORS does not follow the spec and forbids
+ * redirects: https://bugs.webkit.org/show_bug.cgi?id=57600
+ * This limitation appears to be intentional and is documented in WebKit's
+ * Layout tests:
+ * //LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects.html
+ * <li>If the server does not return a 2xx response, IE
+ * implementation will fire the XDomainRequest/XMLHttpRequest object's
+ * onerror handler, but without the corresponding response text returned by
+ * the server. This renders IE incapable of handling command
+ * failures in the standard JSON protocol.
+ * </ul>
+ *
+ * @param {string} url URL for the WebDriver server to send commands to.
+ * @constructor
+ * @implements {webdriver.http.Client}
+ * @see <a href="http://www.w3.org/TR/cors/">CORS Spec</a>
+ * @see <a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol">
+ * JSON wire protocol</a>
+ */
+webdriver.http.CorsClient = function(url) {
+ if (!webdriver.http.CorsClient.isAvailable()) {
+ throw Error('The current environment does not support cross-origin ' +
+ 'resource sharing');
+ }
+
+ /** @private {string} */
+ this.url_ = url + webdriver.http.CorsClient.XDRPC_ENDPOINT;
+};
+
+
+/**
+ * Resource URL to send commands to on the server.
+ * @type {string}
+ * @const
+ */
+webdriver.http.CorsClient.XDRPC_ENDPOINT = '/xdrpc';
+
+
+/**
+ * Tests whether the current environment supports cross-origin resource sharing.
+ * @return {boolean} Whether cross-origin resource sharing is supported.
+ * @see http://www.w3.org/TR/cors/
+ */
+webdriver.http.CorsClient.isAvailable = function() {
+ return typeof XDomainRequest !== 'undefined' ||
+ (typeof XMLHttpRequest !== 'undefined' &&
+ goog.isBoolean(new XMLHttpRequest().withCredentials));
+};
+
+
+/** @override */
+webdriver.http.CorsClient.prototype.send = function(request, callback) {
+ try {
+ var xhr = new (typeof XDomainRequest !== 'undefined' ?
+ XDomainRequest : XMLHttpRequest);
+ xhr.open('POST', this.url_, true);
+
+ xhr.onload = function() {
+ callback(null, webdriver.http.Response.fromXmlHttpRequest(
+ /** @type {!XMLHttpRequest} */ (xhr)));
+ };
+
+ var url = this.url_;
+ xhr.onerror = function() {
+ callback(Error([
+ 'Unable to send request: POST ', url,
+ '\nPerhaps the server did not respond to the preflight request ',
+ 'with valid access control headers?'
+ ].join('')));
+ };
+
+ // Define event handlers for all events on the XDomainRequest. Apparently,
+ // if we don't do this, IE9+10 will silently abort our request. Yay IE.
+ // Note, we're not using goog.nullFunction, because it tends to get
+ // optimized away by the compiler, which leaves us where we were before.
+ xhr.onprogress = xhr.ontimeout = function() {};
+
+ xhr.send(goog.json.serialize({
+ 'method': request.method,
+ 'path': request.path,
+ 'data': request.data
+ }));
+ } catch (ex) {
+ callback(ex);
+ }
+};