Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/net/browsertestchannel.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 BrowserTestChannel class. A
17
* BrowserTestChannel is used during the first part of channel negotiation
18
* with the server to create the channel. It helps us determine whether we're
19
* behind a buffering proxy. It also runs the logic to see if the channel
20
* has been blocked by a network administrator. This class is part of the
21
* BrowserChannel implementation and is not for use by normal application code.
22
*
23
*/
24
25
26
27
goog.provide('goog.net.BrowserTestChannel');
28
29
goog.require('goog.json.NativeJsonProcessor');
30
goog.require('goog.net.ChannelRequest');
31
goog.require('goog.net.ChannelRequest.Error');
32
goog.require('goog.net.tmpnetwork');
33
goog.require('goog.string.Parser');
34
35
36
37
/**
38
* Encapsulates the logic for a single BrowserTestChannel.
39
*
40
* @constructor
41
* @param {goog.net.BrowserChannel} channel The BrowserChannel that owns this
42
* test channel.
43
* @param {goog.net.ChannelDebug} channelDebug A ChannelDebug to use for
44
* logging.
45
* @final
46
*/
47
goog.net.BrowserTestChannel = function(channel, channelDebug) {
48
/**
49
* The BrowserChannel that owns this test channel
50
* @type {goog.net.BrowserChannel}
51
* @private
52
*/
53
this.channel_ = channel;
54
55
/**
56
* The channel debug to use for logging
57
* @type {goog.net.ChannelDebug}
58
* @private
59
*/
60
this.channelDebug_ = channelDebug;
61
62
/**
63
* Parser for a response payload. The parser should return an array.
64
* @type {goog.string.Parser}
65
* @private
66
*/
67
this.parser_ = new goog.json.NativeJsonProcessor();
68
};
69
70
71
/**
72
* Extra HTTP headers to add to all the requests sent to the server.
73
* @type {Object}
74
* @private
75
*/
76
goog.net.BrowserTestChannel.prototype.extraHeaders_ = null;
77
78
79
/**
80
* The test request.
81
* @type {goog.net.ChannelRequest}
82
* @private
83
*/
84
goog.net.BrowserTestChannel.prototype.request_ = null;
85
86
87
/**
88
* Whether we have received the first result as an intermediate result. This
89
* helps us determine whether we're behind a buffering proxy.
90
* @type {boolean}
91
* @private
92
*/
93
goog.net.BrowserTestChannel.prototype.receivedIntermediateResult_ = false;
94
95
96
/**
97
* The time when the test request was started. We use timing in IE as
98
* a heuristic for whether we're behind a buffering proxy.
99
* @type {?number}
100
* @private
101
*/
102
goog.net.BrowserTestChannel.prototype.startTime_ = null;
103
104
105
/**
106
* The time for of the first result part. We use timing in IE as a
107
* heuristic for whether we're behind a buffering proxy.
108
* @type {?number}
109
* @private
110
*/
111
goog.net.BrowserTestChannel.prototype.firstTime_ = null;
112
113
114
/**
115
* The time for of the last result part. We use timing in IE as a
116
* heuristic for whether we're behind a buffering proxy.
117
* @type {?number}
118
* @private
119
*/
120
goog.net.BrowserTestChannel.prototype.lastTime_ = null;
121
122
123
/**
124
* The relative path for test requests.
125
* @type {?string}
126
* @private
127
*/
128
goog.net.BrowserTestChannel.prototype.path_ = null;
129
130
131
/**
132
* The state of the state machine for this object.
133
*
134
* @type {?number}
135
* @private
136
*/
137
goog.net.BrowserTestChannel.prototype.state_ = null;
138
139
140
/**
141
* The last status code received.
142
* @type {number}
143
* @private
144
*/
145
goog.net.BrowserTestChannel.prototype.lastStatusCode_ = -1;
146
147
148
/**
149
* A subdomain prefix for using a subdomain in IE for the backchannel
150
* requests.
151
* @type {?string}
152
* @private
153
*/
154
goog.net.BrowserTestChannel.prototype.hostPrefix_ = null;
155
156
157
/**
158
* A subdomain prefix for testing whether the channel was disabled by
159
* a network administrator;
160
* @type {?string}
161
* @private
162
*/
163
goog.net.BrowserTestChannel.prototype.blockedPrefix_ = null;
164
165
166
/**
167
* Enum type for the browser test channel state machine
168
* @enum {number}
169
* @private
170
*/
171
goog.net.BrowserTestChannel.State_ = {
172
/**
173
* The state for the BrowserTestChannel state machine where we making the
174
* initial call to get the server configured parameters.
175
*/
176
INIT: 0,
177
178
/**
179
* The state for the BrowserTestChannel state machine where we're checking to
180
* see if the channel has been blocked.
181
*/
182
CHECKING_BLOCKED: 1,
183
184
/**
185
* The state for the BrowserTestChannel state machine where we're checking to
186
* se if we're behind a buffering proxy.
187
*/
188
CONNECTION_TESTING: 2
189
};
190
191
192
/**
193
* Time in MS for waiting for the request to see if the channel is blocked.
194
* If the response takes longer than this many ms, we assume the request has
195
* failed.
196
* @type {number}
197
* @private
198
*/
199
goog.net.BrowserTestChannel.BLOCKED_TIMEOUT_ = 5000;
200
201
202
/**
203
* Number of attempts to try to see if the check to see if we're blocked
204
* succeeds. Sometimes the request can fail because of flaky network conditions
205
* and checking multiple times reduces false positives.
206
* @type {number}
207
* @private
208
*/
209
goog.net.BrowserTestChannel.BLOCKED_RETRIES_ = 3;
210
211
212
/**
213
* Time in ms between retries of the blocked request
214
* @type {number}
215
* @private
216
*/
217
goog.net.BrowserTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_ = 2000;
218
219
220
/**
221
* Time between chunks in the test connection that indicates that we
222
* are not behind a buffering proxy. This value should be less than or
223
* equals to the time between chunks sent from the server.
224
* @type {number}
225
* @private
226
*/
227
goog.net.BrowserTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_ = 500;
228
229
230
/**
231
* Sets extra HTTP headers to add to all the requests sent to the server.
232
*
233
* @param {Object} extraHeaders The HTTP headers.
234
*/
235
goog.net.BrowserTestChannel.prototype.setExtraHeaders = function(extraHeaders) {
236
this.extraHeaders_ = extraHeaders;
237
};
238
239
240
/**
241
* Sets a new parser for the response payload.
242
* @param {!goog.string.Parser} parser Parser.
243
*/
244
goog.net.BrowserTestChannel.prototype.setParser = function(parser) {
245
this.parser_ = parser;
246
};
247
248
249
/**
250
* Starts the test channel. This initiates connections to the server.
251
*
252
* @param {string} path The relative uri for the test connection.
253
*/
254
goog.net.BrowserTestChannel.prototype.connect = function(path) {
255
this.path_ = path;
256
var sendDataUri = this.channel_.getForwardChannelUri(this.path_);
257
258
goog.net.BrowserChannel.notifyStatEvent(
259
goog.net.BrowserChannel.Stat.TEST_STAGE_ONE_START);
260
this.startTime_ = goog.now();
261
262
// If the channel already has the result of the first test, then skip it.
263
var firstTestResults = this.channel_.getFirstTestResults();
264
if (goog.isDefAndNotNull(firstTestResults)) {
265
this.hostPrefix_ = this.channel_.correctHostPrefix(firstTestResults[0]);
266
this.blockedPrefix_ = firstTestResults[1];
267
if (this.blockedPrefix_) {
268
this.state_ = goog.net.BrowserTestChannel.State_.CHECKING_BLOCKED;
269
this.checkBlocked_();
270
} else {
271
this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
272
this.connectStage2_();
273
}
274
return;
275
}
276
277
// the first request returns server specific parameters
278
sendDataUri.setParameterValues('MODE', 'init');
279
this.request_ =
280
goog.net.BrowserChannel.createChannelRequest(this, this.channelDebug_);
281
this.request_.setExtraHeaders(this.extraHeaders_);
282
this.request_.xmlHttpGet(
283
sendDataUri, false /* decodeChunks */, null /* hostPrefix */,
284
true /* opt_noClose */);
285
this.state_ = goog.net.BrowserTestChannel.State_.INIT;
286
};
287
288
289
/**
290
* Checks to see whether the channel is blocked. This is for implementing the
291
* feature that allows network administrators to block Gmail Chat. The
292
* strategy to determine if we're blocked is to try to load an image off a
293
* special subdomain that network administrators will block access to if they
294
* are trying to block chat. For Gmail Chat, the subdomain is
295
* chatenabled.mail.google.com.
296
* @private
297
*/
298
goog.net.BrowserTestChannel.prototype.checkBlocked_ = function() {
299
var uri = this.channel_.createDataUri(
300
this.blockedPrefix_, '/mail/images/cleardot.gif');
301
uri.makeUnique();
302
goog.net.tmpnetwork.testLoadImageWithRetries(
303
uri.toString(), goog.net.BrowserTestChannel.BLOCKED_TIMEOUT_,
304
goog.bind(this.checkBlockedCallback_, this),
305
goog.net.BrowserTestChannel.BLOCKED_RETRIES_,
306
goog.net.BrowserTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_);
307
this.notifyServerReachabilityEvent(
308
goog.net.BrowserChannel.ServerReachability.REQUEST_MADE);
309
};
310
311
312
/**
313
* Callback for testLoadImageWithRetries to check if browser channel is
314
* blocked.
315
* @param {boolean} succeeded Whether the request succeeded.
316
* @private
317
*/
318
goog.net.BrowserTestChannel.prototype.checkBlockedCallback_ = function(
319
succeeded) {
320
if (succeeded) {
321
this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
322
this.connectStage2_();
323
} else {
324
goog.net.BrowserChannel.notifyStatEvent(
325
goog.net.BrowserChannel.Stat.CHANNEL_BLOCKED);
326
this.channel_.testConnectionBlocked(this);
327
}
328
329
// We don't dispatch a REQUEST_FAILED server reachability event when the
330
// block request fails, as such a failure is not a good signal that the
331
// server has actually become unreachable.
332
if (succeeded) {
333
this.notifyServerReachabilityEvent(
334
goog.net.BrowserChannel.ServerReachability.REQUEST_SUCCEEDED);
335
}
336
};
337
338
339
/**
340
* Begins the second stage of the test channel where we test to see if we're
341
* behind a buffering proxy. The server sends back a multi-chunked response
342
* with the first chunk containing the content '1' and then two seconds later
343
* sending the second chunk containing the content '2'. Depending on how we
344
* receive the content, we can tell if we're behind a buffering proxy.
345
* @private
346
* @suppress {missingRequire} goog.net.BrowserChannel
347
*/
348
goog.net.BrowserTestChannel.prototype.connectStage2_ = function() {
349
this.channelDebug_.debug('TestConnection: starting stage 2');
350
351
// If the second test results are available, skip its execution.
352
var secondTestResults = this.channel_.getSecondTestResults();
353
if (goog.isDefAndNotNull(secondTestResults)) {
354
this.channelDebug_.debug(
355
'TestConnection: skipping stage 2, precomputed result is ' +
356
secondTestResults ?
357
'Buffered' :
358
'Unbuffered');
359
goog.net.BrowserChannel.notifyStatEvent(
360
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_START);
361
if (secondTestResults) { // Buffered/Proxy connection
362
goog.net.BrowserChannel.notifyStatEvent(
363
goog.net.BrowserChannel.Stat.PROXY);
364
this.channel_.testConnectionFinished(this, false);
365
} else { // Unbuffered/NoProxy connection
366
goog.net.BrowserChannel.notifyStatEvent(
367
goog.net.BrowserChannel.Stat.NOPROXY);
368
this.channel_.testConnectionFinished(this, true);
369
}
370
return; // Skip the test
371
}
372
/** @private @suppress {missingRequire} Circular dep. */
373
this.request_ =
374
goog.net.BrowserChannel.createChannelRequest(this, this.channelDebug_);
375
this.request_.setExtraHeaders(this.extraHeaders_);
376
var recvDataUri = this.channel_.getBackChannelUri(
377
this.hostPrefix_,
378
/** @type {string} */ (this.path_));
379
380
goog.net.BrowserChannel.notifyStatEvent(
381
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_START);
382
if (!goog.net.ChannelRequest.supportsXhrStreaming()) {
383
recvDataUri.setParameterValues('TYPE', 'html');
384
this.request_.tridentGet(recvDataUri, Boolean(this.hostPrefix_));
385
} else {
386
recvDataUri.setParameterValues('TYPE', 'xmlhttp');
387
this.request_.xmlHttpGet(
388
recvDataUri, false /** decodeChunks */, this.hostPrefix_,
389
false /** opt_noClose */);
390
}
391
};
392
393
394
/**
395
* Factory method for XhrIo objects.
396
* @param {?string} hostPrefix The host prefix, if we need an XhrIo object
397
* capable of calling a secondary domain.
398
* @return {!goog.net.XhrIo} New XhrIo object.
399
*/
400
goog.net.BrowserTestChannel.prototype.createXhrIo = function(hostPrefix) {
401
return this.channel_.createXhrIo(hostPrefix);
402
};
403
404
405
/**
406
* Aborts the test channel.
407
*/
408
goog.net.BrowserTestChannel.prototype.abort = function() {
409
if (this.request_) {
410
this.request_.cancel();
411
this.request_ = null;
412
}
413
this.lastStatusCode_ = -1;
414
};
415
416
417
/**
418
* Returns whether the test channel is closed. The ChannelRequest object expects
419
* this method to be implemented on its handler.
420
*
421
* @return {boolean} Whether the channel is closed.
422
*/
423
goog.net.BrowserTestChannel.prototype.isClosed = function() {
424
return false;
425
};
426
427
428
/**
429
* Callback from ChannelRequest for when new data is received
430
*
431
* @param {goog.net.ChannelRequest} req The request object.
432
* @param {string} responseText The text of the response.
433
*/
434
goog.net.BrowserTestChannel.prototype.onRequestData = function(
435
req, responseText) {
436
this.lastStatusCode_ = req.getLastStatusCode();
437
if (this.state_ == goog.net.BrowserTestChannel.State_.INIT) {
438
this.channelDebug_.debug('TestConnection: Got data for stage 1');
439
if (!responseText) {
440
this.channelDebug_.debug('TestConnection: Null responseText');
441
// The server should always send text; something is wrong here
442
this.channel_.testConnectionFailure(
443
this, goog.net.ChannelRequest.Error.BAD_DATA);
444
return;
445
}
446
447
try {
448
var respArray = this.parser_.parse(responseText);
449
} catch (e) {
450
this.channelDebug_.dumpException(e);
451
this.channel_.testConnectionFailure(
452
this, goog.net.ChannelRequest.Error.BAD_DATA);
453
return;
454
}
455
this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);
456
this.blockedPrefix_ = respArray[1];
457
} else if (
458
this.state_ == goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
459
if (this.receivedIntermediateResult_) {
460
goog.net.BrowserChannel.notifyStatEvent(
461
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_DATA_TWO);
462
this.lastTime_ = goog.now();
463
} else {
464
// '11111' is used instead of '1' to prevent a small amount of buffering
465
// by Safari.
466
if (responseText == '11111') {
467
goog.net.BrowserChannel.notifyStatEvent(
468
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_DATA_ONE);
469
this.receivedIntermediateResult_ = true;
470
this.firstTime_ = goog.now();
471
if (this.checkForEarlyNonBuffered_()) {
472
// If early chunk detection is on, and we passed the tests,
473
// assume HTTP_OK, cancel the test and turn on noproxy mode.
474
this.lastStatusCode_ = 200;
475
this.request_.cancel();
476
this.channelDebug_.debug(
477
'Test connection succeeded; using streaming connection');
478
goog.net.BrowserChannel.notifyStatEvent(
479
goog.net.BrowserChannel.Stat.NOPROXY);
480
this.channel_.testConnectionFinished(this, true);
481
}
482
} else {
483
goog.net.BrowserChannel.notifyStatEvent(
484
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_DATA_BOTH);
485
this.firstTime_ = this.lastTime_ = goog.now();
486
this.receivedIntermediateResult_ = false;
487
}
488
}
489
}
490
};
491
492
493
/**
494
* Callback from ChannelRequest that indicates a request has completed.
495
*
496
* @param {goog.net.ChannelRequest} req The request object.
497
* @suppress {missingRequire} Cannot depend on goog.net.BrowserChannel because
498
* it creates a circular dependency.
499
*/
500
goog.net.BrowserTestChannel.prototype.onRequestComplete = function(req) {
501
this.lastStatusCode_ = this.request_.getLastStatusCode();
502
if (!this.request_.getSuccess()) {
503
this.channelDebug_.debug(
504
'TestConnection: request failed, in state ' + this.state_);
505
if (this.state_ == goog.net.BrowserTestChannel.State_.INIT) {
506
goog.net.BrowserChannel.notifyStatEvent(
507
goog.net.BrowserChannel.Stat.TEST_STAGE_ONE_FAILED);
508
} else if (
509
this.state_ == goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
510
goog.net.BrowserChannel.notifyStatEvent(
511
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_FAILED);
512
}
513
this.channel_.testConnectionFailure(
514
this,
515
/** @type {goog.net.ChannelRequest.Error} */
516
(this.request_.getLastError()));
517
return;
518
}
519
520
if (this.state_ == goog.net.BrowserTestChannel.State_.INIT) {
521
this.channelDebug_.debug(
522
'TestConnection: request complete for initial check');
523
if (this.blockedPrefix_) {
524
this.state_ = goog.net.BrowserTestChannel.State_.CHECKING_BLOCKED;
525
this.checkBlocked_();
526
} else {
527
this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
528
this.connectStage2_();
529
}
530
} else if (
531
this.state_ == goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
532
this.channelDebug_.debug('TestConnection: request complete for stage 2');
533
var goodConn = false;
534
535
if (!goog.net.ChannelRequest.supportsXhrStreaming()) {
536
// we always get Trident responses in separate calls to
537
// onRequestData, so we have to check the time they came
538
var ms = this.lastTime_ - this.firstTime_;
539
if (ms < 200) {
540
// TODO: need to empirically verify that this number is OK
541
// for slow computers
542
goodConn = false;
543
} else {
544
goodConn = true;
545
}
546
} else {
547
goodConn = this.receivedIntermediateResult_;
548
}
549
550
if (goodConn) {
551
this.channelDebug_.debug(
552
'Test connection succeeded; using streaming connection');
553
goog.net.BrowserChannel.notifyStatEvent(
554
goog.net.BrowserChannel.Stat.NOPROXY);
555
this.channel_.testConnectionFinished(this, true);
556
} else {
557
this.channelDebug_.debug('Test connection failed; not using streaming');
558
/** @suppress {missingRequire} Circular dep */
559
goog.net.BrowserChannel.notifyStatEvent(
560
goog.net.BrowserChannel.Stat.PROXY);
561
this.channel_.testConnectionFinished(this, false);
562
}
563
}
564
};
565
566
567
/**
568
* Returns the last status code received for a request.
569
* @return {number} The last status code received for a request.
570
*/
571
goog.net.BrowserTestChannel.prototype.getLastStatusCode = function() {
572
return this.lastStatusCode_;
573
};
574
575
576
/**
577
* @return {boolean} Whether we should be using secondary domains when the
578
* server instructs us to do so.
579
*/
580
goog.net.BrowserTestChannel.prototype.shouldUseSecondaryDomains = function() {
581
return this.channel_.shouldUseSecondaryDomains();
582
};
583
584
585
/**
586
* Gets whether this channel is currently active. This is used to determine the
587
* length of time to wait before retrying.
588
*
589
* @param {goog.net.BrowserChannel} browserChannel The browser channel.
590
* @return {boolean} Whether the channel is currently active.
591
*/
592
goog.net.BrowserTestChannel.prototype.isActive = function(browserChannel) {
593
return this.channel_.isActive();
594
};
595
596
597
/**
598
* @return {boolean} True if test stage 2 detected a non-buffered
599
* channel early and early no buffering detection is enabled.
600
* @private
601
*/
602
goog.net.BrowserTestChannel.prototype.checkForEarlyNonBuffered_ = function() {
603
var ms = this.firstTime_ - this.startTime_;
604
605
// we always get Trident responses in separate calls to
606
// onRequestData, so we have to check the time that the first came in
607
// and verify that the data arrived before the second portion could
608
// have been sent. For all other browser's we skip the timing test.
609
return goog.net.ChannelRequest.supportsXhrStreaming() ||
610
ms < goog.net.BrowserTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_;
611
};
612
613
614
/**
615
* Notifies the channel of a fine grained network event.
616
* @param {goog.net.BrowserChannel.ServerReachability} reachabilityType The
617
* reachability event type.
618
*/
619
goog.net.BrowserTestChannel.prototype.notifyServerReachabilityEvent = function(
620
reachabilityType) {
621
this.channel_.notifyServerReachabilityEvent(reachabilityType);
622
};
623
624