# lib/webdriver/http/corsclient.js

    1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.1415goog.provide('webdriver.http.CorsClient');1617goog.require('goog.json');18goog.require('webdriver.http.Response');19202122/**23 * Communicates with a WebDriver server, which may be on a different domain,24 * using the <a href="http://www.w3.org/TR/cors/">cross-origin resource sharing25 * </a> (CORS) extension to WebDriver's JSON wire protocol.26 *27 * <p>Each command from the standard JSON protocol will be encoded in a28 * JSON object with the following form:29 * {method:string, path:string, data:!Object}30 *31 * <p>The encoded command is then sent as a POST request to the server's /xdrpc32 * endpoint.  The server will decode the command, re-route it to the appropriate33 * handler, and then return the command's response as a standard JSON response34 * object.  The JSON responses will <em>always</em> be returned with a 20035 * response from the server; clients must rely on the response's "status" field36 * to determine whether the command succeeded.37 *38 * <p>This client cannot be used with the standard wire protocol due to39 * limitations in the various browser implementations of the CORS specification:40 * <ul>41 *   <li>IE's <a href="http://goo.gl/6l3kA">XDomainRequest</a> object is only42 *     capable of generating the types of requests that may be generated through43 *     a standard <a href="http://goo.gl/vgzAU">HTML form</a> - it can not send44 *     DELETE requests, as is required in the wire protocol.45 *   <li>WebKit's implementation of CORS does not follow the spec and forbids46 *     redirects: https://bugs.webkit.org/show_bug.cgi?id=5760047 *     This limitation appears to be intentional and is documented in WebKit's48 *     Layout tests:49 *     //LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects.html50 *   <li>If the server does not return a 2xx response, IE and Opera's51 *     implementations will fire the XDomainRequest/XMLHttpRequest object's52 *     onerror handler, but without the corresponding response text returned by53 *     the server. This renders IE and Opera incapable of handling command54 *     failures in the standard JSON protocol.55 * </ul>56 *57 * @param {string} url URL for the WebDriver server to send commands to.58 * @constructor59 * @implements {webdriver.http.Client}60 * @see <a href="http://www.w3.org/TR/cors/">CORS Spec</a>61 * @see <a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol">62 *     JSON wire protocol</a>63 */64webdriver.http.CorsClient = function(url) {65  if (!webdriver.http.CorsClient.isAvailable()) {66    throw Error('The current environment does not support cross-origin ' +67        'resource sharing');68  }6970  /** @private {string} */71  this.url_ = url + webdriver.http.CorsClient.XDRPC_ENDPOINT;72};737475/**76 * Resource URL to send commands to on the server.77 * @type {string}78 * @const79 */80webdriver.http.CorsClient.XDRPC_ENDPOINT = '/xdrpc';818283/**84 * Tests whether the current environment supports cross-origin resource sharing.85 * @return {boolean} Whether cross-origin resource sharing is supported.86 * @see http://www.w3.org/TR/cors/87 */88webdriver.http.CorsClient.isAvailable = function() {89  return typeof XDomainRequest !== 'undefined' ||90      (typeof XMLHttpRequest !== 'undefined' &&91          goog.isBoolean(new XMLHttpRequest().withCredentials));92};939495/** @override */96webdriver.http.CorsClient.prototype.send = function(request, callback) {97  try {98    var xhr = new (typeof XDomainRequest !== 'undefined' ?99        XDomainRequest : XMLHttpRequest);100    xhr.open('POST', this.url_, true);101102    xhr.onload = function() {103      callback(null, webdriver.http.Response.fromXmlHttpRequest(104          /** @type {!XMLHttpRequest} */ (xhr)));105    };106107    var url = this.url_;108    xhr.onerror = function() {109      callback(Error([110        'Unable to send request: POST ', url,111        '\nPerhaps the server did not respond to the preflight request ',112        'with valid access control headers?'113      ].join('')));114    };115116    // Define event handlers for all events on the XDomainRequest. Apparently,117    // if we don't do this, IE9+10 will silently abort our request. Yay IE.118    // Note, we're not using goog.nullFunction, because it tends to get119    // optimized away by the compiler, which leaves us where we were before.120    xhr.onprogress = xhr.ontimeout = function() {};121122    xhr.send(goog.json.serialize({123      'method': request.method,124      'path': request.path,125      'data': request.data126    }));127  } catch (ex) {128    callback(ex);129  }130};