Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/labs/net/xhr.js
2868 views
1
// Copyright 2011 The Closure Library Authors. 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
15
16
/**
17
* @fileoverview Offered as an alternative to XhrIo as a way for making requests
18
* via XMLHttpRequest. Instead of mirroring the XHR interface and exposing
19
* events, results are used as a way to pass a "promise" of the response to
20
* interested parties.
21
*
22
*/
23
24
goog.provide('goog.labs.net.xhr');
25
goog.provide('goog.labs.net.xhr.Error');
26
goog.provide('goog.labs.net.xhr.HttpError');
27
goog.provide('goog.labs.net.xhr.Options');
28
goog.provide('goog.labs.net.xhr.PostData');
29
goog.provide('goog.labs.net.xhr.ResponseType');
30
goog.provide('goog.labs.net.xhr.TimeoutError');
31
32
goog.require('goog.Promise');
33
goog.require('goog.asserts');
34
goog.require('goog.debug.Error');
35
goog.require('goog.json');
36
goog.require('goog.net.HttpStatus');
37
goog.require('goog.net.XmlHttp');
38
goog.require('goog.object');
39
goog.require('goog.string');
40
goog.require('goog.uri.utils');
41
goog.require('goog.userAgent');
42
43
44
45
goog.scope(function() {
46
var userAgent = goog.userAgent;
47
var xhr = goog.labs.net.xhr;
48
var HttpStatus = goog.net.HttpStatus;
49
50
51
/**
52
* Configuration options for an XMLHttpRequest.
53
* - headers: map of header key/value pairs.
54
* - timeoutMs: number of milliseconds after which the request will be timed
55
* out by the client. Default is to allow the browser to handle timeouts.
56
* - withCredentials: whether user credentials are to be included in a
57
* cross-origin request. See:
58
* http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute
59
* - mimeType: allows the caller to override the content-type and charset for
60
* the request. See:
61
* http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-overridemimetype
62
* - responseType: may be set to change the response type to an arraybuffer or
63
* blob for downloading binary data. See:
64
* http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-responsetype]
65
* - xmlHttpFactory: allows the caller to override the factory used to create
66
* XMLHttpRequest objects.
67
* - xssiPrefix: Prefix used for protecting against XSSI attacks, which should
68
* be removed before parsing the response as JSON.
69
*
70
* @typedef {{
71
* headers: (Object<string>|undefined),
72
* mimeType: (string|undefined),
73
* responseType: (xhr.ResponseType|undefined),
74
* timeoutMs: (number|undefined),
75
* withCredentials: (boolean|undefined),
76
* xmlHttpFactory: (goog.net.XmlHttpFactory|undefined),
77
* xssiPrefix: (string|undefined)
78
* }}
79
*/
80
xhr.Options;
81
82
83
/**
84
* Defines the types that are allowed as post data.
85
* @typedef {(ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined)}
86
*/
87
xhr.PostData;
88
89
90
/**
91
* The Content-Type HTTP header name.
92
* @type {string}
93
*/
94
xhr.CONTENT_TYPE_HEADER = 'Content-Type';
95
96
97
/**
98
* The Content-Type HTTP header value for a url-encoded form.
99
* @type {string}
100
*/
101
xhr.FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=utf-8';
102
103
104
/**
105
* Supported data types for the responseType field.
106
* See: http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-response
107
* @enum {string}
108
*/
109
xhr.ResponseType = {
110
ARRAYBUFFER: 'arraybuffer',
111
BLOB: 'blob',
112
DOCUMENT: 'document',
113
JSON: 'json',
114
TEXT: 'text'
115
};
116
117
118
/**
119
* Sends a get request, returning a promise that will be resolved
120
* with the response text once the request completes.
121
*
122
* @param {string} url The URL to request.
123
* @param {xhr.Options=} opt_options Configuration options for the request.
124
* @return {!goog.Promise<string>} A promise that will be resolved with the
125
* response text once the request completes.
126
*/
127
xhr.get = function(url, opt_options) {
128
return xhr.send('GET', url, null, opt_options).then(function(request) {
129
return request.responseText;
130
});
131
};
132
133
134
/**
135
* Sends a post request, returning a promise that will be resolved
136
* with the response text once the request completes.
137
*
138
* @param {string} url The URL to request.
139
* @param {xhr.PostData} data The body of the post request.
140
* @param {xhr.Options=} opt_options Configuration options for the request.
141
* @return {!goog.Promise<string>} A promise that will be resolved with the
142
* response text once the request completes.
143
*/
144
xhr.post = function(url, data, opt_options) {
145
return xhr.send('POST', url, data, opt_options).then(function(request) {
146
return request.responseText;
147
});
148
};
149
150
151
/**
152
* Sends a get request, returning a promise that will be resolved with
153
* the parsed response text once the request completes.
154
*
155
* @param {string} url The URL to request.
156
* @param {xhr.Options=} opt_options Configuration options for the request.
157
* @return {!goog.Promise<Object>} A promise that will be resolved with the
158
* response JSON once the request completes.
159
*/
160
xhr.getJson = function(url, opt_options) {
161
return xhr.send('GET', url, null, opt_options).then(function(request) {
162
return xhr.parseJson_(request.responseText, opt_options);
163
});
164
};
165
166
167
/**
168
* Sends a get request, returning a promise that will be resolved with the
169
* response as a Blob.
170
*
171
* @param {string} url The URL to request.
172
* @param {xhr.Options=} opt_options Configuration options for the request. If
173
* responseType is set, it will be ignored for this request.
174
* @return {!goog.Promise<!Blob>} A promise that will be resolved with an
175
* immutable Blob representing the file once the request completes.
176
*/
177
xhr.getBlob = function(url, opt_options) {
178
goog.asserts.assert(
179
'Blob' in goog.global, 'getBlob is not supported in this browser.');
180
181
var options = opt_options ? goog.object.clone(opt_options) : {};
182
options.responseType = xhr.ResponseType.BLOB;
183
184
return xhr.send('GET', url, null, options).then(function(request) {
185
return /** @type {!Blob} */ (request.response);
186
});
187
};
188
189
190
/**
191
* Sends a get request, returning a promise that will be resolved with the
192
* response as an array of bytes.
193
*
194
* Supported in all XMLHttpRequest level 2 browsers, as well as IE9. IE8 and
195
* earlier are not supported.
196
*
197
* @param {string} url The URL to request.
198
* @param {xhr.Options=} opt_options Configuration options for the request. If
199
* responseType is set, it will be ignored for this request.
200
* @return {!goog.Promise<!Uint8Array|!Array<number>>} A promise that will be
201
* resolved with an array of bytes once the request completes.
202
*/
203
xhr.getBytes = function(url, opt_options) {
204
goog.asserts.assert(
205
!userAgent.IE || userAgent.isDocumentModeOrHigher(9),
206
'getBytes is not supported in this browser.');
207
208
var options = opt_options ? goog.object.clone(opt_options) : {};
209
options.responseType = xhr.ResponseType.ARRAYBUFFER;
210
211
return xhr.send('GET', url, null, options).then(function(request) {
212
// Use the ArrayBuffer response in browsers that support XMLHttpRequest2.
213
// This covers nearly all modern browsers: http://caniuse.com/xhr2
214
if (request.response) {
215
return new Uint8Array(/** @type {!ArrayBuffer} */ (request.response));
216
}
217
218
// Fallback for IE9: the response may be accessed as an array of bytes with
219
// the non-standard responseBody property, which can only be accessed as a
220
// VBArray. IE7 and IE8 require significant amounts of VBScript to extract
221
// the bytes.
222
// See: http://stackoverflow.com/questions/1919972/
223
if (goog.global['VBArray']) {
224
return new goog.global['VBArray'](request['responseBody']).toArray();
225
}
226
227
// Nearly all common browsers are covered by the cases above. If downloading
228
// binary files in older browsers is necessary, the MDN article "Sending and
229
// Receiving Binary Data" provides techniques that may work with
230
// XMLHttpRequest level 1 browsers: http://goo.gl/7lEuGN
231
throw new xhr.Error(
232
'getBytes is not supported in this browser.', url, request);
233
});
234
};
235
236
237
/**
238
* Sends a post request, returning a promise that will be resolved with
239
* the parsed response text once the request completes.
240
*
241
* @param {string} url The URL to request.
242
* @param {xhr.PostData} data The body of the post request.
243
* @param {xhr.Options=} opt_options Configuration options for the request.
244
* @return {!goog.Promise<Object>} A promise that will be resolved with the
245
* response JSON once the request completes.
246
*/
247
xhr.postJson = function(url, data, opt_options) {
248
return xhr.send('POST', url, data, opt_options).then(function(request) {
249
return xhr.parseJson_(request.responseText, opt_options);
250
});
251
};
252
253
254
/**
255
* Sends a request, returning a promise that will be resolved
256
* with the XHR object once the request completes.
257
*
258
* If content type hasn't been set in opt_options headers, and hasn't been
259
* explicitly set to null, default to form-urlencoded/UTF8 for POSTs.
260
*
261
* @param {string} method The HTTP method for the request.
262
* @param {string} url The URL to request.
263
* @param {xhr.PostData} data The body of the post request.
264
* @param {xhr.Options=} opt_options Configuration options for the request.
265
* @return {!goog.Promise<!goog.net.XhrLike.OrNative>} A promise that will be
266
* resolved with the XHR object once the request completes.
267
*/
268
xhr.send = function(method, url, data, opt_options) {
269
return new goog.Promise(function(resolve, reject) {
270
var options = opt_options || {};
271
var timer;
272
273
var request = options.xmlHttpFactory ?
274
options.xmlHttpFactory.createInstance() :
275
goog.net.XmlHttp();
276
try {
277
request.open(method, url, true);
278
} catch (e) {
279
// XMLHttpRequest.open may throw when 'open' is called, for example, IE7
280
// throws "Access Denied" for cross-origin requests.
281
reject(new xhr.Error('Error opening XHR: ' + e.message, url, request));
282
}
283
284
// So sad that IE doesn't support onload and onerror.
285
request.onreadystatechange = function() {
286
if (request.readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
287
goog.global.clearTimeout(timer);
288
// Note: When developing locally, XHRs to file:// schemes return
289
// a status code of 0. We mark that case as a success too.
290
if (HttpStatus.isSuccess(request.status) ||
291
request.status === 0 && !xhr.isEffectiveSchemeHttp_(url)) {
292
resolve(request);
293
} else {
294
reject(new xhr.HttpError(request.status, url, request));
295
}
296
}
297
};
298
request.onerror = function() {
299
reject(new xhr.Error('Network error', url, request));
300
};
301
302
// Set the headers.
303
var contentType;
304
if (options.headers) {
305
for (var key in options.headers) {
306
var value = options.headers[key];
307
if (goog.isDefAndNotNull(value)) {
308
request.setRequestHeader(key, value);
309
}
310
}
311
contentType = options.headers[xhr.CONTENT_TYPE_HEADER];
312
}
313
314
// Browsers will automatically set the content type to multipart/form-data
315
// when passed a FormData object.
316
var dataIsFormData =
317
(goog.global['FormData'] && (data instanceof goog.global['FormData']));
318
// If a content type hasn't been set, it hasn't been explicitly set to null,
319
// and the data isn't a FormData, default to form-urlencoded/UTF8 for POSTs.
320
// This is because some proxies have been known to reject posts without a
321
// content-type.
322
if (method == 'POST' && contentType === undefined && !dataIsFormData) {
323
request.setRequestHeader(xhr.CONTENT_TYPE_HEADER, xhr.FORM_CONTENT_TYPE);
324
}
325
326
// Set whether to include cookies with cross-domain requests. See:
327
// http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute
328
if (options.withCredentials) {
329
request.withCredentials = options.withCredentials;
330
}
331
332
// Allows setting an alternative response type, such as an ArrayBuffer. See:
333
// http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-responsetype
334
if (options.responseType) {
335
request.responseType = options.responseType;
336
}
337
338
// Allow the request to override the MIME type of the response. See:
339
// http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-overridemimetype
340
if (options.mimeType) {
341
request.overrideMimeType(options.mimeType);
342
}
343
344
// Handle timeouts, if requested.
345
if (options.timeoutMs > 0) {
346
timer = goog.global.setTimeout(function() {
347
// Clear event listener before aborting so the errback will not be
348
// called twice.
349
request.onreadystatechange = goog.nullFunction;
350
request.abort();
351
reject(new xhr.TimeoutError(url, request));
352
}, options.timeoutMs);
353
}
354
355
// Trigger the send.
356
try {
357
request.send(data);
358
} catch (e) {
359
// XMLHttpRequest.send is known to throw on some versions of FF,
360
// for example if a cross-origin request is disallowed.
361
request.onreadystatechange = goog.nullFunction;
362
goog.global.clearTimeout(timer);
363
reject(new xhr.Error('Error sending XHR: ' + e.message, url, request));
364
}
365
});
366
};
367
368
369
/**
370
* @param {string} url The URL to test.
371
* @return {boolean} Whether the effective scheme is HTTP or HTTPs.
372
* @private
373
*/
374
xhr.isEffectiveSchemeHttp_ = function(url) {
375
var scheme = goog.uri.utils.getEffectiveScheme(url);
376
// NOTE(user): Empty-string is for the case under FF3.5 when the location
377
// is not defined inside a web worker.
378
return scheme == 'http' || scheme == 'https' || scheme == '';
379
};
380
381
382
/**
383
* JSON-parses the given response text, returning an Object.
384
*
385
* @param {string} responseText Response text.
386
* @param {xhr.Options|undefined} options The options object.
387
* @return {Object} The JSON-parsed value of the original responseText.
388
* @private
389
*/
390
xhr.parseJson_ = function(responseText, options) {
391
var prefixStrippedResult = responseText;
392
if (options && options.xssiPrefix) {
393
prefixStrippedResult =
394
xhr.stripXssiPrefix_(options.xssiPrefix, prefixStrippedResult);
395
}
396
return goog.json.parse(prefixStrippedResult);
397
};
398
399
400
/**
401
* Strips the XSSI prefix from the input string.
402
*
403
* @param {string} prefix The XSSI prefix.
404
* @param {string} string The string to strip the prefix from.
405
* @return {string} The input string without the prefix.
406
* @private
407
*/
408
xhr.stripXssiPrefix_ = function(prefix, string) {
409
if (goog.string.startsWith(string, prefix)) {
410
string = string.substring(prefix.length);
411
}
412
return string;
413
};
414
415
416
417
/**
418
* Generic error that may occur during a request.
419
*
420
* @param {string} message The error message.
421
* @param {string} url The URL that was being requested.
422
* @param {!goog.net.XhrLike.OrNative} request The XHR that failed.
423
* @extends {goog.debug.Error}
424
* @constructor
425
*/
426
xhr.Error = function(message, url, request) {
427
xhr.Error.base(this, 'constructor', message + ', url=' + url);
428
429
/**
430
* The URL that was requested.
431
* @type {string}
432
*/
433
this.url = url;
434
435
/**
436
* The XMLHttpRequest corresponding with the failed request.
437
* @type {!goog.net.XhrLike.OrNative}
438
*/
439
this.xhr = request;
440
};
441
goog.inherits(xhr.Error, goog.debug.Error);
442
443
444
/** @override */
445
xhr.Error.prototype.name = 'XhrError';
446
447
448
449
/**
450
* Class for HTTP errors.
451
*
452
* @param {number} status The HTTP status code of the response.
453
* @param {string} url The URL that was being requested.
454
* @param {!goog.net.XhrLike.OrNative} request The XHR that failed.
455
* @extends {xhr.Error}
456
* @constructor
457
* @final
458
*/
459
xhr.HttpError = function(status, url, request) {
460
xhr.HttpError.base(
461
this, 'constructor', 'Request Failed, status=' + status, url, request);
462
463
/**
464
* The HTTP status code for the error.
465
* @type {number}
466
*/
467
this.status = status;
468
};
469
goog.inherits(xhr.HttpError, xhr.Error);
470
471
472
/** @override */
473
xhr.HttpError.prototype.name = 'XhrHttpError';
474
475
476
477
/**
478
* Class for Timeout errors.
479
*
480
* @param {string} url The URL that timed out.
481
* @param {!goog.net.XhrLike.OrNative} request The XHR that failed.
482
* @extends {xhr.Error}
483
* @constructor
484
* @final
485
*/
486
xhr.TimeoutError = function(url, request) {
487
xhr.TimeoutError.base(this, 'constructor', 'Request timed out', url, request);
488
};
489
goog.inherits(xhr.TimeoutError, xhr.Error);
490
491
492
/** @override */
493
xhr.TimeoutError.prototype.name = 'XhrTimeoutError';
494
495
}); // goog.scope
496
497