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 at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// 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 and
13// limitations under the License.
14
15goog.provide('webdriver.http.CorsClient');
16
17goog.require('goog.json');
18goog.require('webdriver.http.Response');
19
20
21
22/**
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 sharing
25 * </a> (CORS) extension to WebDriver's JSON wire protocol.
26 *
27 * <p>Each command from the standard JSON protocol will be encoded in a
28 * 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 /xdrpc
32 * endpoint. The server will decode the command, re-route it to the appropriate
33 * handler, and then return the command's response as a standard JSON response
34 * object. The JSON responses will <em>always</em> be returned with a 200
35 * response from the server; clients must rely on the response's "status" field
36 * to determine whether the command succeeded.
37 *
38 * <p>This client cannot be used with the standard wire protocol due to
39 * 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 only
42 * capable of generating the types of requests that may be generated through
43 * a standard <a href="http://goo.gl/vgzAU">HTML form</a> - it can not send
44 * DELETE requests, as is required in the wire protocol.
45 * <li>WebKit's implementation of CORS does not follow the spec and forbids
46 * redirects: https://bugs.webkit.org/show_bug.cgi?id=57600
47 * This limitation appears to be intentional and is documented in WebKit's
48 * Layout tests:
49 * //LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects.html
50 * <li>If the server does not return a 2xx response, IE and Opera's
51 * implementations will fire the XDomainRequest/XMLHttpRequest object's
52 * onerror handler, but without the corresponding response text returned by
53 * the server. This renders IE and Opera incapable of handling command
54 * failures in the standard JSON protocol.
55 * </ul>
56 *
57 * @param {string} url URL for the WebDriver server to send commands to.
58 * @constructor
59 * @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 }
69
70 /** @private {string} */
71 this.url_ = url + webdriver.http.CorsClient.XDRPC_ENDPOINT;
72};
73
74
75/**
76 * Resource URL to send commands to on the server.
77 * @type {string}
78 * @const
79 */
80webdriver.http.CorsClient.XDRPC_ENDPOINT = '/xdrpc';
81
82
83/**
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};
93
94
95/** @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);
101
102 xhr.onload = function() {
103 callback(null, webdriver.http.Response.fromXmlHttpRequest(
104 /** @type {!XMLHttpRequest} */ (xhr)));
105 };
106
107 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 };
115
116 // 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 get
119 // optimized away by the compiler, which leaves us where we were before.
120 xhr.onprogress = xhr.ontimeout = function() {};
121
122 xhr.send(goog.json.serialize({
123 'method': request.method,
124 'path': request.path,
125 'data': request.data
126 }));
127 } catch (ex) {
128 callback(ex);
129 }
130};