OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Utility class for making XHRs more pleasant. | 7 * Utility class for making XHRs more pleasant. |
8 * | |
9 * Note: a mock version of this API exists in mock_xhr.js. | |
8 */ | 10 */ |
9 | 11 |
10 'use strict'; | 12 'use strict'; |
11 | 13 |
12 /** @suppress {duplicate} */ | 14 /** @suppress {duplicate} */ |
13 var remoting = remoting || {}; | 15 var remoting = remoting || {}; |
14 | 16 |
15 /** | 17 /** |
16 * @constructor | 18 * @constructor |
17 * @param {remoting.Xhr.Params} params | 19 * @param {remoting.Xhr.Params} params |
18 */ | 20 */ |
19 remoting.Xhr = function(params) { | 21 remoting.Xhr = function(params) { |
20 remoting.Xhr.checkParams_(params); | 22 remoting.Xhr.checkParams_(params); |
21 | 23 |
22 // Apply URL parameters. | 24 // Apply URL parameters. |
23 var url = params.url; | 25 var url = params.url; |
24 var parameterString = ''; | 26 var parameterString = ''; |
25 if (typeof(params.urlParams) === 'string') { | 27 if (typeof(params.urlParams) === 'string') { |
26 parameterString = params.urlParams; | 28 parameterString = params.urlParams; |
27 } else if (typeof(params.urlParams) === 'object') { | 29 } else if (typeof(params.urlParams) === 'object') { |
28 parameterString = remoting.Xhr.urlencodeParamHash( | 30 parameterString = remoting.Xhr.urlencodeParamHash( |
29 remoting.Xhr.removeNullFields_(params.urlParams)); | 31 base.copyWithoutNullFields(params.urlParams)); |
30 } | 32 } |
31 if (parameterString) { | 33 if (parameterString) { |
32 url += '?' + parameterString; | 34 url += '?' + parameterString; |
33 } | 35 } |
34 | 36 |
35 // Prepare the build modified headers. | 37 // Prepare the build modified headers. |
36 /** @const */ | 38 /** @const */ |
37 this.headers_ = remoting.Xhr.removeNullFields_(params.headers); | 39 this.headers_ = base.copyWithoutNullFields(params.headers); |
38 | 40 |
39 // Convert the content fields to a single text content variable. | 41 // Convert the content fields to a single text content variable. |
40 /** @private {?string} */ | 42 /** @private {?string} */ |
41 this.content_ = null; | 43 this.content_ = null; |
42 if (params.textContent !== undefined) { | 44 if (params.textContent !== undefined) { |
43 this.maybeSetContentType_('text/plain'); | 45 this.maybeSetContentType_('text/plain'); |
44 this.content_ = params.textContent; | 46 this.content_ = params.textContent; |
45 } else if (params.formContent !== undefined) { | 47 } else if (params.formContent !== undefined) { |
46 this.maybeSetContentType_('application/x-www-form-urlencoded'); | 48 this.maybeSetContentType_('application/x-www-form-urlencoded'); |
47 this.content_ = remoting.Xhr.urlencodeParamHash(params.formContent); | 49 this.content_ = remoting.Xhr.urlencodeParamHash(params.formContent); |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
153 } else { | 155 } else { |
154 this.sendXhr_(); | 156 this.sendXhr_(); |
155 } | 157 } |
156 } | 158 } |
157 return this.deferred_.promise(); | 159 return this.deferred_.promise(); |
158 }; | 160 }; |
159 | 161 |
160 /** | 162 /** |
161 * @param {remoting.Xhr.Params} params | 163 * @param {remoting.Xhr.Params} params |
162 * @throws {Error} if params are invalid | 164 * @throws {Error} if params are invalid |
165 * @private | |
163 */ | 166 */ |
164 remoting.Xhr.checkParams_ = function(params) { | 167 remoting.Xhr.checkParams_ = function(params) { |
165 if (params.urlParams) { | 168 if (params.urlParams) { |
166 if (params.url.indexOf('?') != -1) { | 169 if (params.url.indexOf('?') != -1) { |
167 throw new Error('URL may not contain "?" when urlParams is set'); | 170 throw new Error('URL may not contain "?" when urlParams is set'); |
168 } | 171 } |
169 if (params.url.indexOf('#') != -1) { | 172 if (params.url.indexOf('#') != -1) { |
170 throw new Error('URL may not contain "#" when urlParams is set'); | 173 throw new Error('URL may not contain "#" when urlParams is set'); |
171 } | 174 } |
172 } | 175 } |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
227 if (!(key in this.headers_)) { | 230 if (!(key in this.headers_)) { |
228 this.headers_[key] = value; | 231 this.headers_[key] = value; |
229 return true; | 232 return true; |
230 } | 233 } |
231 return false; | 234 return false; |
232 }; | 235 }; |
233 | 236 |
234 /** @private */ | 237 /** @private */ |
235 remoting.Xhr.prototype.sendXhr_ = function() { | 238 remoting.Xhr.prototype.sendXhr_ = function() { |
236 for (var key in this.headers_) { | 239 for (var key in this.headers_) { |
237 this.nativeXhr_.setRequestHeader(key, this.headers_[key]); | 240 this.nativeXhr_.setRequestHeader( |
241 key, /** @type {string} */ (this.headers_[key])); | |
238 } | 242 } |
239 this.nativeXhr_.send(this.content_); | 243 this.nativeXhr_.send(this.content_); |
240 this.content_ = null; // for gc | 244 this.content_ = null; // for gc |
241 }; | 245 }; |
242 | 246 |
243 /** | 247 /** |
244 * @private | 248 * @private |
245 */ | 249 */ |
246 remoting.Xhr.prototype.onReadyStateChange_ = function() { | 250 remoting.Xhr.prototype.onReadyStateChange_ = function() { |
247 var xhr = this.nativeXhr_; | 251 var xhr = this.nativeXhr_; |
248 if (xhr.readyState == 4) { | 252 if (xhr.readyState == 4) { |
249 // See comments at remoting.Xhr.Response. | 253 // See comments at remoting.Xhr.Response. |
250 this.deferred_.resolve(new remoting.Xhr.Response( | 254 this.deferred_.resolve(remoting.Xhr.Response.fromXhr_( |
251 xhr, this.acceptJson_)); | 255 xhr, this.acceptJson_)); |
252 } | 256 } |
253 }; | 257 }; |
254 | 258 |
255 /** | 259 /** |
256 * The response-related parts of an XMLHttpRequest. Note that this | 260 * The response-related parts of an XMLHttpRequest. Note that this |
257 * class is not just a facade for XMLHttpRequest; it saves the value | 261 * class is not just a facade for XMLHttpRequest; it saves the value |
258 * of the |responseText| field becuase once onReadyStateChange_ | 262 * of the |responseText| field becuase once onReadyStateChange_ |
259 * (above) returns, the value of |responseText| is reset to the empty | 263 * (above) returns, the value of |responseText| is reset to the empty |
260 * string! This is a documented anti-feature of the XMLHttpRequest | 264 * string! This is a documented anti-feature of the XMLHttpRequest |
261 * API. | 265 * API. |
262 * | 266 * |
263 * @constructor | 267 * @constructor |
264 * @param {!XMLHttpRequest} xhr | 268 * @param {number} status |
269 * @param {string} statusText | |
270 * @param {?string} url | |
271 * @param {string} text | |
265 * @param {boolean} allowJson | 272 * @param {boolean} allowJson |
266 */ | 273 */ |
267 remoting.Xhr.Response = function(xhr, allowJson) { | 274 remoting.Xhr.Response = function( |
268 /** @private @const */ | 275 status, statusText, url, text, allowJson) { |
269 this.allowJson_ = allowJson; | |
270 | |
271 /** | 276 /** |
272 * The HTTP status code. | 277 * The HTTP status code. |
273 * @const {number} | 278 * @const {number} |
274 */ | 279 */ |
275 this.status = xhr.status; | 280 this.status = status; |
276 | 281 |
277 /** | 282 /** |
278 * The HTTP status description. | 283 * The HTTP status description. |
279 * @const {string} | 284 * @const {string} |
280 */ | 285 */ |
281 this.statusText = xhr.statusText; | 286 this.statusText = statusText; |
282 | 287 |
283 /** | 288 /** |
284 * The response URL, if any. | 289 * The response URL, if any. |
285 * @const {?string} | 290 * @const {?string} |
286 */ | 291 */ |
287 this.url = xhr.responseURL; | 292 this.url = url; |
288 | 293 |
289 /** @private {string} */ | 294 /** @private {string} */ |
290 this.text_ = xhr.responseText || ''; | 295 this.text_ = text; |
296 | |
297 /** @private @const */ | |
298 this.allowJson_ = allowJson; | |
291 | 299 |
292 /** @private {*|undefined} */ | 300 /** @private {*|undefined} */ |
293 this.json_ = undefined; | 301 this.json_ = undefined; |
294 }; | 302 }; |
295 | 303 |
296 /** | 304 /** |
305 * @param {!XMLHttpRequest} xhr | |
306 * @param {boolean} allowJson | |
307 * @return {!remoting.Xhr.Response} | |
308 */ | |
309 remoting.Xhr.Response.fromXhr_ = function(xhr, allowJson) { | |
310 return new remoting.Xhr.Response( | |
311 xhr.status, | |
312 xhr.statusText, | |
313 xhr.responseURL, | |
314 xhr.responseText || '', | |
315 allowJson); | |
316 }; | |
317 | |
318 /** | |
297 * @return {boolean} True if the response code is outside the 200-299 | 319 * @return {boolean} True if the response code is outside the 200-299 |
298 * range (i.e. success as defined by the HTTP protocol). | 320 * range (i.e. success as defined by the HTTP protocol). |
299 */ | 321 */ |
300 remoting.Xhr.Response.prototype.isError = function() { | 322 remoting.Xhr.Response.prototype.isError = function() { |
301 return this.status < 200 || this.status >= 300; | 323 return this.status < 200 || this.status >= 300; |
302 }; | 324 }; |
303 | 325 |
304 /** | 326 /** |
305 * @return {string} The text content of the response. | 327 * @return {string} The text content of the response. |
306 */ | 328 */ |
307 remoting.Xhr.Response.prototype.getText = function() { | 329 remoting.Xhr.Response.prototype.getText = function() { |
308 return this.text_; | 330 return this.text_; |
309 }; | 331 }; |
310 | 332 |
311 /** | 333 /** |
312 * Get the JSON content of the response. Requires acceptJson to have | 334 * Get the JSON content of the response. Requires acceptJson to have |
313 * been true in the request. | 335 * been true in the request. |
314 * @return {*} The parsed JSON content of the response. | 336 * @return {*} The parsed JSON content of the response. |
315 */ | 337 */ |
316 remoting.Xhr.Response.prototype.getJson = function() { | 338 remoting.Xhr.Response.prototype.getJson = function() { |
317 base.debug.assert(this.allowJson_); | 339 base.debug.assert(this.allowJson_); |
318 if (this.json_ === undefined) { | 340 if (this.json_ === undefined) { |
319 this.json_ = JSON.parse(this.text_); | 341 this.json_ = JSON.parse(this.text_); |
320 } | 342 } |
321 return this.json_; | 343 return this.json_; |
322 }; | 344 }; |
323 | 345 |
324 /** | 346 /** |
325 * Returns a copy of the input object with all null or undefined | |
John Williams
2015/04/02 23:57:06
Moved to base.js.
| |
326 * fields removed. | |
327 * | |
328 * @param {Object<string,?string>|undefined} input | |
329 * @return {!Object<string,string>} | |
330 * @private | |
331 */ | |
332 remoting.Xhr.removeNullFields_ = function(input) { | |
333 /** @type {!Object<string,string>} */ | |
334 var result = {}; | |
335 if (input) { | |
336 for (var field in input) { | |
337 var value = input[field]; | |
338 if (value != null) { | |
339 result[field] = value; | |
340 } | |
341 } | |
342 } | |
343 return result; | |
344 }; | |
345 | |
346 /** | |
347 * Takes an associative array of parameters and urlencodes it. | 347 * Takes an associative array of parameters and urlencodes it. |
348 * | 348 * |
349 * @param {Object<string,string>} paramHash The parameter key/value pairs. | 349 * @param {Object<string,string>} paramHash The parameter key/value pairs. |
350 * @return {string} URLEncoded version of paramHash. | 350 * @return {string} URLEncoded version of paramHash. |
351 */ | 351 */ |
352 remoting.Xhr.urlencodeParamHash = function(paramHash) { | 352 remoting.Xhr.urlencodeParamHash = function(paramHash) { |
353 var paramArray = []; | 353 var paramArray = []; |
354 for (var key in paramHash) { | 354 for (var key in paramHash) { |
355 paramArray.push(encodeURIComponent(key) + | 355 var value = paramHash[key]; |
John Williams
2015/04/02 23:57:06
To match semantics of base.copyWithoutNullFields.
| |
356 '=' + encodeURIComponent(paramHash[key])); | 356 if (value != null) { |
357 paramArray.push(encodeURIComponent(key) + | |
358 '=' + encodeURIComponent(value)); | |
359 } | |
357 } | 360 } |
358 if (paramArray.length > 0) { | 361 if (paramArray.length > 0) { |
359 return paramArray.join('&'); | 362 return paramArray.join('&'); |
360 } | 363 } |
361 return ''; | 364 return ''; |
362 }; | 365 }; |
OLD | NEW |