blob: 5aaac40c41bf117d014d3914a11c591ec49baa24 [file] [log] [blame]
// 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.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 and Opera's
* implementations will fire the XDomainRequest/XMLHttpRequest object's
* onerror handler, but without the corresponding response text returned by
* the server. This renders IE and Opera 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');
}
/**
* @type {string}
* @private
*/
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);
}
};