| // 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); |
| } |
| }; |