Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/net/channelrequest.js
2868 views
1
// Copyright 2006 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
* @fileoverview Definition of the ChannelRequest class. The ChannelRequest
17
* object encapsulates the logic for making a single request, either for the
18
* forward channel, back channel, or test channel, to the server. It contains
19
* the logic for the three types of transports we use in the BrowserChannel:
20
* XMLHTTP, Trident ActiveX (ie only), and Image request. It provides timeout
21
* detection. This class is part of the BrowserChannel implementation and is not
22
* for use by normal application code.
23
*
24
*/
25
26
27
goog.provide('goog.net.ChannelRequest');
28
goog.provide('goog.net.ChannelRequest.Error');
29
30
goog.require('goog.Timer');
31
goog.require('goog.async.Throttle');
32
goog.require('goog.dom.TagName');
33
goog.require('goog.dom.safe');
34
goog.require('goog.events.EventHandler');
35
goog.require('goog.html.SafeUrl');
36
goog.require('goog.html.uncheckedconversions');
37
goog.require('goog.net.ErrorCode');
38
goog.require('goog.net.EventType');
39
goog.require('goog.net.XmlHttp');
40
goog.require('goog.object');
41
goog.require('goog.string');
42
goog.require('goog.string.Const');
43
goog.require('goog.userAgent');
44
45
// TODO(nnaze): This file depends on goog.net.BrowserChannel and vice versa (a
46
// circular dependency). Usages of BrowserChannel are marked as
47
// "missingRequire" below for now. This should be fixed through refactoring.
48
49
50
51
/**
52
* Creates a ChannelRequest object which encapsulates a request to the server.
53
* A new ChannelRequest is created for each request to the server.
54
*
55
* @param {goog.net.BrowserChannel|goog.net.BrowserTestChannel} channel
56
* The BrowserChannel that owns this request.
57
* @param {goog.net.ChannelDebug} channelDebug A ChannelDebug to use for
58
* logging.
59
* @param {string=} opt_sessionId The session id for the channel.
60
* @param {string|number=} opt_requestId The request id for this request.
61
* @param {number=} opt_retryId The retry id for this request.
62
* @constructor
63
*/
64
goog.net.ChannelRequest = function(
65
channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId) {
66
/**
67
* The BrowserChannel object that owns the request.
68
* @type {goog.net.BrowserChannel|goog.net.BrowserTestChannel}
69
* @private
70
*/
71
this.channel_ = channel;
72
73
/**
74
* The channel debug to use for logging
75
* @type {goog.net.ChannelDebug}
76
* @private
77
*/
78
this.channelDebug_ = channelDebug;
79
80
/**
81
* The Session ID for the channel.
82
* @type {string|undefined}
83
* @private
84
*/
85
this.sid_ = opt_sessionId;
86
87
/**
88
* The RID (request ID) for the request.
89
* @type {string|number|undefined}
90
* @private
91
*/
92
this.rid_ = opt_requestId;
93
94
95
/**
96
* The attempt number of the current request.
97
* @type {number}
98
* @private
99
*/
100
this.retryId_ = opt_retryId || 1;
101
102
103
/**
104
* The timeout in ms before failing the request.
105
* @type {number}
106
* @private
107
*/
108
this.timeout_ = goog.net.ChannelRequest.TIMEOUT_MS;
109
110
/**
111
* An object to keep track of the channel request event listeners.
112
* @type {!goog.events.EventHandler<!goog.net.ChannelRequest>}
113
* @private
114
*/
115
this.eventHandler_ = new goog.events.EventHandler(this);
116
117
/**
118
* A timer for polling responseText in browsers that don't fire
119
* onreadystatechange during incremental loading of responseText.
120
* @type {goog.Timer}
121
* @private
122
*/
123
this.pollingTimer_ = new goog.Timer();
124
125
this.pollingTimer_.setInterval(goog.net.ChannelRequest.POLLING_INTERVAL_MS);
126
};
127
128
129
/**
130
* Extra HTTP headers to add to all the requests sent to the server.
131
* @type {Object}
132
* @private
133
*/
134
goog.net.ChannelRequest.prototype.extraHeaders_ = null;
135
136
137
/**
138
* Whether the request was successful. This is only set to true after the
139
* request successfuly completes.
140
* @type {boolean}
141
* @private
142
*/
143
goog.net.ChannelRequest.prototype.successful_ = false;
144
145
146
/**
147
* The TimerID of the timer used to detect if the request has timed-out.
148
* @type {?number}
149
* @private
150
*/
151
goog.net.ChannelRequest.prototype.watchDogTimerId_ = null;
152
153
154
/**
155
* The time in the future when the request will timeout.
156
* @type {?number}
157
* @private
158
*/
159
goog.net.ChannelRequest.prototype.watchDogTimeoutTime_ = null;
160
161
162
/**
163
* The time the request started.
164
* @type {?number}
165
* @private
166
*/
167
goog.net.ChannelRequest.prototype.requestStartTime_ = null;
168
169
170
/**
171
* The type of request (XMLHTTP, IMG, Trident)
172
* @type {?number}
173
* @private
174
*/
175
goog.net.ChannelRequest.prototype.type_ = null;
176
177
178
/**
179
* The base Uri for the request. The includes all the parameters except the
180
* one that indicates the retry number.
181
* @type {goog.Uri?}
182
* @private
183
*/
184
goog.net.ChannelRequest.prototype.baseUri_ = null;
185
186
187
/**
188
* The request Uri that was actually used for the most recent request attempt.
189
* @type {goog.Uri?}
190
* @private
191
*/
192
goog.net.ChannelRequest.prototype.requestUri_ = null;
193
194
195
/**
196
* The post data, if the request is a post.
197
* @type {?string}
198
* @private
199
*/
200
goog.net.ChannelRequest.prototype.postData_ = null;
201
202
203
/**
204
* The XhrLte request if the request is using XMLHTTP
205
* @type {goog.net.XhrIo}
206
* @private
207
*/
208
goog.net.ChannelRequest.prototype.xmlHttp_ = null;
209
210
211
/**
212
* The position of where the next unprocessed chunk starts in the response
213
* text.
214
* @type {number}
215
* @private
216
*/
217
goog.net.ChannelRequest.prototype.xmlHttpChunkStart_ = 0;
218
219
220
/**
221
* The Trident instance if the request is using Trident.
222
* @type {Object}
223
* @private
224
*/
225
goog.net.ChannelRequest.prototype.trident_ = null;
226
227
228
/**
229
* The verb (Get or Post) for the request.
230
* @type {?string}
231
* @private
232
*/
233
goog.net.ChannelRequest.prototype.verb_ = null;
234
235
236
/**
237
* The last error if the request failed.
238
* @type {?goog.net.ChannelRequest.Error}
239
* @private
240
*/
241
goog.net.ChannelRequest.prototype.lastError_ = null;
242
243
244
/**
245
* The last status code received.
246
* @type {number}
247
* @private
248
*/
249
goog.net.ChannelRequest.prototype.lastStatusCode_ = -1;
250
251
252
/**
253
* Whether to send the Connection:close header as part of the request.
254
* @type {boolean}
255
* @private
256
*/
257
goog.net.ChannelRequest.prototype.sendClose_ = true;
258
259
260
/**
261
* Whether the request has been cancelled due to a call to cancel.
262
* @type {boolean}
263
* @private
264
*/
265
goog.net.ChannelRequest.prototype.cancelled_ = false;
266
267
268
/**
269
* A throttle time in ms for readystatechange events for the backchannel.
270
* Useful for throttling when ready state is INTERACTIVE (partial data).
271
* If set to zero no throttle is used.
272
*
273
* @see goog.net.BrowserChannel.prototype.readyStateChangeThrottleMs_
274
*
275
* @type {number}
276
* @private
277
*/
278
goog.net.ChannelRequest.prototype.readyStateChangeThrottleMs_ = 0;
279
280
281
/**
282
* The throttle for readystatechange events for the current request, or null
283
* if there is none.
284
* @type {goog.async.Throttle}
285
* @private
286
*/
287
goog.net.ChannelRequest.prototype.readyStateChangeThrottle_ = null;
288
289
290
/**
291
* Default timeout in MS for a request. The server must return data within this
292
* time limit for the request to not timeout.
293
* @type {number}
294
*/
295
goog.net.ChannelRequest.TIMEOUT_MS = 45 * 1000;
296
297
298
/**
299
* How often to poll (in MS) for changes to responseText in browsers that don't
300
* fire onreadystatechange during incremental loading of responseText.
301
* @type {number}
302
*/
303
goog.net.ChannelRequest.POLLING_INTERVAL_MS = 250;
304
305
306
/**
307
* Minimum version of Safari that receives a non-null responseText in ready
308
* state interactive.
309
* @type {string}
310
* @private
311
*/
312
goog.net.ChannelRequest.MIN_WEBKIT_FOR_INTERACTIVE_ = '420+';
313
314
315
/**
316
* Enum for channel requests type
317
* @enum {number}
318
* @private
319
*/
320
goog.net.ChannelRequest.Type_ = {
321
/**
322
* XMLHTTP requests.
323
*/
324
XML_HTTP: 1,
325
326
/**
327
* IMG requests.
328
*/
329
IMG: 2,
330
331
/**
332
* Requests that use the MSHTML ActiveX control.
333
*/
334
TRIDENT: 3
335
};
336
337
338
/**
339
* Enum type for identifying a ChannelRequest error.
340
* @enum {number}
341
*/
342
goog.net.ChannelRequest.Error = {
343
/**
344
* Errors due to a non-200 status code.
345
*/
346
STATUS: 0,
347
348
/**
349
* Errors due to no data being returned.
350
*/
351
NO_DATA: 1,
352
353
/**
354
* Errors due to a timeout.
355
*/
356
TIMEOUT: 2,
357
358
/**
359
* Errors due to the server returning an unknown.
360
*/
361
UNKNOWN_SESSION_ID: 3,
362
363
/**
364
* Errors due to bad data being received.
365
*/
366
BAD_DATA: 4,
367
368
/**
369
* Errors due to the handler throwing an exception.
370
*/
371
HANDLER_EXCEPTION: 5,
372
373
/**
374
* The browser declared itself offline during the request.
375
*/
376
BROWSER_OFFLINE: 6,
377
378
/**
379
* IE is blocking ActiveX streaming.
380
*/
381
ACTIVE_X_BLOCKED: 7
382
};
383
384
385
/**
386
* Returns a useful error string for debugging based on the specified error
387
* code.
388
* @param {goog.net.ChannelRequest.Error} errorCode The error code.
389
* @param {number} statusCode The HTTP status code.
390
* @return {string} The error string for the given code combination.
391
*/
392
goog.net.ChannelRequest.errorStringFromCode = function(errorCode, statusCode) {
393
switch (errorCode) {
394
case goog.net.ChannelRequest.Error.STATUS:
395
return 'Non-200 return code (' + statusCode + ')';
396
case goog.net.ChannelRequest.Error.NO_DATA:
397
return 'XMLHTTP failure (no data)';
398
case goog.net.ChannelRequest.Error.TIMEOUT:
399
return 'HttpConnection timeout';
400
default:
401
return 'Unknown error';
402
}
403
};
404
405
406
/**
407
* Sentinel value used to indicate an invalid chunk in a multi-chunk response.
408
* @type {Object}
409
* @private
410
*/
411
goog.net.ChannelRequest.INVALID_CHUNK_ = {};
412
413
414
/**
415
* Sentinel value used to indicate an incomplete chunk in a multi-chunk
416
* response.
417
* @type {Object}
418
* @private
419
*/
420
goog.net.ChannelRequest.INCOMPLETE_CHUNK_ = {};
421
422
423
/**
424
* Returns whether XHR streaming is supported on this browser.
425
*
426
* If XHR streaming is not supported, we will try to use an ActiveXObject
427
* to create a Forever IFrame.
428
*
429
* @return {boolean} Whether XHR streaming is supported.
430
* @see http://code.google.com/p/closure-library/issues/detail?id=346
431
*/
432
goog.net.ChannelRequest.supportsXhrStreaming = function() {
433
return !goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(10);
434
};
435
436
437
/**
438
* Sets extra HTTP headers to add to all the requests sent to the server.
439
*
440
* @param {Object} extraHeaders The HTTP headers.
441
*/
442
goog.net.ChannelRequest.prototype.setExtraHeaders = function(extraHeaders) {
443
this.extraHeaders_ = extraHeaders;
444
};
445
446
447
/**
448
* Sets the timeout for a request
449
*
450
* @param {number} timeout The timeout in MS for when we fail the request.
451
*/
452
goog.net.ChannelRequest.prototype.setTimeout = function(timeout) {
453
this.timeout_ = timeout;
454
};
455
456
457
/**
458
* Sets the throttle for handling onreadystatechange events for the request.
459
*
460
* @param {number} throttle The throttle in ms. A value of zero indicates
461
* no throttle.
462
*/
463
goog.net.ChannelRequest.prototype.setReadyStateChangeThrottle = function(
464
throttle) {
465
this.readyStateChangeThrottleMs_ = throttle;
466
};
467
468
469
/**
470
* Uses XMLHTTP to send an HTTP POST to the server.
471
*
472
* @param {goog.Uri} uri The uri of the request.
473
* @param {string} postData The data for the post body.
474
* @param {boolean} decodeChunks Whether to the result is expected to be
475
* encoded for chunking and thus requires decoding.
476
*/
477
goog.net.ChannelRequest.prototype.xmlHttpPost = function(
478
uri, postData, decodeChunks) {
479
this.type_ = goog.net.ChannelRequest.Type_.XML_HTTP;
480
this.baseUri_ = uri.clone().makeUnique();
481
this.postData_ = postData;
482
this.decodeChunks_ = decodeChunks;
483
this.sendXmlHttp_(null /* hostPrefix */);
484
};
485
486
487
/**
488
* Uses XMLHTTP to send an HTTP GET to the server.
489
*
490
* @param {goog.Uri} uri The uri of the request.
491
* @param {boolean} decodeChunks Whether to the result is expected to be
492
* encoded for chunking and thus requires decoding.
493
* @param {?string} hostPrefix The host prefix, if we might be using a
494
* secondary domain. Note that it should also be in the URL, adding this
495
* won't cause it to be added to the URL.
496
* @param {boolean=} opt_noClose Whether to request that the tcp/ip connection
497
* should be closed.
498
*/
499
goog.net.ChannelRequest.prototype.xmlHttpGet = function(
500
uri, decodeChunks, hostPrefix, opt_noClose) {
501
this.type_ = goog.net.ChannelRequest.Type_.XML_HTTP;
502
this.baseUri_ = uri.clone().makeUnique();
503
this.postData_ = null;
504
this.decodeChunks_ = decodeChunks;
505
if (opt_noClose) {
506
this.sendClose_ = false;
507
}
508
this.sendXmlHttp_(hostPrefix);
509
};
510
511
512
/**
513
* Sends a request via XMLHTTP according to the current state of the
514
* ChannelRequest object.
515
*
516
* @param {?string} hostPrefix The host prefix, if we might be using a secondary
517
* domain.
518
* @private
519
*/
520
goog.net.ChannelRequest.prototype.sendXmlHttp_ = function(hostPrefix) {
521
this.requestStartTime_ = goog.now();
522
this.ensureWatchDogTimer_();
523
524
// clone the base URI to create the request URI. The request uri has the
525
// attempt number as a parameter which helps in debugging.
526
this.requestUri_ = this.baseUri_.clone();
527
this.requestUri_.setParameterValues('t', this.retryId_);
528
529
// send the request either as a POST or GET
530
this.xmlHttpChunkStart_ = 0;
531
var useSecondaryDomains = this.channel_.shouldUseSecondaryDomains();
532
this.xmlHttp_ =
533
this.channel_.createXhrIo(useSecondaryDomains ? hostPrefix : null);
534
535
if (this.readyStateChangeThrottleMs_ > 0) {
536
this.readyStateChangeThrottle_ = new goog.async.Throttle(
537
goog.bind(this.xmlHttpHandler_, this, this.xmlHttp_),
538
this.readyStateChangeThrottleMs_);
539
}
540
541
this.eventHandler_.listen(
542
this.xmlHttp_, goog.net.EventType.READY_STATE_CHANGE,
543
this.readyStateChangeHandler_);
544
545
var headers = this.extraHeaders_ ? goog.object.clone(this.extraHeaders_) : {};
546
if (this.postData_) {
547
// todo (jonp) - use POST constant when Dan defines it
548
this.verb_ = 'POST';
549
headers['Content-Type'] = 'application/x-www-form-urlencoded';
550
this.xmlHttp_.send(this.requestUri_, this.verb_, this.postData_, headers);
551
} else {
552
// todo (jonp) - use GET constant when Dan defines it
553
this.verb_ = 'GET';
554
555
// If the user agent is webkit, we cannot send the close header since it is
556
// disallowed by the browser. If we attempt to set the "Connection: close"
557
// header in WEBKIT browser, it will actually causes an error message.
558
if (this.sendClose_ && !goog.userAgent.WEBKIT) {
559
headers['Connection'] = 'close';
560
}
561
this.xmlHttp_.send(this.requestUri_, this.verb_, null, headers);
562
}
563
this.channel_.notifyServerReachabilityEvent(
564
/** @suppress {missingRequire} */ (
565
goog.net.BrowserChannel.ServerReachability.REQUEST_MADE));
566
this.channelDebug_.xmlHttpChannelRequest(
567
this.verb_, this.requestUri_, this.rid_, this.retryId_, this.postData_);
568
};
569
570
571
/**
572
* Handles a readystatechange event.
573
* @param {goog.events.Event} evt The event.
574
* @private
575
*/
576
goog.net.ChannelRequest.prototype.readyStateChangeHandler_ = function(evt) {
577
var xhr = /** @type {goog.net.XhrIo} */ (evt.target);
578
var throttle = this.readyStateChangeThrottle_;
579
if (throttle &&
580
xhr.getReadyState() == goog.net.XmlHttp.ReadyState.INTERACTIVE) {
581
// Only throttle in the partial data case.
582
this.channelDebug_.debug('Throttling readystatechange.');
583
throttle.fire();
584
} else {
585
// If we haven't throttled, just handle response directly.
586
this.xmlHttpHandler_(xhr);
587
}
588
};
589
590
591
/**
592
* XmlHttp handler
593
* @param {goog.net.XhrIo} xmlhttp The XhrIo object for the current request.
594
* @private
595
*/
596
goog.net.ChannelRequest.prototype.xmlHttpHandler_ = function(xmlhttp) {
597
/** @suppress {missingRequire} */
598
goog.net.BrowserChannel.onStartExecution();
599
600
601
try {
602
if (xmlhttp == this.xmlHttp_) {
603
this.onXmlHttpReadyStateChanged_();
604
} else {
605
this.channelDebug_.warning(
606
'Called back with an ' +
607
'unexpected xmlhttp');
608
}
609
} catch (ex) {
610
this.channelDebug_.debug('Failed call to OnXmlHttpReadyStateChanged_');
611
if (this.xmlHttp_ && this.xmlHttp_.getResponseText()) {
612
this.channelDebug_.dumpException(
613
ex, 'ResponseText: ' + this.xmlHttp_.getResponseText());
614
} else {
615
this.channelDebug_.dumpException(ex, 'No response text');
616
}
617
} finally {
618
/** @suppress {missingRequire} */
619
goog.net.BrowserChannel.onEndExecution();
620
}
621
};
622
623
624
/**
625
* Called by the readystate handler for XMLHTTP requests.
626
*
627
* @private
628
*/
629
goog.net.ChannelRequest.prototype.onXmlHttpReadyStateChanged_ = function() {
630
var readyState = this.xmlHttp_.getReadyState();
631
var errorCode = this.xmlHttp_.getLastErrorCode();
632
var statusCode = this.xmlHttp_.getStatus();
633
// If it is Safari less than 420+, there is a bug that causes null to be
634
// in the responseText on ready state interactive so we must wait for
635
// ready state complete.
636
if (!goog.net.ChannelRequest.supportsXhrStreaming() ||
637
(goog.userAgent.WEBKIT &&
638
!goog.userAgent.isVersionOrHigher(
639
goog.net.ChannelRequest.MIN_WEBKIT_FOR_INTERACTIVE_))) {
640
if (readyState < goog.net.XmlHttp.ReadyState.COMPLETE) {
641
// not yet ready
642
return;
643
}
644
} else {
645
// we get partial results in browsers that support ready state interactive.
646
// We also make sure that getResponseText is not null in interactive mode
647
// before we continue. However, we don't do it in Opera because it only
648
// fire readyState == INTERACTIVE once. We need the following code to poll
649
if (readyState < goog.net.XmlHttp.ReadyState.INTERACTIVE ||
650
readyState == goog.net.XmlHttp.ReadyState.INTERACTIVE &&
651
!goog.userAgent.OPERA && !this.xmlHttp_.getResponseText()) {
652
// not yet ready
653
return;
654
}
655
}
656
657
// Dispatch any appropriate network events.
658
if (!this.cancelled_ && readyState == goog.net.XmlHttp.ReadyState.COMPLETE &&
659
errorCode != goog.net.ErrorCode.ABORT) {
660
// Pretty conservative, these are the only known scenarios which we'd
661
// consider indicative of a truly non-functional network connection.
662
if (errorCode == goog.net.ErrorCode.TIMEOUT || statusCode <= 0) {
663
this.channel_.notifyServerReachabilityEvent(
664
/** @suppress {missingRequire} */
665
goog.net.BrowserChannel.ServerReachability.REQUEST_FAILED);
666
} else {
667
this.channel_.notifyServerReachabilityEvent(
668
/** @suppress {missingRequire} */
669
goog.net.BrowserChannel.ServerReachability.REQUEST_SUCCEEDED);
670
}
671
}
672
673
// got some data so cancel the watchdog timer
674
this.cancelWatchDogTimer_();
675
676
var status = this.xmlHttp_.getStatus();
677
this.lastStatusCode_ = status;
678
var responseText = this.xmlHttp_.getResponseText();
679
if (!responseText) {
680
this.channelDebug_.debug(
681
'No response text for uri ' + this.requestUri_ + ' status ' + status);
682
}
683
this.successful_ = (status == 200);
684
685
this.channelDebug_.xmlHttpChannelResponseMetaData(
686
/** @type {string} */ (this.verb_), this.requestUri_, this.rid_,
687
this.retryId_, readyState, status);
688
689
if (!this.successful_) {
690
if (status == 400 && responseText.indexOf('Unknown SID') > 0) {
691
// the server error string will include 'Unknown SID' which indicates the
692
// server doesn't know about the session (maybe it got restarted, maybe
693
// the user got moved to another server, etc.,). Handlers can special
694
// case this error
695
this.lastError_ = goog.net.ChannelRequest.Error.UNKNOWN_SESSION_ID;
696
/** @suppress {missingRequire} */
697
goog.net.BrowserChannel.notifyStatEvent(
698
/** @suppress {missingRequire} */
699
goog.net.BrowserChannel.Stat.REQUEST_UNKNOWN_SESSION_ID);
700
this.channelDebug_.warning('XMLHTTP Unknown SID (' + this.rid_ + ')');
701
} else {
702
this.lastError_ = goog.net.ChannelRequest.Error.STATUS;
703
/** @suppress {missingRequire} */
704
goog.net.BrowserChannel.notifyStatEvent(
705
/** @suppress {missingRequire} */
706
goog.net.BrowserChannel.Stat.REQUEST_BAD_STATUS);
707
this.channelDebug_.warning(
708
'XMLHTTP Bad status ' + status + ' (' + this.rid_ + ')');
709
}
710
this.cleanup_();
711
this.dispatchFailure_();
712
return;
713
}
714
715
if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
716
this.cleanup_();
717
}
718
719
if (this.decodeChunks_) {
720
this.decodeNextChunks_(readyState, responseText);
721
if (goog.userAgent.OPERA && this.successful_ &&
722
readyState == goog.net.XmlHttp.ReadyState.INTERACTIVE) {
723
this.startPolling_();
724
}
725
} else {
726
this.channelDebug_.xmlHttpChannelResponseText(
727
this.rid_, responseText, null);
728
this.safeOnRequestData_(responseText);
729
}
730
731
if (!this.successful_) {
732
return;
733
}
734
735
if (!this.cancelled_) {
736
if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
737
this.channel_.onRequestComplete(this);
738
} else {
739
// The default is false, the result from this callback shouldn't carry
740
// over to the next callback, otherwise the request looks successful if
741
// the watchdog timer gets called
742
this.successful_ = false;
743
this.ensureWatchDogTimer_();
744
}
745
}
746
};
747
748
749
/**
750
* Decodes the next set of available chunks in the response.
751
* @param {number} readyState The value of readyState.
752
* @param {string} responseText The value of responseText.
753
* @private
754
*/
755
goog.net.ChannelRequest.prototype.decodeNextChunks_ = function(
756
readyState, responseText) {
757
var decodeNextChunksSuccessful = true;
758
while (!this.cancelled_ && this.xmlHttpChunkStart_ < responseText.length) {
759
var chunkText = this.getNextChunk_(responseText);
760
if (chunkText == goog.net.ChannelRequest.INCOMPLETE_CHUNK_) {
761
if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
762
// should have consumed entire response when the request is done
763
this.lastError_ = goog.net.ChannelRequest.Error.BAD_DATA;
764
/** @suppress {missingRequire} */
765
goog.net.BrowserChannel.notifyStatEvent(
766
/** @suppress {missingRequire} */
767
goog.net.BrowserChannel.Stat.REQUEST_INCOMPLETE_DATA);
768
decodeNextChunksSuccessful = false;
769
}
770
this.channelDebug_.xmlHttpChannelResponseText(
771
this.rid_, null, '[Incomplete Response]');
772
break;
773
} else if (chunkText == goog.net.ChannelRequest.INVALID_CHUNK_) {
774
this.lastError_ = goog.net.ChannelRequest.Error.BAD_DATA;
775
/** @suppress {missingRequire} */
776
goog.net.BrowserChannel.notifyStatEvent(
777
/** @suppress {missingRequire} */
778
goog.net.BrowserChannel.Stat.REQUEST_BAD_DATA);
779
this.channelDebug_.xmlHttpChannelResponseText(
780
this.rid_, responseText, '[Invalid Chunk]');
781
decodeNextChunksSuccessful = false;
782
break;
783
} else {
784
this.channelDebug_.xmlHttpChannelResponseText(
785
this.rid_, /** @type {string} */ (chunkText), null);
786
this.safeOnRequestData_(/** @type {string} */ (chunkText));
787
}
788
}
789
if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE &&
790
responseText.length == 0) {
791
// also an error if we didn't get any response
792
this.lastError_ = goog.net.ChannelRequest.Error.NO_DATA;
793
/** @suppress {missingRequire} */
794
goog.net.BrowserChannel.notifyStatEvent(
795
/** @suppress {missingRequire} */
796
goog.net.BrowserChannel.Stat.REQUEST_NO_DATA);
797
decodeNextChunksSuccessful = false;
798
}
799
this.successful_ = this.successful_ && decodeNextChunksSuccessful;
800
if (!decodeNextChunksSuccessful) {
801
// malformed response - we make this trigger retry logic
802
this.channelDebug_.xmlHttpChannelResponseText(
803
this.rid_, responseText, '[Invalid Chunked Response]');
804
this.cleanup_();
805
this.dispatchFailure_();
806
}
807
};
808
809
810
/**
811
* Polls the response for new data.
812
* @private
813
*/
814
goog.net.ChannelRequest.prototype.pollResponse_ = function() {
815
var readyState = this.xmlHttp_.getReadyState();
816
var responseText = this.xmlHttp_.getResponseText();
817
if (this.xmlHttpChunkStart_ < responseText.length) {
818
this.cancelWatchDogTimer_();
819
this.decodeNextChunks_(readyState, responseText);
820
if (this.successful_ &&
821
readyState != goog.net.XmlHttp.ReadyState.COMPLETE) {
822
this.ensureWatchDogTimer_();
823
}
824
}
825
};
826
827
828
/**
829
* Starts a polling interval for changes to responseText of the
830
* XMLHttpRequest, for browsers that don't fire onreadystatechange
831
* as data comes in incrementally. This timer is disabled in
832
* cleanup_().
833
* @private
834
*/
835
goog.net.ChannelRequest.prototype.startPolling_ = function() {
836
this.eventHandler_.listen(
837
this.pollingTimer_, goog.Timer.TICK, this.pollResponse_);
838
this.pollingTimer_.start();
839
};
840
841
842
/**
843
* Returns the next chunk of a chunk-encoded response. This is not standard
844
* HTTP chunked encoding because browsers don't expose the chunk boundaries to
845
* the application through XMLHTTP. So we have an additional chunk encoding at
846
* the application level that lets us tell where the beginning and end of
847
* individual responses are so that we can only try to eval a complete JS array.
848
*
849
* The encoding is the size of the chunk encoded as a decimal string followed
850
* by a newline followed by the data.
851
*
852
* @param {string} responseText The response text from the XMLHTTP response.
853
* @return {string|Object} The next chunk string or a sentinel object
854
* indicating a special condition.
855
* @private
856
*/
857
goog.net.ChannelRequest.prototype.getNextChunk_ = function(responseText) {
858
var sizeStartIndex = this.xmlHttpChunkStart_;
859
var sizeEndIndex = responseText.indexOf('\n', sizeStartIndex);
860
if (sizeEndIndex == -1) {
861
return goog.net.ChannelRequest.INCOMPLETE_CHUNK_;
862
}
863
864
var sizeAsString = responseText.substring(sizeStartIndex, sizeEndIndex);
865
var size = Number(sizeAsString);
866
if (isNaN(size)) {
867
return goog.net.ChannelRequest.INVALID_CHUNK_;
868
}
869
870
var chunkStartIndex = sizeEndIndex + 1;
871
if (chunkStartIndex + size > responseText.length) {
872
return goog.net.ChannelRequest.INCOMPLETE_CHUNK_;
873
}
874
875
var chunkText = responseText.substr(chunkStartIndex, size);
876
this.xmlHttpChunkStart_ = chunkStartIndex + size;
877
return chunkText;
878
};
879
880
881
/**
882
* Uses the Trident htmlfile ActiveX control to send a GET request in IE. This
883
* is the innovation discovered that lets us get intermediate results in
884
* Internet Explorer. Thanks to http://go/kev
885
* @param {goog.Uri} uri The uri to request from.
886
* @param {boolean} usingSecondaryDomain Whether to use a secondary domain.
887
*/
888
goog.net.ChannelRequest.prototype.tridentGet = function(
889
uri, usingSecondaryDomain) {
890
this.type_ = goog.net.ChannelRequest.Type_.TRIDENT;
891
this.baseUri_ = uri.clone().makeUnique();
892
this.tridentGet_(usingSecondaryDomain);
893
};
894
895
896
/**
897
* Starts the Trident request.
898
* @param {boolean} usingSecondaryDomain Whether to use a secondary domain.
899
* @private
900
*/
901
goog.net.ChannelRequest.prototype.tridentGet_ = function(usingSecondaryDomain) {
902
this.requestStartTime_ = goog.now();
903
this.ensureWatchDogTimer_();
904
905
var hostname = usingSecondaryDomain ? window.location.hostname : '';
906
this.requestUri_ = this.baseUri_.clone();
907
this.requestUri_.setParameterValue('DOMAIN', hostname);
908
this.requestUri_.setParameterValue('t', this.retryId_);
909
910
try {
911
this.trident_ = new ActiveXObject('htmlfile');
912
} catch (e) {
913
this.channelDebug_.severe('ActiveX blocked');
914
this.cleanup_();
915
916
this.lastError_ = goog.net.ChannelRequest.Error.ACTIVE_X_BLOCKED;
917
/** @suppress {missingRequire} */
918
goog.net.BrowserChannel.notifyStatEvent(
919
/** @suppress {missingRequire} */
920
goog.net.BrowserChannel.Stat.ACTIVE_X_BLOCKED);
921
this.dispatchFailure_();
922
return;
923
}
924
925
// Using goog.html.SafeHtml.create() might be viable here but since
926
// this code is now superseded by
927
// closure/labs/net/webchannel/channelrequest.js it's not worth risking
928
// the performance regressions and bugs that might result. Instead we
929
// do an unchecked conversion. Please be extra careful if modifying
930
// the HTML construction in this code, it's brittle and so it's easy to make
931
// mistakes.
932
933
var body = '<html><body>';
934
if (usingSecondaryDomain) {
935
var escapedHostname =
936
goog.net.ChannelRequest.escapeForStringInScript_(hostname);
937
body += '<script>document.domain="' + escapedHostname + '"</scr' +
938
'ipt>';
939
}
940
body += '</body></html>';
941
var bodyHtml = goog.html.uncheckedconversions
942
.safeHtmlFromStringKnownToSatisfyTypeContract(
943
goog.string.Const.from('b/12014412'), body);
944
945
this.trident_.open();
946
goog.dom.safe.documentWrite(
947
/** @type {!Document} */ (this.trident_), bodyHtml);
948
this.trident_.close();
949
950
this.trident_.parentWindow['m'] = goog.bind(this.onTridentRpcMessage_, this);
951
this.trident_.parentWindow['d'] = goog.bind(this.onTridentDone_, this, true);
952
this.trident_.parentWindow['rpcClose'] =
953
goog.bind(this.onTridentDone_, this, false);
954
955
var div = this.trident_.createElement(String(goog.dom.TagName.DIV));
956
this.trident_.parentWindow.document.body.appendChild(div);
957
958
var safeUrl = goog.html.SafeUrl.sanitize(this.requestUri_.toString());
959
var sanitizedEscapedUrl =
960
goog.string.htmlEscape(goog.html.SafeUrl.unwrap(safeUrl));
961
var iframeHtml =
962
goog.html.uncheckedconversions
963
.safeHtmlFromStringKnownToSatisfyTypeContract(
964
goog.string.Const.from('b/12014412'),
965
'<iframe src="' + sanitizedEscapedUrl + '"></iframe>');
966
goog.dom.safe.setInnerHtml(div, iframeHtml);
967
968
this.channelDebug_.tridentChannelRequest(
969
'GET', this.requestUri_, this.rid_, this.retryId_);
970
this.channel_.notifyServerReachabilityEvent(
971
/** @suppress {missingRequire} */
972
goog.net.BrowserChannel.ServerReachability.REQUEST_MADE);
973
};
974
975
976
/**
977
* JavaScript-escapes a string so that it can be included inside a JS string.
978
* Since the JS string is expected to be inside a <script>, HTML-escaping
979
* cannot be used and thus '<' and '>' are also JS-escaped.
980
* @param {string} string
981
* @return {string}
982
* @private
983
*/
984
goog.net.ChannelRequest.escapeForStringInScript_ = function(string) {
985
var escaped = '';
986
for (var i = 0; i < string.length; i++) {
987
var c = string.charAt(i);
988
if (c == '<') {
989
escaped += '\\x3c';
990
} else if (c == '>') {
991
escaped += '\\x3e';
992
} else {
993
// This will escape both " and '.
994
escaped += goog.string.escapeChar(c);
995
}
996
}
997
return escaped;
998
};
999
1000
1001
/**
1002
* Callback from the Trident htmlfile ActiveX control for when a new message
1003
* is received.
1004
*
1005
* @param {string} msg The data payload.
1006
* @private
1007
*/
1008
goog.net.ChannelRequest.prototype.onTridentRpcMessage_ = function(msg) {
1009
// need to do async b/c this gets called off of the context of the ActiveX
1010
/** @suppress {missingRequire} */
1011
goog.net.BrowserChannel.setTimeout(
1012
goog.bind(this.onTridentRpcMessageAsync_, this, msg), 0);
1013
};
1014
1015
1016
/**
1017
* Callback from the Trident htmlfile ActiveX control for when a new message
1018
* is received.
1019
*
1020
* @param {string} msg The data payload.
1021
* @private
1022
*/
1023
goog.net.ChannelRequest.prototype.onTridentRpcMessageAsync_ = function(msg) {
1024
if (this.cancelled_) {
1025
return;
1026
}
1027
this.channelDebug_.tridentChannelResponseText(this.rid_, msg);
1028
this.cancelWatchDogTimer_();
1029
this.safeOnRequestData_(msg);
1030
this.ensureWatchDogTimer_();
1031
};
1032
1033
1034
/**
1035
* Callback from the Trident htmlfile ActiveX control for when the request
1036
* is complete
1037
*
1038
* @param {boolean} successful Whether the request successfully completed.
1039
* @private
1040
*/
1041
goog.net.ChannelRequest.prototype.onTridentDone_ = function(successful) {
1042
// need to do async b/c this gets called off of the context of the ActiveX
1043
/** @suppress {missingRequire} */
1044
goog.net.BrowserChannel.setTimeout(
1045
goog.bind(this.onTridentDoneAsync_, this, successful), 0);
1046
};
1047
1048
1049
/**
1050
* Callback from the Trident htmlfile ActiveX control for when the request
1051
* is complete
1052
*
1053
* @param {boolean} successful Whether the request successfully completed.
1054
* @private
1055
*/
1056
goog.net.ChannelRequest.prototype.onTridentDoneAsync_ = function(successful) {
1057
if (this.cancelled_) {
1058
return;
1059
}
1060
this.channelDebug_.tridentChannelResponseDone(this.rid_, successful);
1061
this.cleanup_();
1062
this.successful_ = successful;
1063
this.channel_.onRequestComplete(this);
1064
this.channel_.notifyServerReachabilityEvent(
1065
/** @suppress {missingRequire} */
1066
goog.net.BrowserChannel.ServerReachability.BACK_CHANNEL_ACTIVITY);
1067
};
1068
1069
1070
/**
1071
* Uses an IMG tag to send an HTTP get to the server. This is only currently
1072
* used to terminate the connection, as an IMG tag is the most reliable way to
1073
* send something to the server while the page is getting torn down.
1074
* @param {goog.Uri} uri The uri to send a request to.
1075
*/
1076
goog.net.ChannelRequest.prototype.sendUsingImgTag = function(uri) {
1077
this.type_ = goog.net.ChannelRequest.Type_.IMG;
1078
this.baseUri_ = uri.clone().makeUnique();
1079
this.imgTagGet_();
1080
};
1081
1082
1083
/**
1084
* Starts the IMG request.
1085
*
1086
* @private
1087
*/
1088
goog.net.ChannelRequest.prototype.imgTagGet_ = function() {
1089
var eltImg = new Image();
1090
eltImg.src = this.baseUri_;
1091
this.requestStartTime_ = goog.now();
1092
this.ensureWatchDogTimer_();
1093
};
1094
1095
1096
/**
1097
* Cancels the request no matter what the underlying transport is.
1098
*/
1099
goog.net.ChannelRequest.prototype.cancel = function() {
1100
this.cancelled_ = true;
1101
this.cleanup_();
1102
};
1103
1104
1105
/**
1106
* Ensures that there is watchdog timeout which is used to ensure that
1107
* the connection completes in time.
1108
*
1109
* @private
1110
*/
1111
goog.net.ChannelRequest.prototype.ensureWatchDogTimer_ = function() {
1112
this.watchDogTimeoutTime_ = goog.now() + this.timeout_;
1113
this.startWatchDogTimer_(this.timeout_);
1114
};
1115
1116
1117
/**
1118
* Starts the watchdog timer which is used to ensure that the connection
1119
* completes in time.
1120
* @param {number} time The number of milliseconds to wait.
1121
* @private
1122
* @suppress {missingRequire} goog.net.BrowserChannel
1123
*/
1124
goog.net.ChannelRequest.prototype.startWatchDogTimer_ = function(time) {
1125
if (this.watchDogTimerId_ != null) {
1126
// assertion
1127
throw Error('WatchDog timer not null');
1128
}
1129
/** @private @suppress {missingRequire} Circular dep. */
1130
this.watchDogTimerId_ = goog.net.BrowserChannel.setTimeout(
1131
goog.bind(this.onWatchDogTimeout_, this), time);
1132
};
1133
1134
1135
/**
1136
* Cancels the watchdog timer if it has been started.
1137
*
1138
* @private
1139
*/
1140
goog.net.ChannelRequest.prototype.cancelWatchDogTimer_ = function() {
1141
if (this.watchDogTimerId_) {
1142
goog.global.clearTimeout(this.watchDogTimerId_);
1143
this.watchDogTimerId_ = null;
1144
}
1145
};
1146
1147
1148
/**
1149
* Called when the watchdog timer is triggered. It also handles a case where it
1150
* is called too early which we suspect may be happening sometimes
1151
* (not sure why)
1152
*
1153
* @private
1154
*/
1155
goog.net.ChannelRequest.prototype.onWatchDogTimeout_ = function() {
1156
this.watchDogTimerId_ = null;
1157
var now = goog.now();
1158
if (now - this.watchDogTimeoutTime_ >= 0) {
1159
this.handleTimeout_();
1160
} else {
1161
// got called too early for some reason
1162
this.channelDebug_.warning('WatchDog timer called too early');
1163
this.startWatchDogTimer_(this.watchDogTimeoutTime_ - now);
1164
}
1165
};
1166
1167
1168
/**
1169
* Called when the request has actually timed out. Will cleanup and notify the
1170
* channel of the failure.
1171
*
1172
* @private
1173
*/
1174
goog.net.ChannelRequest.prototype.handleTimeout_ = function() {
1175
if (this.successful_) {
1176
// Should never happen.
1177
this.channelDebug_.severe(
1178
'Received watchdog timeout even though request loaded successfully');
1179
}
1180
1181
this.channelDebug_.timeoutResponse(this.requestUri_);
1182
// IMG requests never notice if they were successful, and always 'time out'.
1183
// This fact says nothing about reachability.
1184
if (this.type_ != goog.net.ChannelRequest.Type_.IMG) {
1185
this.channel_.notifyServerReachabilityEvent(
1186
/** @suppress {missingRequire} */
1187
goog.net.BrowserChannel.ServerReachability.REQUEST_FAILED);
1188
}
1189
this.cleanup_();
1190
1191
// set error and dispatch failure
1192
this.lastError_ = goog.net.ChannelRequest.Error.TIMEOUT;
1193
/** @suppress {missingRequire} */
1194
goog.net.BrowserChannel.notifyStatEvent(
1195
/** @suppress {missingRequire} */
1196
goog.net.BrowserChannel.Stat.REQUEST_TIMEOUT);
1197
this.dispatchFailure_();
1198
};
1199
1200
1201
/**
1202
* Notifies the channel that this request failed.
1203
* @private
1204
*/
1205
goog.net.ChannelRequest.prototype.dispatchFailure_ = function() {
1206
if (this.channel_.isClosed() || this.cancelled_) {
1207
return;
1208
}
1209
1210
this.channel_.onRequestComplete(this);
1211
};
1212
1213
1214
/**
1215
* Cleans up the objects used to make the request. This function is
1216
* idempotent.
1217
*
1218
* @private
1219
*/
1220
goog.net.ChannelRequest.prototype.cleanup_ = function() {
1221
this.cancelWatchDogTimer_();
1222
1223
goog.dispose(this.readyStateChangeThrottle_);
1224
this.readyStateChangeThrottle_ = null;
1225
1226
// Stop the polling timer, if necessary.
1227
this.pollingTimer_.stop();
1228
1229
// Unhook all event handlers.
1230
this.eventHandler_.removeAll();
1231
1232
if (this.xmlHttp_) {
1233
// clear out this.xmlHttp_ before aborting so we handle getting reentered
1234
// inside abort
1235
var xmlhttp = this.xmlHttp_;
1236
this.xmlHttp_ = null;
1237
xmlhttp.abort();
1238
xmlhttp.dispose();
1239
}
1240
1241
if (this.trident_) {
1242
this.trident_ = null;
1243
}
1244
};
1245
1246
1247
/**
1248
* Indicates whether the request was successful. Only valid after the handler
1249
* is called to indicate completion of the request.
1250
*
1251
* @return {boolean} True if the request succeeded.
1252
*/
1253
goog.net.ChannelRequest.prototype.getSuccess = function() {
1254
return this.successful_;
1255
};
1256
1257
1258
/**
1259
* If the request was not successful, returns the reason.
1260
*
1261
* @return {?goog.net.ChannelRequest.Error} The last error.
1262
*/
1263
goog.net.ChannelRequest.prototype.getLastError = function() {
1264
return this.lastError_;
1265
};
1266
1267
1268
/**
1269
* Returns the status code of the last request.
1270
* @return {number} The status code of the last request.
1271
*/
1272
goog.net.ChannelRequest.prototype.getLastStatusCode = function() {
1273
return this.lastStatusCode_;
1274
};
1275
1276
1277
/**
1278
* Returns the session id for this channel.
1279
*
1280
* @return {string|undefined} The session ID.
1281
*/
1282
goog.net.ChannelRequest.prototype.getSessionId = function() {
1283
return this.sid_;
1284
};
1285
1286
1287
/**
1288
* Returns the request id for this request. Each request has a unique request
1289
* id and the request IDs are a sequential increasing count.
1290
*
1291
* @return {string|number|undefined} The request ID.
1292
*/
1293
goog.net.ChannelRequest.prototype.getRequestId = function() {
1294
return this.rid_;
1295
};
1296
1297
1298
/**
1299
* Returns the data for a post, if this request is a post.
1300
*
1301
* @return {?string} The POST data provided by the request initiator.
1302
*/
1303
goog.net.ChannelRequest.prototype.getPostData = function() {
1304
return this.postData_;
1305
};
1306
1307
1308
/**
1309
* Returns the time that the request started, if it has started.
1310
*
1311
* @return {?number} The time the request started, as returned by goog.now().
1312
*/
1313
goog.net.ChannelRequest.prototype.getRequestStartTime = function() {
1314
return this.requestStartTime_;
1315
};
1316
1317
1318
/**
1319
* Helper to call the callback's onRequestData, which catches any
1320
* exception and cleans up the request.
1321
* @param {string} data The request data.
1322
* @private
1323
*/
1324
goog.net.ChannelRequest.prototype.safeOnRequestData_ = function(data) {
1325
1326
try {
1327
this.channel_.onRequestData(this, data);
1328
/** @suppress {missingRequire} goog.net.BrowserChannel */
1329
this.channel_.notifyServerReachabilityEvent(
1330
goog.net.BrowserChannel.ServerReachability.BACK_CHANNEL_ACTIVITY);
1331
} catch (e) {
1332
// Dump debug info, but keep going without closing the channel.
1333
this.channelDebug_.dumpException(e, 'Error in httprequest callback');
1334
}
1335
};
1336
1337