// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Definition of the BrowserChannel class. A BrowserChannel * simulates a bidirectional socket over HTTP. It is the basis of the * Gmail Chat IM connections to the server. * * Typical usage will look like * var handler = [handler object]; * var channel = new BrowserChannel(clientVersion); * channel.setHandler(handler); * channel.connect('channel/test', 'channel/bind'); * * See goog.net.BrowserChannel.Handler for the handler interface. * */ goog.provide('goog.net.BrowserChannel'); goog.provide('goog.net.BrowserChannel.Error'); goog.provide('goog.net.BrowserChannel.Event'); goog.provide('goog.net.BrowserChannel.Handler'); goog.provide('goog.net.BrowserChannel.LogSaver'); goog.provide('goog.net.BrowserChannel.QueuedMap'); goog.provide('goog.net.BrowserChannel.ServerReachability'); goog.provide('goog.net.BrowserChannel.ServerReachabilityEvent'); goog.provide('goog.net.BrowserChannel.Stat'); goog.provide('goog.net.BrowserChannel.StatEvent'); goog.provide('goog.net.BrowserChannel.State'); goog.provide('goog.net.BrowserChannel.TimingEvent'); goog.require('goog.Uri'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.debug.TextFormatter'); goog.require('goog.events.Event'); goog.require('goog.events.EventTarget'); goog.require('goog.json'); goog.require('goog.json.NativeJsonProcessor'); goog.require('goog.log'); goog.require('goog.net.BrowserTestChannel'); goog.require('goog.net.ChannelDebug'); goog.require('goog.net.ChannelRequest'); goog.require('goog.net.XhrIo'); goog.require('goog.net.tmpnetwork'); goog.require('goog.object'); goog.require('goog.string'); goog.require('goog.structs'); goog.require('goog.structs.CircularBuffer'); /** * Encapsulates the logic for a single BrowserChannel. * * @param {string=} opt_clientVersion An application-specific version number * that is sent to the server when connected. * @param {Array=} opt_firstTestResults Previously determined results * of the first browser channel test. * @param {boolean=} opt_secondTestResults Previously determined results * of the second browser channel test. * @param {boolean=} opt_asyncTest Whether to perform the test requests * asynchronously. While the test is performed, we'll assume the worst * (connection is buffered), in order to avoid delaying the connection * until the test is performed. * @constructor */ goog.net.BrowserChannel = function( opt_clientVersion, opt_firstTestResults, opt_secondTestResults, opt_asyncTest) { /** * The application specific version that is passed to the server. * @type {?string} * @private */ this.clientVersion_ = opt_clientVersion || null; /** * The current state of the BrowserChannel. It should be one of the * goog.net.BrowserChannel.State constants. * @type {!goog.net.BrowserChannel.State} * @private */ this.state_ = goog.net.BrowserChannel.State.INIT; /** * An array of queued maps that need to be sent to the server. * @type {Array} * @private */ this.outgoingMaps_ = []; /** * An array of dequeued maps that we have either received a non-successful * response for, or no response at all, and which therefore may or may not * have been received by the server. * @type {Array} * @private */ this.pendingMaps_ = []; /** * The channel debug used for browserchannel logging * @type {!goog.net.ChannelDebug} * @private */ this.channelDebug_ = new goog.net.ChannelDebug(); /** * Parser for a response payload. The parser should return an array. * @type {!goog.string.Parser} * @private */ this.parser_ = new goog.json.NativeJsonProcessor(); /** * An array of results for the first browser channel test call. * @type {Array} * @private */ this.firstTestResults_ = opt_firstTestResults || null; /** * The results of the second browser channel test. True implies the * connection is buffered, False means unbuffered, null means that * the results are not available. * @private */ this.secondTestResults_ = goog.isDefAndNotNull(opt_secondTestResults) ? opt_secondTestResults : null; /** * Whether to perform the test requests asynchronously. While the test is * performed, we'll assume the worst (connection is buffered), in order to * avoid delaying the connection until the test is performed. * @private {boolean} */ this.asyncTest_ = opt_asyncTest || false; }; /** * Simple container class for a (mapId, map) pair. * @param {number} mapId The id for this map. * @param {Object|goog.structs.Map} map The map itself. * @param {Object=} opt_context The context associated with the map. * @constructor * @final */ goog.net.BrowserChannel.QueuedMap = function(mapId, map, opt_context) { /** * The id for this map. * @type {number} */ this.mapId = mapId; /** * The map itself. * @type {Object} */ this.map = map; /** * The context for the map. * @type {Object} */ this.context = opt_context || null; }; /** * Extra HTTP headers to add to all the requests sent to the server. * @type {Object} * @private */ goog.net.BrowserChannel.prototype.extraHeaders_ = null; /** * Extra parameters to add to all the requests sent to the server. * @type {Object} * @private */ goog.net.BrowserChannel.prototype.extraParams_ = null; /** * The current ChannelRequest object for the forwardchannel. * @type {goog.net.ChannelRequest?} * @private */ goog.net.BrowserChannel.prototype.forwardChannelRequest_ = null; /** * The ChannelRequest object for the backchannel. * @type {goog.net.ChannelRequest?} * @private */ goog.net.BrowserChannel.prototype.backChannelRequest_ = null; /** * The relative path (in the context of the the page hosting the browser * channel) for making requests to the server. * @type {?string} * @private */ goog.net.BrowserChannel.prototype.path_ = null; /** * The absolute URI for the forwardchannel request. * @type {goog.Uri} * @private */ goog.net.BrowserChannel.prototype.forwardChannelUri_ = null; /** * The absolute URI for the backchannel request. * @type {goog.Uri} * @private */ goog.net.BrowserChannel.prototype.backChannelUri_ = null; /** * A subdomain prefix for using a subdomain in IE for the backchannel * requests. * @type {?string} * @private */ goog.net.BrowserChannel.prototype.hostPrefix_ = null; /** * Whether we allow the use of a subdomain in IE for the backchannel requests. * @private */ goog.net.BrowserChannel.prototype.allowHostPrefix_ = true; /** * The next id to use for the RID (request identifier) parameter. This * identifier uniquely identifies the forward channel request. * @type {number} * @private */ goog.net.BrowserChannel.prototype.nextRid_ = 0; /** * The id to use for the next outgoing map. This identifier uniquely * identifies a sent map. * @type {number} * @private */ goog.net.BrowserChannel.prototype.nextMapId_ = 0; /** * Whether to fail forward-channel requests after one try, or after a few tries. * @type {boolean} * @private */ goog.net.BrowserChannel.prototype.failFast_ = false; /** * The handler that receive callbacks for state changes and data. * @type {goog.net.BrowserChannel.Handler} * @private */ goog.net.BrowserChannel.prototype.handler_ = null; /** * Timer identifier for asynchronously making a forward channel request. * @type {?number} * @private */ goog.net.BrowserChannel.prototype.forwardChannelTimerId_ = null; /** * Timer identifier for asynchronously making a back channel request. * @type {?number} * @private */ goog.net.BrowserChannel.prototype.backChannelTimerId_ = null; /** * Timer identifier for the timer that waits for us to retry the backchannel in * the case where it is dead and no longer receiving data. * @type {?number} * @private */ goog.net.BrowserChannel.prototype.deadBackChannelTimerId_ = null; /** * The BrowserTestChannel object which encapsulates the logic for determining * interesting network conditions about the client. * @type {goog.net.BrowserTestChannel?} * @private */ goog.net.BrowserChannel.prototype.connectionTest_ = null; /** * Whether the client's network conditions can support chunked responses. * @type {?boolean} * @private */ goog.net.BrowserChannel.prototype.useChunked_ = null; /** * Whether chunked mode is allowed. In certain debugging situations, it's * useful to disable this. * @private */ goog.net.BrowserChannel.prototype.allowChunkedMode_ = true; /** * The array identifier of the last array received from the server for the * backchannel request. * @type {number} * @private */ goog.net.BrowserChannel.prototype.lastArrayId_ = -1; /** * The array identifier of the last array sent by the server that we know about. * @type {number} * @private */ goog.net.BrowserChannel.prototype.lastPostResponseArrayId_ = -1; /** * The last status code received. * @type {number} * @private */ goog.net.BrowserChannel.prototype.lastStatusCode_ = -1; /** * Number of times we have retried the current forward channel request. * @type {number} * @private */ goog.net.BrowserChannel.prototype.forwardChannelRetryCount_ = 0; /** * Number of times it a row that we have retried the current back channel * request and received no data. * @type {number} * @private */ goog.net.BrowserChannel.prototype.backChannelRetryCount_ = 0; /** * The attempt id for the current back channel request. Starts at 1 and * increments for each reconnect. The server uses this to log if our connection * is flaky or not. * @type {number} * @private */ goog.net.BrowserChannel.prototype.backChannelAttemptId_; /** * The base part of the time before firing next retry request. Default is 5 * seconds. Note that a random delay is added (see {@link retryDelaySeedMs_}) * for all retries, and linear backoff is applied to the sum for subsequent * retries. * @type {number} * @private */ goog.net.BrowserChannel.prototype.baseRetryDelayMs_ = 5 * 1000; /** * A random time between 0 and this number of MS is added to the * {@link baseRetryDelayMs_}. Default is 10 seconds. * @type {number} * @private */ goog.net.BrowserChannel.prototype.retryDelaySeedMs_ = 10 * 1000; /** * Maximum number of attempts to connect to the server for forward channel * requests. Defaults to 2. * @type {number} * @private */ goog.net.BrowserChannel.prototype.forwardChannelMaxRetries_ = 2; /** * The timeout in milliseconds for a forward channel request. Defaults to 20 * seconds. Note that part of this timeout can be randomized. * @type {number} * @private */ goog.net.BrowserChannel.prototype.forwardChannelRequestTimeoutMs_ = 20 * 1000; /** * A throttle time in ms for readystatechange events for the backchannel. * Useful for throttling when ready state is INTERACTIVE (partial data). * * This throttle is useful if the server sends large data chunks down the * backchannel. It prevents examining XHR partial data on every * readystate change event. This is useful because large chunks can * trigger hundreds of readystatechange events, each of which takes ~5ms * or so to handle, in turn making the UI unresponsive for a significant period. * * If set to zero no throttle is used. * @type {number} * @private */ goog.net.BrowserChannel.prototype.readyStateChangeThrottleMs_ = 0; /** * Whether cross origin requests are supported for the browser channel. * * See {@link goog.net.XhrIo#setWithCredentials}. * @type {boolean} * @private */ goog.net.BrowserChannel.prototype.supportsCrossDomainXhrs_ = false; /** * The latest protocol version that this class supports. We request this version * from the server when opening the connection. Should match * com.google.net.browserchannel.BrowserChannel.LATEST_CHANNEL_VERSION. * @type {number} */ goog.net.BrowserChannel.LATEST_CHANNEL_VERSION = 8; /** * The channel version that we negotiated with the server for this session. * Starts out as the version we request, and then is changed to the negotiated * version after the initial open. * @type {number} * @private */ goog.net.BrowserChannel.prototype.channelVersion_ = goog.net.BrowserChannel.LATEST_CHANNEL_VERSION; /** * Enum type for the browser channel state machine. * @enum {number} */ goog.net.BrowserChannel.State = { /** The channel is closed. */ CLOSED: 0, /** The channel has been initialized but hasn't yet initiated a connection. */ INIT: 1, /** The channel is in the process of opening a connection to the server. */ OPENING: 2, /** The channel is open. */ OPENED: 3 }; /** * The timeout in milliseconds for a forward channel request. * @type {number} */ goog.net.BrowserChannel.FORWARD_CHANNEL_RETRY_TIMEOUT = 20 * 1000; /** * Maximum number of attempts to connect to the server for back channel * requests. * @type {number} */ goog.net.BrowserChannel.BACK_CHANNEL_MAX_RETRIES = 3; /** * A number in MS of how long we guess the maxmium amount of time a round trip * to the server should take. In the future this could be substituted with a * real measurement of the RTT. * @type {number} */ goog.net.BrowserChannel.RTT_ESTIMATE = 3 * 1000; /** * When retrying for an inactive channel, we will multiply the total delay by * this number. * @type {number} */ goog.net.BrowserChannel.INACTIVE_CHANNEL_RETRY_FACTOR = 2; /** * Enum type for identifying a BrowserChannel error. * @enum {number} */ goog.net.BrowserChannel.Error = { /** Value that indicates no error has occurred. */ OK: 0, /** An error due to a request failing. */ REQUEST_FAILED: 2, /** An error due to the user being logged out. */ LOGGED_OUT: 4, /** An error due to server response which contains no data. */ NO_DATA: 5, /** An error due to a server response indicating an unknown session id */ UNKNOWN_SESSION_ID: 6, /** An error due to a server response requesting to stop the channel. */ STOP: 7, /** A general network error. */ NETWORK: 8, /** An error due to the channel being blocked by a network administrator. */ BLOCKED: 9, /** An error due to bad data being returned from the server. */ BAD_DATA: 10, /** An error due to a response that doesn't start with the magic cookie. */ BAD_RESPONSE: 11, /** ActiveX is blocked by the machine's admin settings. */ ACTIVE_X_BLOCKED: 12 }; /** * Internal enum type for the two browser channel channel types. * @enum {number} * @private */ goog.net.BrowserChannel.ChannelType_ = { FORWARD_CHANNEL: 1, BACK_CHANNEL: 2 }; /** * The maximum number of maps that can be sent in one POST. Should match * com.google.net.browserchannel.BrowserChannel.MAX_MAPS_PER_REQUEST. * @type {number} * @private */ goog.net.BrowserChannel.MAX_MAPS_PER_REQUEST_ = 1000; /** * Singleton event target for firing stat events * @type {goog.events.EventTarget} * @private */ goog.net.BrowserChannel.statEventTarget_ = new goog.events.EventTarget(); /** * Events fired by BrowserChannel and associated objects * @const */ goog.net.BrowserChannel.Event = {}; /** * Stat Event that fires when things of interest happen that may be useful for * applications to know about for stats or debugging purposes. This event fires * on the EventTarget returned by getStatEventTarget. */ goog.net.BrowserChannel.Event.STAT_EVENT = 'statevent'; /** * Event class for goog.net.BrowserChannel.Event.STAT_EVENT * * @param {goog.events.EventTarget} eventTarget The stat event target for the browser channel. * @param {goog.net.BrowserChannel.Stat} stat The stat. * @constructor * @extends {goog.events.Event} * @final */ goog.net.BrowserChannel.StatEvent = function(eventTarget, stat) { goog.events.Event.call( this, goog.net.BrowserChannel.Event.STAT_EVENT, eventTarget); /** * The stat * @type {goog.net.BrowserChannel.Stat} */ this.stat = stat; }; goog.inherits(goog.net.BrowserChannel.StatEvent, goog.events.Event); /** * An event that fires when POST requests complete successfully, indicating * the size of the POST and the round trip time. * This event fires on the EventTarget returned by getStatEventTarget. */ goog.net.BrowserChannel.Event.TIMING_EVENT = 'timingevent'; /** * Event class for goog.net.BrowserChannel.Event.TIMING_EVENT * * @param {goog.events.EventTarget} target The stat event target for the browser channel. * @param {number} size The number of characters in the POST data. * @param {number} rtt The total round trip time from POST to response in MS. * @param {number} retries The number of times the POST had to be retried. * @constructor * @extends {goog.events.Event} * @final */ goog.net.BrowserChannel.TimingEvent = function(target, size, rtt, retries) { goog.events.Event.call( this, goog.net.BrowserChannel.Event.TIMING_EVENT, target); /** * @type {number} */ this.size = size; /** * @type {number} */ this.rtt = rtt; /** * @type {number} */ this.retries = retries; }; goog.inherits(goog.net.BrowserChannel.TimingEvent, goog.events.Event); /** * The type of event that occurs every time some information about how reachable * the server is is discovered. */ goog.net.BrowserChannel.Event.SERVER_REACHABILITY_EVENT = 'serverreachability'; /** * Types of events which reveal information about the reachability of the * server. * @enum {number} */ goog.net.BrowserChannel.ServerReachability = { REQUEST_MADE: 1, REQUEST_SUCCEEDED: 2, REQUEST_FAILED: 3, BACK_CHANNEL_ACTIVITY: 4 }; /** * Event class for goog.net.BrowserChannel.Event.SERVER_REACHABILITY_EVENT. * * @param {goog.events.EventTarget} target The stat event target for the browser channel. * @param {goog.net.BrowserChannel.ServerReachability} reachabilityType The * reachability event type. * @constructor * @extends {goog.events.Event} * @final */ goog.net.BrowserChannel.ServerReachabilityEvent = function( target, reachabilityType) { goog.events.Event.call( this, goog.net.BrowserChannel.Event.SERVER_REACHABILITY_EVENT, target); /** * @type {goog.net.BrowserChannel.ServerReachability} */ this.reachabilityType = reachabilityType; }; goog.inherits( goog.net.BrowserChannel.ServerReachabilityEvent, goog.events.Event); /** * Enum that identifies events for statistics that are interesting to track. * TODO(user) - Change name not to use Event or use EventTarget * @enum {number} */ goog.net.BrowserChannel.Stat = { /** Event indicating a new connection attempt. */ CONNECT_ATTEMPT: 0, /** Event indicating a connection error due to a general network problem. */ ERROR_NETWORK: 1, /** * Event indicating a connection error that isn't due to a general network * problem. */ ERROR_OTHER: 2, /** Event indicating the start of test stage one. */ TEST_STAGE_ONE_START: 3, /** Event indicating the channel is blocked by a network administrator. */ CHANNEL_BLOCKED: 4, /** Event indicating the start of test stage two. */ TEST_STAGE_TWO_START: 5, /** Event indicating the first piece of test data was received. */ TEST_STAGE_TWO_DATA_ONE: 6, /** * Event indicating that the second piece of test data was received and it was * received separately from the first. */ TEST_STAGE_TWO_DATA_TWO: 7, /** Event indicating both pieces of test data were received simultaneously. */ TEST_STAGE_TWO_DATA_BOTH: 8, /** Event indicating stage one of the test request failed. */ TEST_STAGE_ONE_FAILED: 9, /** Event indicating stage two of the test request failed. */ TEST_STAGE_TWO_FAILED: 10, /** * Event indicating that a buffering proxy is likely between the client and * the server. */ PROXY: 11, /** * Event indicating that no buffering proxy is likely between the client and * the server. */ NOPROXY: 12, /** Event indicating an unknown SID error. */ REQUEST_UNKNOWN_SESSION_ID: 13, /** Event indicating a bad status code was received. */ REQUEST_BAD_STATUS: 14, /** Event indicating incomplete data was received */ REQUEST_INCOMPLETE_DATA: 15, /** Event indicating bad data was received */ REQUEST_BAD_DATA: 16, /** Event indicating no data was received when data was expected. */ REQUEST_NO_DATA: 17, /** Event indicating a request timeout. */ REQUEST_TIMEOUT: 18, /** * Event indicating that the server never received our hanging GET and so it * is being retried. */ BACKCHANNEL_MISSING: 19, /** * Event indicating that we have determined that our hanging GET is not * receiving data when it should be. Thus it is dead dead and will be retried. */ BACKCHANNEL_DEAD: 20, /** * The browser declared itself offline during the lifetime of a request, or * was offline when a request was initially made. */ BROWSER_OFFLINE: 21, /** ActiveX is blocked by the machine's admin settings. */ ACTIVE_X_BLOCKED: 22 }; /** * A guess at a cutoff at which to no longer assume the backchannel is dead * when we are slow to receive data. Number in bytes. * * Assumption: The worst bandwidth we work on is 50 kilobits/sec * 50kbits/sec * (1 byte / 8 bits) * 6 sec dead backchannel timeout * @type {number} */ goog.net.BrowserChannel.OUTSTANDING_DATA_BACKCHANNEL_RETRY_CUTOFF = 37500; /** * Returns the browserchannel logger. * * @return {!goog.net.ChannelDebug} The channel debug object. */ goog.net.BrowserChannel.prototype.getChannelDebug = function() { return this.channelDebug_; }; /** * Set the browserchannel logger. * TODO(user): Add interface for channel loggers or remove this function. * * @param {goog.net.ChannelDebug} channelDebug The channel debug object. */ goog.net.BrowserChannel.prototype.setChannelDebug = function(channelDebug) { if (goog.isDefAndNotNull(channelDebug)) { this.channelDebug_ = channelDebug; } }; /** * Allows the application to set an execution hooks for when BrowserChannel * starts processing requests. This is useful to track timing or logging * special information. The function takes no parameters and return void. * @param {Function} startHook The function for the start hook. */ goog.net.BrowserChannel.setStartThreadExecutionHook = function(startHook) { goog.net.BrowserChannel.startExecutionHook_ = startHook; }; /** * Allows the application to set an execution hooks for when BrowserChannel * stops processing requests. This is useful to track timing or logging * special information. The function takes no parameters and return void. * @param {Function} endHook The function for the end hook. */ goog.net.BrowserChannel.setEndThreadExecutionHook = function(endHook) { goog.net.BrowserChannel.endExecutionHook_ = endHook; }; /** * Application provided execution hook for the start hook. * * @type {Function} * @private */ goog.net.BrowserChannel.startExecutionHook_ = function() {}; /** * Application provided execution hook for the end hook. * * @type {Function} * @private */ goog.net.BrowserChannel.endExecutionHook_ = function() {}; /** * Instantiates a ChannelRequest with the given parameters. Overidden in tests. * * @param {goog.net.BrowserChannel|goog.net.BrowserTestChannel} channel * The BrowserChannel that owns this request. * @param {goog.net.ChannelDebug} channelDebug A ChannelDebug to use for * logging. * @param {string=} opt_sessionId The session id for the channel. * @param {string|number=} opt_requestId The request id for this request. * @param {number=} opt_retryId The retry id for this request. * @return {!goog.net.ChannelRequest} The created channel request. */ goog.net.BrowserChannel.createChannelRequest = function( channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId) { return new goog.net.ChannelRequest( channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId); }; /** * Starts the channel. This initiates connections to the server. * * @param {string} testPath The path for the test connection. * @param {string} channelPath The path for the channel connection. * @param {Object=} opt_extraParams Extra parameter keys and values to add to * the requests. * @param {string=} opt_oldSessionId Session ID from a previous session. * @param {number=} opt_oldArrayId The last array ID from a previous session. */ goog.net.BrowserChannel.prototype.connect = function( testPath, channelPath, opt_extraParams, opt_oldSessionId, opt_oldArrayId) { this.channelDebug_.debug('connect()'); goog.net.BrowserChannel.notifyStatEvent( goog.net.BrowserChannel.Stat.CONNECT_ATTEMPT); this.path_ = channelPath; this.extraParams_ = opt_extraParams || {}; // Attach parameters about the previous session if reconnecting. if (opt_oldSessionId && goog.isDef(opt_oldArrayId)) { this.extraParams_['OSID'] = opt_oldSessionId; this.extraParams_['OAID'] = opt_oldArrayId; } if (this.asyncTest_) { goog.net.BrowserChannel.setTimeout( goog.bind(this.connectTest_, this, testPath), 100); this.connectChannel_(); } else { this.connectTest_(testPath); } }; /** * Disconnects and closes the channel. */ goog.net.BrowserChannel.prototype.disconnect = function() { this.channelDebug_.debug('disconnect()'); this.cancelRequests_(); if (this.state_ == goog.net.BrowserChannel.State.OPENED) { var rid = this.nextRid_++; var uri = this.forwardChannelUri_.clone(); uri.setParameterValue('SID', this.sid_); uri.setParameterValue('RID', rid); uri.setParameterValue('TYPE', 'terminate'); // Add the reconnect parameters. this.addAdditionalParams_(uri); var request = goog.net.BrowserChannel.createChannelRequest( this, this.channelDebug_, this.sid_, rid); request.sendUsingImgTag(uri); } this.onClose_(); }; /** * Returns the session id of the channel. Only available after the * channel has been opened. * @return {string} Session ID. */ goog.net.BrowserChannel.prototype.getSessionId = function() { return this.sid_; }; /** * Starts the test channel to determine network conditions. * * @param {string} testPath The relative PATH for the test connection. * @private */ goog.net.BrowserChannel.prototype.connectTest_ = function(testPath) { this.channelDebug_.debug('connectTest_()'); if (!this.okToMakeRequest_()) { return; // channel is cancelled } this.connectionTest_ = new goog.net.BrowserTestChannel(this, this.channelDebug_); this.connectionTest_.setExtraHeaders(this.extraHeaders_); this.connectionTest_.setParser(this.parser_); this.connectionTest_.connect(testPath); }; /** * Starts the regular channel which is run after the test channel is complete. * @private */ goog.net.BrowserChannel.prototype.connectChannel_ = function() { this.channelDebug_.debug('connectChannel_()'); this.ensureInState_( goog.net.BrowserChannel.State.INIT, goog.net.BrowserChannel.State.CLOSED); this.forwardChannelUri_ = this.getForwardChannelUri(/** @type {string} */ (this.path_)); this.ensureForwardChannel_(); }; /** * Cancels all outstanding requests. * @private */ goog.net.BrowserChannel.prototype.cancelRequests_ = function() { if (this.connectionTest_) { this.connectionTest_.abort(); this.connectionTest_ = null; } if (this.backChannelRequest_) { this.backChannelRequest_.cancel(); this.backChannelRequest_ = null; } if (this.backChannelTimerId_) { goog.global.clearTimeout(this.backChannelTimerId_); this.backChannelTimerId_ = null; } this.clearDeadBackchannelTimer_(); if (this.forwardChannelRequest_) { this.forwardChannelRequest_.cancel(); this.forwardChannelRequest_ = null; } if (this.forwardChannelTimerId_) { goog.global.clearTimeout(this.forwardChannelTimerId_); this.forwardChannelTimerId_ = null; } }; /** * Returns the extra HTTP headers to add to all the requests sent to the server. * * @return {Object} The HTTP headers, or null. */ goog.net.BrowserChannel.prototype.getExtraHeaders = function() { return this.extraHeaders_; }; /** * Sets extra HTTP headers to add to all the requests sent to the server. * * @param {Object} extraHeaders The HTTP headers, or null. */ goog.net.BrowserChannel.prototype.setExtraHeaders = function(extraHeaders) { this.extraHeaders_ = extraHeaders; }; /** * Sets the throttle for handling onreadystatechange events for the request. * * @param {number} throttle The throttle in ms. A value of zero indicates * no throttle. */ goog.net.BrowserChannel.prototype.setReadyStateChangeThrottle = function( throttle) { this.readyStateChangeThrottleMs_ = throttle; }; /** * Sets whether cross origin requests are supported for the browser channel. * * Setting this allows the creation of requests to secondary domains and * sends XHRs with the CORS withCredentials bit set to true. * * In order for cross-origin requests to work, the server will also need to set * CORS response headers as per: * https://developer.mozilla.org/en-US/docs/HTTP_access_control * * See {@link goog.net.XhrIo#setWithCredentials}. * @param {boolean} supportCrossDomain Whether cross domain XHRs are supported. */ goog.net.BrowserChannel.prototype.setSupportsCrossDomainXhrs = function( supportCrossDomain) { this.supportsCrossDomainXhrs_ = supportCrossDomain; }; /** * Returns the handler used for channel callback events. * * @return {goog.net.BrowserChannel.Handler} The handler. */ goog.net.BrowserChannel.prototype.getHandler = function() { return this.handler_; }; /** * Sets the handler used for channel callback events. * @param {goog.net.BrowserChannel.Handler} handler The handler to set. */ goog.net.BrowserChannel.prototype.setHandler = function(handler) { this.handler_ = handler; }; /** * Returns whether the channel allows the use of a subdomain. There may be * cases where this isn't allowed. * @return {boolean} Whether a host prefix is allowed. */ goog.net.BrowserChannel.prototype.getAllowHostPrefix = function() { return this.allowHostPrefix_; }; /** * Sets whether the channel allows the use of a subdomain. There may be cases * where this isn't allowed, for example, logging in with troutboard where * using a subdomain causes Apache to force the user to authenticate twice. * @param {boolean} allowHostPrefix Whether a host prefix is allowed. */ goog.net.BrowserChannel.prototype.setAllowHostPrefix = function( allowHostPrefix) { this.allowHostPrefix_ = allowHostPrefix; }; /** * Returns whether the channel is buffered or not. This state is valid for * querying only after the test connection has completed. This may be * queried in the goog.net.BrowserChannel.okToMakeRequest() callback. * A channel may be buffered if the test connection determines that * a chunked response could not be sent down within a suitable time. * @return {boolean} Whether the channel is buffered. */ goog.net.BrowserChannel.prototype.isBuffered = function() { return !this.useChunked_; }; /** * Returns whether chunked mode is allowed. In certain debugging situations, * it's useful for the application to have a way to disable chunked mode for a * user. * @return {boolean} Whether chunked mode is allowed. */ goog.net.BrowserChannel.prototype.getAllowChunkedMode = function() { return this.allowChunkedMode_; }; /** * Sets whether chunked mode is allowed. In certain debugging situations, it's * useful for the application to have a way to disable chunked mode for a user. * @param {boolean} allowChunkedMode Whether chunked mode is allowed. */ goog.net.BrowserChannel.prototype.setAllowChunkedMode = function( allowChunkedMode) { this.allowChunkedMode_ = allowChunkedMode; }; /** * Sends a request to the server. The format of the request is a Map data * structure of key/value pairs. These maps are then encoded in a format * suitable for the wire and then reconstituted as a Map data structure that * the server can process. * @param {Object} map The map to send. * @param {?Object=} opt_context The context associated with the map. */ goog.net.BrowserChannel.prototype.sendMap = function(map, opt_context) { if (this.state_ == goog.net.BrowserChannel.State.CLOSED) { throw Error('Invalid operation: sending map when state is closed'); } // We can only send 1000 maps per POST, but typically we should never have // that much to send, so warn if we exceed that (we still send all the maps). if (this.outgoingMaps_.length == goog.net.BrowserChannel.MAX_MAPS_PER_REQUEST_) { // severe() is temporary so that we get these uploaded and can figure out // what's causing them. Afterwards can change to warning(). this.channelDebug_.severe( 'Already have ' + goog.net.BrowserChannel.MAX_MAPS_PER_REQUEST_ + ' queued maps upon queueing ' + this.parser_.stringify(map)); } this.outgoingMaps_.push( new goog.net.BrowserChannel.QueuedMap( this.nextMapId_++, map, opt_context)); if (this.state_ == goog.net.BrowserChannel.State.OPENING || this.state_ == goog.net.BrowserChannel.State.OPENED) { this.ensureForwardChannel_(); } }; /** * When set to true, this changes the behavior of the forward channel so it * will not retry requests; it will fail after one network failure, and if * there was already one network failure, the request will fail immediately. * @param {boolean} failFast Whether or not to fail fast. */ goog.net.BrowserChannel.prototype.setFailFast = function(failFast) { this.failFast_ = failFast; this.channelDebug_.info('setFailFast: ' + failFast); if ((this.forwardChannelRequest_ || this.forwardChannelTimerId_) && this.forwardChannelRetryCount_ > this.getForwardChannelMaxRetries()) { this.channelDebug_.info( 'Retry count ' + this.forwardChannelRetryCount_ + ' > new maxRetries ' + this.getForwardChannelMaxRetries() + '. Fail immediately!'); if (this.forwardChannelRequest_) { this.forwardChannelRequest_.cancel(); // Go through the standard onRequestComplete logic to expose the max-retry // failure in the standard way. this.onRequestComplete(this.forwardChannelRequest_); } else { // i.e., this.forwardChannelTimerId_ goog.global.clearTimeout(this.forwardChannelTimerId_); this.forwardChannelTimerId_ = null; // The error code from the last failed request is gone, so just use a // generic one. this.signalError_(goog.net.BrowserChannel.Error.REQUEST_FAILED); } } }; /** * @return {number} The max number of forward-channel retries, which will be 0 * in fail-fast mode. */ goog.net.BrowserChannel.prototype.getForwardChannelMaxRetries = function() { return this.failFast_ ? 0 : this.forwardChannelMaxRetries_; }; /** * Sets the maximum number of attempts to connect to the server for forward * channel requests. * @param {number} retries The maximum number of attempts. */ goog.net.BrowserChannel.prototype.setForwardChannelMaxRetries = function( retries) { this.forwardChannelMaxRetries_ = retries; }; /** * Sets the timeout for a forward channel request. * @param {number} timeoutMs The timeout in milliseconds. */ goog.net.BrowserChannel.prototype.setForwardChannelRequestTimeout = function( timeoutMs) { this.forwardChannelRequestTimeoutMs_ = timeoutMs; }; /** * @return {number} The max number of back-channel retries, which is a constant. */ goog.net.BrowserChannel.prototype.getBackChannelMaxRetries = function() { // Back-channel retries is a constant. return goog.net.BrowserChannel.BACK_CHANNEL_MAX_RETRIES; }; /** * Returns whether the channel is closed * @return {boolean} true if the channel is closed. */ goog.net.BrowserChannel.prototype.isClosed = function() { return this.state_ == goog.net.BrowserChannel.State.CLOSED; }; /** * Returns the browser channel state. * @return {goog.net.BrowserChannel.State} The current state of the browser * channel. */ goog.net.BrowserChannel.prototype.getState = function() { return this.state_; }; /** * Return the last status code received for a request. * @return {number} The last status code received for a request. */ goog.net.BrowserChannel.prototype.getLastStatusCode = function() { return this.lastStatusCode_; }; /** * @return {number} The last array id received. */ goog.net.BrowserChannel.prototype.getLastArrayId = function() { return this.lastArrayId_; }; /** * Returns whether there are outstanding requests servicing the channel. * @return {boolean} true if there are outstanding requests. */ goog.net.BrowserChannel.prototype.hasOutstandingRequests = function() { return this.outstandingRequests_() != 0; }; /** * Sets a new parser for the response payload. * @param {!goog.string.Parser} parser Parser. */ goog.net.BrowserChannel.prototype.setParser = function(parser) { this.parser_ = parser; }; /** * Returns the number of outstanding requests. * @return {number} The number of outstanding requests to the server. * @private */ goog.net.BrowserChannel.prototype.outstandingRequests_ = function() { var count = 0; if (this.backChannelRequest_) { count++; } if (this.forwardChannelRequest_) { count++; } return count; }; /** * Ensures that a forward channel request is scheduled. * @private */ goog.net.BrowserChannel.prototype.ensureForwardChannel_ = function() { if (this.forwardChannelRequest_) { // connection in process - no need to start a new request return; } if (this.forwardChannelTimerId_) { // no need to start a new request - one is already scheduled return; } this.forwardChannelTimerId_ = goog.net.BrowserChannel.setTimeout( goog.bind(this.onStartForwardChannelTimer_, this), 0); this.forwardChannelRetryCount_ = 0; }; /** * Schedules a forward-channel retry for the specified request, unless the max * retries has been reached. * @param {goog.net.ChannelRequest} request The failed request to retry. * @return {boolean} true iff a retry was scheduled. * @private */ goog.net.BrowserChannel.prototype.maybeRetryForwardChannel_ = function( request) { if (this.forwardChannelRequest_ || this.forwardChannelTimerId_) { // Should be impossible to be called in this state. this.channelDebug_.severe('Request already in progress'); return false; } if (this.state_ == goog.net.BrowserChannel.State.INIT || // no retry open_() (this.forwardChannelRetryCount_ >= this.getForwardChannelMaxRetries())) { return false; } this.channelDebug_.debug('Going to retry POST'); this.forwardChannelTimerId_ = goog.net.BrowserChannel.setTimeout( goog.bind(this.onStartForwardChannelTimer_, this, request), this.getRetryTime_(this.forwardChannelRetryCount_)); this.forwardChannelRetryCount_++; return true; }; /** * Timer callback for ensureForwardChannel * @param {goog.net.ChannelRequest=} opt_retryRequest A failed request to retry. * @private */ goog.net.BrowserChannel.prototype.onStartForwardChannelTimer_ = function( opt_retryRequest) { this.forwardChannelTimerId_ = null; this.startForwardChannel_(opt_retryRequest); }; /** * Begins a new forward channel operation to the server. * @param {goog.net.ChannelRequest=} opt_retryRequest A failed request to retry. * @private */ goog.net.BrowserChannel.prototype.startForwardChannel_ = function( opt_retryRequest) { this.channelDebug_.debug('startForwardChannel_'); if (!this.okToMakeRequest_()) { return; // channel is cancelled } else if (this.state_ == goog.net.BrowserChannel.State.INIT) { if (opt_retryRequest) { this.channelDebug_.severe('Not supposed to retry the open'); return; } this.open_(); this.state_ = goog.net.BrowserChannel.State.OPENING; } else if (this.state_ == goog.net.BrowserChannel.State.OPENED) { if (opt_retryRequest) { this.makeForwardChannelRequest_(opt_retryRequest); return; } if (this.outgoingMaps_.length == 0) { this.channelDebug_.debug( 'startForwardChannel_ returned: ' + 'nothing to send'); // no need to start a new forward channel request return; } if (this.forwardChannelRequest_) { // Should be impossible to be called in this state. this.channelDebug_.severe( 'startForwardChannel_ returned: ' + 'connection already in progress'); return; } this.makeForwardChannelRequest_(); this.channelDebug_.debug('startForwardChannel_ finished, sent request'); } }; /** * Establishes a new channel session with the the server. * @private */ goog.net.BrowserChannel.prototype.open_ = function() { this.channelDebug_.debug('open_()'); this.nextRid_ = Math.floor(Math.random() * 100000); var rid = this.nextRid_++; var request = goog.net.BrowserChannel.createChannelRequest( this, this.channelDebug_, '', rid); request.setExtraHeaders(this.extraHeaders_); var requestText = this.dequeueOutgoingMaps_(); var uri = this.forwardChannelUri_.clone(); uri.setParameterValue('RID', rid); if (this.clientVersion_) { uri.setParameterValue('CVER', this.clientVersion_); } // Add the reconnect parameters. this.addAdditionalParams_(uri); request.xmlHttpPost(uri, requestText, true); this.forwardChannelRequest_ = request; }; /** * Makes a forward channel request using XMLHTTP. * @param {goog.net.ChannelRequest=} opt_retryRequest A failed request to retry. * @private */ goog.net.BrowserChannel.prototype.makeForwardChannelRequest_ = function( opt_retryRequest) { var rid; var requestText; if (opt_retryRequest) { if (this.channelVersion_ > 6) { // In version 7 and up we can tack on new arrays to a retry. this.requeuePendingMaps_(); rid = this.nextRid_ - 1; // Must use last RID requestText = this.dequeueOutgoingMaps_(); } else { // TODO(user): Remove this code and the opt_retryRequest passing // once server-side support for ver 7 is ubiquitous. rid = opt_retryRequest.getRequestId(); requestText = /** @type {string} */ (opt_retryRequest.getPostData()); } } else { rid = this.nextRid_++; requestText = this.dequeueOutgoingMaps_(); } var uri = this.forwardChannelUri_.clone(); uri.setParameterValue('SID', this.sid_); uri.setParameterValue('RID', rid); uri.setParameterValue('AID', this.lastArrayId_); // Add the additional reconnect parameters. this.addAdditionalParams_(uri); var request = goog.net.BrowserChannel.createChannelRequest( this, this.channelDebug_, this.sid_, rid, this.forwardChannelRetryCount_ + 1); request.setExtraHeaders(this.extraHeaders_); // randomize from 50%-100% of the forward channel timeout to avoid // a big hit if servers happen to die at once. request.setTimeout( Math.round(this.forwardChannelRequestTimeoutMs_ * 0.50) + Math.round(this.forwardChannelRequestTimeoutMs_ * 0.50 * Math.random())); this.forwardChannelRequest_ = request; request.xmlHttpPost(uri, requestText, true); }; /** * Adds the additional parameters from the handler to the given URI. * @param {goog.Uri} uri The URI to add the parameters to. * @private */ goog.net.BrowserChannel.prototype.addAdditionalParams_ = function(uri) { // Add the additional reconnect parameters as needed. if (this.handler_) { var params = this.handler_.getAdditionalParams(this); if (params) { goog.object.forEach( params, function(value, key) { uri.setParameterValue(key, value); }); } } }; /** * Returns the request text from the outgoing maps and resets it. * @return {string} The encoded request text created from all the currently * queued outgoing maps. * @private */ goog.net.BrowserChannel.prototype.dequeueOutgoingMaps_ = function() { var count = Math.min( this.outgoingMaps_.length, goog.net.BrowserChannel.MAX_MAPS_PER_REQUEST_); var sb = ['count=' + count]; var offset; if (this.channelVersion_ > 6 && count > 0) { // To save a bit of bandwidth, specify the base mapId and the rest as // offsets from it. offset = this.outgoingMaps_[0].mapId; sb.push('ofs=' + offset); } else { offset = 0; } for (var i = 0; i < count; i++) { var mapId = this.outgoingMaps_[i].mapId; var map = this.outgoingMaps_[i].map; if (this.channelVersion_ <= 6) { // Map IDs were not used in ver 6 and before, just indexes in the request. mapId = i; } else { mapId -= offset; } try { goog.object.forEach(map, function(value, key, coll) { sb.push('req' + mapId + '_' + key + '=' + encodeURIComponent(value)); }); } catch (ex) { // We send a map here because lots of the retry logic relies on map IDs, // so we have to send something. sb.push( 'req' + mapId + '_' + 'type' + '=' + encodeURIComponent('_badmap')); if (this.handler_) { this.handler_.badMapError(this, map); } } } this.pendingMaps_ = this.pendingMaps_.concat(this.outgoingMaps_.splice(0, count)); return sb.join('&'); }; /** * Requeues unacknowledged sent arrays for retransmission in the next forward * channel request. * @private */ goog.net.BrowserChannel.prototype.requeuePendingMaps_ = function() { this.outgoingMaps_ = this.pendingMaps_.concat(this.outgoingMaps_); this.pendingMaps_.length = 0; }; /** * Ensures there is a backchannel request for receiving data from the server. * @private */ goog.net.BrowserChannel.prototype.ensureBackChannel_ = function() { if (this.backChannelRequest_) { // already have one return; } if (this.backChannelTimerId_) { // no need to start a new request - one is already scheduled return; } this.backChannelAttemptId_ = 1; this.backChannelTimerId_ = goog.net.BrowserChannel.setTimeout( goog.bind(this.onStartBackChannelTimer_, this), 0); this.backChannelRetryCount_ = 0; }; /** * Schedules a back-channel retry, unless the max retries has been reached. * @return {boolean} true iff a retry was scheduled. * @private */ goog.net.BrowserChannel.prototype.maybeRetryBackChannel_ = function() { if (this.backChannelRequest_ || this.backChannelTimerId_) { // Should be impossible to be called in this state. this.channelDebug_.severe('Request already in progress'); return false; } if (this.backChannelRetryCount_ >= this.getBackChannelMaxRetries()) { return false; } this.channelDebug_.debug('Going to retry GET'); this.backChannelAttemptId_++; this.backChannelTimerId_ = goog.net.BrowserChannel.setTimeout( goog.bind(this.onStartBackChannelTimer_, this), this.getRetryTime_(this.backChannelRetryCount_)); this.backChannelRetryCount_++; return true; }; /** * Timer callback for ensureBackChannel_. * @private */ goog.net.BrowserChannel.prototype.onStartBackChannelTimer_ = function() { this.backChannelTimerId_ = null; this.startBackChannel_(); }; /** * Begins a new back channel operation to the server. * @private */ goog.net.BrowserChannel.prototype.startBackChannel_ = function() { if (!this.okToMakeRequest_()) { // channel is cancelled return; } this.channelDebug_.debug('Creating new HttpRequest'); this.backChannelRequest_ = goog.net.BrowserChannel.createChannelRequest( this, this.channelDebug_, this.sid_, 'rpc', this.backChannelAttemptId_); this.backChannelRequest_.setExtraHeaders(this.extraHeaders_); this.backChannelRequest_.setReadyStateChangeThrottle( this.readyStateChangeThrottleMs_); var uri = this.backChannelUri_.clone(); uri.setParameterValue('RID', 'rpc'); uri.setParameterValue('SID', this.sid_); uri.setParameterValue('CI', this.useChunked_ ? '0' : '1'); uri.setParameterValue('AID', this.lastArrayId_); // Add the reconnect parameters. this.addAdditionalParams_(uri); if (!goog.net.ChannelRequest.supportsXhrStreaming()) { uri.setParameterValue('TYPE', 'html'); this.backChannelRequest_.tridentGet(uri, Boolean(this.hostPrefix_)); } else { uri.setParameterValue('TYPE', 'xmlhttp'); this.backChannelRequest_.xmlHttpGet( uri, true /* decodeChunks */, this.hostPrefix_, false /* opt_noClose */); } this.channelDebug_.debug('New Request created'); }; /** * Gives the handler a chance to return an error code and stop channel * execution. A handler might want to do this to check that the user is still * logged in, for example. * @private * @return {boolean} If it's OK to make a request. */ goog.net.BrowserChannel.prototype.okToMakeRequest_ = function() { if (this.handler_) { var result = this.handler_.okToMakeRequest(this); if (result != goog.net.BrowserChannel.Error.OK) { this.channelDebug_.debug( 'Handler returned error code from ' + 'okToMakeRequest'); this.signalError_(result); return false; } } return true; }; /** * Callback from BrowserTestChannel for when the channel is finished. * @param {goog.net.BrowserTestChannel} testChannel The BrowserTestChannel. * @param {boolean} useChunked Whether we can chunk responses. */ goog.net.BrowserChannel.prototype.testConnectionFinished = function( testChannel, useChunked) { this.channelDebug_.debug('Test Connection Finished'); this.useChunked_ = this.allowChunkedMode_ && useChunked; this.lastStatusCode_ = testChannel.getLastStatusCode(); // When using asynchronous test, the channel is already open by connect(). if (!this.asyncTest_) { this.connectChannel_(); } }; /** * Callback from BrowserTestChannel for when the channel has an error. * @param {goog.net.BrowserTestChannel} testChannel The BrowserTestChannel. * @param {goog.net.ChannelRequest.Error} errorCode The error code of the failure. */ goog.net.BrowserChannel.prototype.testConnectionFailure = function( testChannel, errorCode) { this.channelDebug_.debug('Test Connection Failed'); this.lastStatusCode_ = testChannel.getLastStatusCode(); this.signalError_(goog.net.BrowserChannel.Error.REQUEST_FAILED); }; /** * Callback from BrowserTestChannel for when the channel is blocked. * @param {goog.net.BrowserTestChannel} testChannel The BrowserTestChannel. */ goog.net.BrowserChannel.prototype.testConnectionBlocked = function( testChannel) { this.channelDebug_.debug('Test Connection Blocked'); this.lastStatusCode_ = this.connectionTest_.getLastStatusCode(); this.signalError_(goog.net.BrowserChannel.Error.BLOCKED); }; /** * Callback from ChannelRequest for when new data is received * @param {goog.net.ChannelRequest} request The request object. * @param {string} responseText The text of the response. */ goog.net.BrowserChannel.prototype.onRequestData = function( request, responseText) { if (this.state_ == goog.net.BrowserChannel.State.CLOSED || (this.backChannelRequest_ != request && this.forwardChannelRequest_ != request)) { // either CLOSED or a request we don't know about (perhaps an old request) return; } this.lastStatusCode_ = request.getLastStatusCode(); if (this.forwardChannelRequest_ == request && this.state_ == goog.net.BrowserChannel.State.OPENED) { if (this.channelVersion_ > 7) { var response; try { response = this.parser_.parse(responseText); } catch (ex) { response = null; } if (goog.isArray(response) && response.length == 3) { this.handlePostResponse_(response); } else { this.channelDebug_.debug('Bad POST response data returned'); this.signalError_(goog.net.BrowserChannel.Error.BAD_RESPONSE); } } else if (responseText != goog.net.ChannelDebug.MAGIC_RESPONSE_COOKIE) { this.channelDebug_.debug( 'Bad data returned - missing/invald ' + 'magic cookie'); this.signalError_(goog.net.BrowserChannel.Error.BAD_RESPONSE); } } else { if (this.backChannelRequest_ == request) { this.clearDeadBackchannelTimer_(); } if (!goog.string.isEmptyOrWhitespace(responseText)) { var response = this.parser_.parse(responseText); goog.asserts.assert(goog.isArray(response)); this.onInput_(/** @type {!Array} */ (response)); } } }; /** * Handles a POST response from the server. * @param {Array} responseValues The key value pairs in the POST * response. * @private */ goog.net.BrowserChannel.prototype.handlePostResponse_ = function( responseValues) { // The first response value is set to 0 if server is missing backchannel. if (responseValues[0] == 0) { this.handleBackchannelMissing_(); return; } this.lastPostResponseArrayId_ = responseValues[1]; var outstandingArrays = this.lastPostResponseArrayId_ - this.lastArrayId_; if (0 < outstandingArrays) { var numOutstandingBackchannelBytes = responseValues[2]; this.channelDebug_.debug( numOutstandingBackchannelBytes + ' bytes (in ' + outstandingArrays + ' arrays) are outstanding on the BackChannel'); if (!this.shouldRetryBackChannel_(numOutstandingBackchannelBytes)) { return; } if (!this.deadBackChannelTimerId_) { // We expect to receive data within 2 RTTs or we retry the backchannel. this.deadBackChannelTimerId_ = goog.net.BrowserChannel.setTimeout( goog.bind(this.onBackChannelDead_, this), 2 * goog.net.BrowserChannel.RTT_ESTIMATE); } } }; /** * Handles a POST response from the server telling us that it has detected that * we have no hanging GET connection. * @private */ goog.net.BrowserChannel.prototype.handleBackchannelMissing_ = function() { // As long as the back channel was started before the POST was sent, // we should retry the backchannel. We give a slight buffer of RTT_ESTIMATE // so as not to excessively retry the backchannel this.channelDebug_.debug('Server claims our backchannel is missing.'); if (this.backChannelTimerId_) { this.channelDebug_.debug('But we are currently starting the request.'); return; } else if (!this.backChannelRequest_) { this.channelDebug_.warning('We do not have a BackChannel established'); } else if ( this.backChannelRequest_.getRequestStartTime() + goog.net.BrowserChannel.RTT_ESTIMATE < this.forwardChannelRequest_.getRequestStartTime()) { this.clearDeadBackchannelTimer_(); this.backChannelRequest_.cancel(); this.backChannelRequest_ = null; } else { return; } this.maybeRetryBackChannel_(); goog.net.BrowserChannel.notifyStatEvent( goog.net.BrowserChannel.Stat.BACKCHANNEL_MISSING); }; /** * Determines whether we should start the process of retrying a possibly * dead backchannel. * @param {number} outstandingBytes The number of bytes for which the server has * not yet received acknowledgement. * @return {boolean} Whether to start the backchannel retry timer. * @private */ goog.net.BrowserChannel.prototype.shouldRetryBackChannel_ = function( outstandingBytes) { // Not too many outstanding bytes, not buffered and not after a retry. return outstandingBytes < goog.net.BrowserChannel.OUTSTANDING_DATA_BACKCHANNEL_RETRY_CUTOFF && !this.isBuffered() && this.backChannelRetryCount_ == 0; }; /** * Decides which host prefix should be used, if any. If there is a handler, * allows the handler to validate a host prefix provided by the server, and * optionally override it. * @param {?string} serverHostPrefix The host prefix provided by the server. * @return {?string} The host prefix to actually use, if any. Will return null * if the use of host prefixes was disabled via setAllowHostPrefix(). */ goog.net.BrowserChannel.prototype.correctHostPrefix = function( serverHostPrefix) { if (this.allowHostPrefix_) { if (this.handler_) { return this.handler_.correctHostPrefix(serverHostPrefix); } return serverHostPrefix; } return null; }; /** * Handles the timer that indicates that our backchannel is no longer able to * successfully receive data from the server. * @private */ goog.net.BrowserChannel.prototype.onBackChannelDead_ = function() { if (goog.isDefAndNotNull(this.deadBackChannelTimerId_)) { this.deadBackChannelTimerId_ = null; this.backChannelRequest_.cancel(); this.backChannelRequest_ = null; this.maybeRetryBackChannel_(); goog.net.BrowserChannel.notifyStatEvent( goog.net.BrowserChannel.Stat.BACKCHANNEL_DEAD); } }; /** * Clears the timer that indicates that our backchannel is no longer able to * successfully receive data from the server. * @private */ goog.net.BrowserChannel.prototype.clearDeadBackchannelTimer_ = function() { if (goog.isDefAndNotNull(this.deadBackChannelTimerId_)) { goog.global.clearTimeout(this.deadBackChannelTimerId_); this.deadBackChannelTimerId_ = null; } }; /** * Returns whether or not the given error/status combination is fatal or not. * On fatal errors we immediately close the session rather than retrying the * failed request. * @param {goog.net.ChannelRequest.Error?} error The error code for the failed * request. * @param {number} statusCode The last HTTP status code. * @return {boolean} Whether or not the error is fatal. * @private */ goog.net.BrowserChannel.isFatalError_ = function(error, statusCode) { return error == goog.net.ChannelRequest.Error.UNKNOWN_SESSION_ID || error == goog.net.ChannelRequest.Error.ACTIVE_X_BLOCKED || (error == goog.net.ChannelRequest.Error.STATUS && statusCode > 0); }; /** * Callback from ChannelRequest that indicates a request has completed. * @param {goog.net.ChannelRequest} request The request object. */ goog.net.BrowserChannel.prototype.onRequestComplete = function(request) { this.channelDebug_.debug('Request complete'); var type; if (this.backChannelRequest_ == request) { this.clearDeadBackchannelTimer_(); this.backChannelRequest_ = null; type = goog.net.BrowserChannel.ChannelType_.BACK_CHANNEL; } else if (this.forwardChannelRequest_ == request) { this.forwardChannelRequest_ = null; type = goog.net.BrowserChannel.ChannelType_.FORWARD_CHANNEL; } else { // return if it was an old request from a previous session return; } this.lastStatusCode_ = request.getLastStatusCode(); if (this.state_ == goog.net.BrowserChannel.State.CLOSED) { return; } if (request.getSuccess()) { // Yay! if (type == goog.net.BrowserChannel.ChannelType_.FORWARD_CHANNEL) { var size = request.getPostData() ? request.getPostData().length : 0; goog.net.BrowserChannel.notifyTimingEvent( size, goog.now() - request.getRequestStartTime(), this.forwardChannelRetryCount_); this.ensureForwardChannel_(); this.onSuccess_(); this.pendingMaps_.length = 0; } else { // i.e., back-channel this.ensureBackChannel_(); } return; } // Else unsuccessful. Fall through. var lastError = request.getLastError(); if (!goog.net.BrowserChannel.isFatalError_(lastError, this.lastStatusCode_)) { // Maybe retry. this.channelDebug_.debug( 'Maybe retrying, last error: ' + goog.net.ChannelRequest.errorStringFromCode( /** @type {goog.net.ChannelRequest.Error} */ (lastError), this.lastStatusCode_)); if (type == goog.net.BrowserChannel.ChannelType_.FORWARD_CHANNEL) { if (this.maybeRetryForwardChannel_(request)) { return; } } if (type == goog.net.BrowserChannel.ChannelType_.BACK_CHANNEL) { if (this.maybeRetryBackChannel_()) { return; } } // Else exceeded max retries. Fall through. this.channelDebug_.debug('Exceeded max number of retries'); } else { // Else fatal error. Fall through and mark the pending maps as failed. this.channelDebug_.debug('Not retrying due to error type'); } // Can't save this session. :( this.channelDebug_.debug('Error: HTTP request failed'); switch (lastError) { case goog.net.ChannelRequest.Error.NO_DATA: this.signalError_(goog.net.BrowserChannel.Error.NO_DATA); break; case goog.net.ChannelRequest.Error.BAD_DATA: this.signalError_(goog.net.BrowserChannel.Error.BAD_DATA); break; case goog.net.ChannelRequest.Error.UNKNOWN_SESSION_ID: this.signalError_(goog.net.BrowserChannel.Error.UNKNOWN_SESSION_ID); break; case goog.net.ChannelRequest.Error.ACTIVE_X_BLOCKED: this.signalError_(goog.net.BrowserChannel.Error.ACTIVE_X_BLOCKED); break; default: this.signalError_(goog.net.BrowserChannel.Error.REQUEST_FAILED); break; } }; /** * @param {number} retryCount Number of retries so far. * @return {number} Time in ms before firing next retry request. * @private */ goog.net.BrowserChannel.prototype.getRetryTime_ = function(retryCount) { var retryTime = this.baseRetryDelayMs_ + Math.floor(Math.random() * this.retryDelaySeedMs_); if (!this.isActive()) { this.channelDebug_.debug('Inactive channel'); retryTime = retryTime * goog.net.BrowserChannel.INACTIVE_CHANNEL_RETRY_FACTOR; } // Backoff for subsequent retries retryTime = retryTime * retryCount; return retryTime; }; /** * @param {number} baseDelayMs The base part of the retry delay, in ms. * @param {number} delaySeedMs A random delay between 0 and this is added to * the base part. */ goog.net.BrowserChannel.prototype.setRetryDelay = function( baseDelayMs, delaySeedMs) { this.baseRetryDelayMs_ = baseDelayMs; this.retryDelaySeedMs_ = delaySeedMs; }; /** * Processes the data returned by the server. * @param {!Array>} respArray The response array returned * by the server. * @private */ goog.net.BrowserChannel.prototype.onInput_ = function(respArray) { var batch = this.handler_ && this.handler_.channelHandleMultipleArrays ? [] : null; for (var i = 0; i < respArray.length; i++) { var nextArray = respArray[i]; this.lastArrayId_ = nextArray[0]; nextArray = nextArray[1]; if (this.state_ == goog.net.BrowserChannel.State.OPENING) { if (nextArray[0] == 'c') { this.sid_ = nextArray[1]; this.hostPrefix_ = this.correctHostPrefix(nextArray[2]); var negotiatedVersion = nextArray[3]; if (goog.isDefAndNotNull(negotiatedVersion)) { this.channelVersion_ = negotiatedVersion; } else { // Servers prior to version 7 did not send this, so assume version 6. this.channelVersion_ = 6; } this.state_ = goog.net.BrowserChannel.State.OPENED; if (this.handler_) { this.handler_.channelOpened(this); } this.backChannelUri_ = this.getBackChannelUri( this.hostPrefix_, /** @type {string} */ (this.path_)); // Open connection to receive data this.ensureBackChannel_(); } else if (nextArray[0] == 'stop') { this.signalError_(goog.net.BrowserChannel.Error.STOP); } } else if (this.state_ == goog.net.BrowserChannel.State.OPENED) { if (nextArray[0] == 'stop') { if (batch && !goog.array.isEmpty(batch)) { this.handler_.channelHandleMultipleArrays(this, batch); batch.length = 0; } this.signalError_(goog.net.BrowserChannel.Error.STOP); } else if (nextArray[0] == 'noop') { // ignore - noop to keep connection happy } else { if (batch) { batch.push(nextArray); } else if (this.handler_) { this.handler_.channelHandleArray(this, nextArray); } } // We have received useful data on the back-channel, so clear its retry // count. We do this because back-channels by design do not complete // quickly, so on a flaky connection we could have many fail to complete // fully but still deliver a lot of data before they fail. We don't want // to count such failures towards the retry limit, because we don't want // to give up on a session if we can still receive data. this.backChannelRetryCount_ = 0; } } if (batch && !goog.array.isEmpty(batch)) { this.handler_.channelHandleMultipleArrays(this, batch); } }; /** * Helper to ensure the BrowserChannel is in the expected state. * @param {...number} var_args The channel must be in one of the indicated * states. * @private */ goog.net.BrowserChannel.prototype.ensureInState_ = function(var_args) { if (!goog.array.contains(arguments, this.state_)) { throw Error('Unexpected channel state: ' + this.state_); } }; /** * Signals an error has occurred. * @param {goog.net.BrowserChannel.Error} error The error code for the failure. * @private */ goog.net.BrowserChannel.prototype.signalError_ = function(error) { this.channelDebug_.info('Error code ' + error); if (error == goog.net.BrowserChannel.Error.REQUEST_FAILED || error == goog.net.BrowserChannel.Error.BLOCKED) { // Ping google to check if it's a server error or user's network error. var imageUri = null; if (this.handler_) { imageUri = this.handler_.getNetworkTestImageUri(this); } goog.net.tmpnetwork.testGoogleCom( goog.bind(this.testGoogleComCallback_, this), imageUri); } else { goog.net.BrowserChannel.notifyStatEvent( goog.net.BrowserChannel.Stat.ERROR_OTHER); } this.onError_(error); }; /** * Callback for testGoogleCom during error handling. * @param {boolean} networkUp Whether the network is up. * @private */ goog.net.BrowserChannel.prototype.testGoogleComCallback_ = function(networkUp) { if (networkUp) { this.channelDebug_.info('Successfully pinged google.com'); goog.net.BrowserChannel.notifyStatEvent( goog.net.BrowserChannel.Stat.ERROR_OTHER); } else { this.channelDebug_.info('Failed to ping google.com'); goog.net.BrowserChannel.notifyStatEvent( goog.net.BrowserChannel.Stat.ERROR_NETWORK); // We call onError_ here instead of signalError_ because the latter just // calls notifyStatEvent, and we don't want to have another stat event. this.onError_(goog.net.BrowserChannel.Error.NETWORK); } }; /** * Called when messages have been successfully sent from the queue. * @private */ goog.net.BrowserChannel.prototype.onSuccess_ = function() { if (this.handler_) { this.handler_.channelSuccess(this, this.pendingMaps_); } }; /** * Called when we've determined the final error for a channel. It closes the * notifiers the handler of the error and closes the channel. * @param {goog.net.BrowserChannel.Error} error The error code for the failure. * @private */ goog.net.BrowserChannel.prototype.onError_ = function(error) { this.channelDebug_.debug('HttpChannel: error - ' + error); this.state_ = goog.net.BrowserChannel.State.CLOSED; if (this.handler_) { this.handler_.channelError(this, error); } this.onClose_(); this.cancelRequests_(); }; /** * Called when the channel has been closed. It notifiers the handler of the * event, and reports any pending or undelivered maps. * @private */ goog.net.BrowserChannel.prototype.onClose_ = function() { this.state_ = goog.net.BrowserChannel.State.CLOSED; this.lastStatusCode_ = -1; if (this.handler_) { if (this.pendingMaps_.length == 0 && this.outgoingMaps_.length == 0) { this.handler_.channelClosed(this); } else { this.channelDebug_.debug( 'Number of undelivered maps' + ', pending: ' + this.pendingMaps_.length + ', outgoing: ' + this.outgoingMaps_.length); var copyOfPendingMaps = goog.array.clone(this.pendingMaps_); var copyOfUndeliveredMaps = goog.array.clone(this.outgoingMaps_); this.pendingMaps_.length = 0; this.outgoingMaps_.length = 0; this.handler_.channelClosed( this, copyOfPendingMaps, copyOfUndeliveredMaps); } } }; /** * Gets the Uri used for the connection that sends data to the server. * @param {string} path The path on the host. * @return {!goog.Uri} The forward channel URI. */ goog.net.BrowserChannel.prototype.getForwardChannelUri = function(path) { var uri = this.createDataUri(null, path); this.channelDebug_.debug('GetForwardChannelUri: ' + uri); return uri; }; /** * Gets the results for the first browser channel test * @return {Array} The results. */ goog.net.BrowserChannel.prototype.getFirstTestResults = function() { return this.firstTestResults_; }; /** * Gets the results for the second browser channel test * @return {?boolean} The results. True -> buffered connection, * False -> unbuffered, null -> unknown. */ goog.net.BrowserChannel.prototype.getSecondTestResults = function() { return this.secondTestResults_; }; /** * Gets the Uri used for the connection that receives data from the server. * @param {?string} hostPrefix The host prefix. * @param {string} path The path on the host. * @return {!goog.Uri} The back channel URI. */ goog.net.BrowserChannel.prototype.getBackChannelUri = function( hostPrefix, path) { var uri = this.createDataUri( this.shouldUseSecondaryDomains() ? hostPrefix : null, path); this.channelDebug_.debug('GetBackChannelUri: ' + uri); return uri; }; /** * Creates a data Uri applying logic for secondary hostprefix, port * overrides, and versioning. * @param {?string} hostPrefix The host prefix. * @param {string} path The path on the host (may be absolute or relative). * @param {number=} opt_overridePort Optional override port. * @return {!goog.Uri} The data URI. */ goog.net.BrowserChannel.prototype.createDataUri = function( hostPrefix, path, opt_overridePort) { var uri = goog.Uri.parse(path); var uriAbsolute = (uri.getDomain() != ''); if (uriAbsolute) { if (hostPrefix) { uri.setDomain(hostPrefix + '.' + uri.getDomain()); } uri.setPort(opt_overridePort || uri.getPort()); } else { var locationPage = window.location; var hostName; if (hostPrefix) { hostName = hostPrefix + '.' + locationPage.hostname; } else { hostName = locationPage.hostname; } var port = opt_overridePort || locationPage.port; uri = goog.Uri.create(locationPage.protocol, null, hostName, port, path); } if (this.extraParams_) { goog.object.forEach(this.extraParams_, function(value, key) { uri.setParameterValue(key, value); }); } // Add the protocol version to the URI. uri.setParameterValue('VER', this.channelVersion_); // Add the reconnect parameters. this.addAdditionalParams_(uri); return uri; }; /** * Called when BC needs to create an XhrIo object. Override in a subclass if * you need to customize the behavior, for example to enable the creation of * XHR's capable of calling a secondary domain. Will also allow calling * a secondary domain if withCredentials (CORS) is enabled. * @param {?string} hostPrefix The host prefix, if we need an XhrIo object * capable of calling a secondary domain. * @return {!goog.net.XhrIo} A new XhrIo object. */ goog.net.BrowserChannel.prototype.createXhrIo = function(hostPrefix) { if (hostPrefix && !this.supportsCrossDomainXhrs_) { throw Error('Can\'t create secondary domain capable XhrIo object.'); } var xhr = new goog.net.XhrIo(); xhr.setWithCredentials(this.supportsCrossDomainXhrs_); return xhr; }; /** * Gets whether this channel is currently active. This is used to determine the * length of time to wait before retrying. This call delegates to the handler. * @return {boolean} Whether the channel is currently active. */ goog.net.BrowserChannel.prototype.isActive = function() { return !!this.handler_ && this.handler_.isActive(this); }; /** * Wrapper around SafeTimeout which calls the start and end execution hooks * with a try...finally block. * @param {Function} fn The callback function. * @param {number} ms The time in MS for the timer. * @return {number} The ID of the timer. */ goog.net.BrowserChannel.setTimeout = function(fn, ms) { if (!goog.isFunction(fn)) { throw Error('Fn must not be null and must be a function'); } return goog.global.setTimeout(function() { goog.net.BrowserChannel.onStartExecution(); try { fn(); } finally { goog.net.BrowserChannel.onEndExecution(); } }, ms); }; /** * Helper function to call the start hook */ goog.net.BrowserChannel.onStartExecution = function() { goog.net.BrowserChannel.startExecutionHook_(); }; /** * Helper function to call the end hook */ goog.net.BrowserChannel.onEndExecution = function() { goog.net.BrowserChannel.endExecutionHook_(); }; /** * Returns the singleton event target for stat events. * @return {goog.events.EventTarget} The event target for stat events. */ goog.net.BrowserChannel.getStatEventTarget = function() { return goog.net.BrowserChannel.statEventTarget_; }; /** * Notify the channel that a particular fine grained network event has occurred. * Should be considered package-private. * @param {goog.net.BrowserChannel.ServerReachability} reachabilityType The * reachability event type. */ goog.net.BrowserChannel.prototype.notifyServerReachabilityEvent = function( reachabilityType) { var target = goog.net.BrowserChannel.statEventTarget_; target.dispatchEvent( new goog.net.BrowserChannel.ServerReachabilityEvent( target, reachabilityType)); }; /** * Helper function to call the stat event callback. * @param {goog.net.BrowserChannel.Stat} stat The stat. */ goog.net.BrowserChannel.notifyStatEvent = function(stat) { var target = goog.net.BrowserChannel.statEventTarget_; target.dispatchEvent(new goog.net.BrowserChannel.StatEvent(target, stat)); }; /** * Helper function to notify listeners about POST request performance. * * @param {number} size Number of characters in the POST data. * @param {number} rtt The amount of time from POST start to response. * @param {number} retries The number of times the POST had to be retried. */ goog.net.BrowserChannel.notifyTimingEvent = function(size, rtt, retries) { var target = goog.net.BrowserChannel.statEventTarget_; target.dispatchEvent( new goog.net.BrowserChannel.TimingEvent(target, size, rtt, retries)); }; /** * Determines whether to use a secondary domain when the server gives us * a host prefix. This allows us to work around browser per-domain * connection limits. * * Currently, we use secondary domains when using Trident's ActiveXObject, * because it supports cross-domain requests out of the box. Note that in IE10 * we no longer use ActiveX since it's not supported in Metro mode and IE10 * supports XHR streaming. * * If you need to use secondary domains on other browsers and IE10, * you have two choices: * 1) If you only care about browsers that support CORS * (https://developer.mozilla.org/en-US/docs/HTTP_access_control), you * can use {@link #setSupportsCrossDomainXhrs} and set the appropriate * CORS response headers on the server. * 2) Or, override this method in a subclass, and make sure that those * browsers use some messaging mechanism that works cross-domain (e.g * iframes and window.postMessage). * * @return {boolean} Whether to use secondary domains. * @see http://code.google.com/p/closure-library/issues/detail?id=339 */ goog.net.BrowserChannel.prototype.shouldUseSecondaryDomains = function() { return this.supportsCrossDomainXhrs_ || !goog.net.ChannelRequest.supportsXhrStreaming(); }; /** * A LogSaver that can be used to accumulate all the debug logs for * BrowserChannels so they can be sent to the server when a problem is * detected. * @const */ goog.net.BrowserChannel.LogSaver = {}; /** * Buffer for accumulating the debug log * @type {goog.structs.CircularBuffer} * @private */ goog.net.BrowserChannel.LogSaver.buffer_ = new goog.structs.CircularBuffer(1000); /** * Whether we're currently accumulating the debug log. * @type {boolean} * @private */ goog.net.BrowserChannel.LogSaver.enabled_ = false; /** * Formatter for saving logs. * @type {goog.debug.Formatter} * @private */ goog.net.BrowserChannel.LogSaver.formatter_ = new goog.debug.TextFormatter(); /** * Returns whether the LogSaver is enabled. * @return {boolean} Whether saving is enabled or disabled. */ goog.net.BrowserChannel.LogSaver.isEnabled = function() { return goog.net.BrowserChannel.LogSaver.enabled_; }; /** * Enables of disables the LogSaver. * @param {boolean} enable Whether to enable or disable saving. */ goog.net.BrowserChannel.LogSaver.setEnabled = function(enable) { if (enable == goog.net.BrowserChannel.LogSaver.enabled_) { return; } var fn = goog.net.BrowserChannel.LogSaver.addLogRecord; var logger = goog.log.getLogger('goog.net'); if (enable) { goog.log.addHandler(logger, fn); } else { goog.log.removeHandler(logger, fn); } }; /** * Adds a log record. * @param {goog.log.LogRecord} logRecord the LogRecord. */ goog.net.BrowserChannel.LogSaver.addLogRecord = function(logRecord) { goog.net.BrowserChannel.LogSaver.buffer_.add( goog.net.BrowserChannel.LogSaver.formatter_.formatRecord(logRecord)); }; /** * Returns the log as a single string. * @return {string} The log as a single string. */ goog.net.BrowserChannel.LogSaver.getBuffer = function() { return goog.net.BrowserChannel.LogSaver.buffer_.getValues().join(''); }; /** * Clears the buffer */ goog.net.BrowserChannel.LogSaver.clearBuffer = function() { goog.net.BrowserChannel.LogSaver.buffer_.clear(); }; /** * Abstract base class for the browser channel handler * @constructor */ goog.net.BrowserChannel.Handler = function() {}; /** * Callback handler for when a batch of response arrays is received from the * server. * @type {?function(!goog.net.BrowserChannel, !Array>)} */ goog.net.BrowserChannel.Handler.prototype.channelHandleMultipleArrays = null; /** * Whether it's okay to make a request to the server. A handler can return * false if the channel should fail. For example, if the user has logged out, * the handler may want all requests to fail immediately. * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @return {goog.net.BrowserChannel.Error} An error code. The code should * return goog.net.BrowserChannel.Error.OK to indicate it's okay. Any other * error code will cause a failure. */ goog.net.BrowserChannel.Handler.prototype.okToMakeRequest = function( browserChannel) { return goog.net.BrowserChannel.Error.OK; }; /** * Indicates the BrowserChannel has successfully negotiated with the server * and can now send and receive data. * @param {goog.net.BrowserChannel} browserChannel The browser channel. */ goog.net.BrowserChannel.Handler.prototype.channelOpened = function( browserChannel) {}; /** * New input is available for the application to process. * * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @param {Array} array The data array. */ goog.net.BrowserChannel.Handler.prototype.channelHandleArray = function( browserChannel, array) {}; /** * Indicates maps were successfully sent on the BrowserChannel. * * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @param {Array} deliveredMaps The * array of maps that have been delivered to the server. This is a direct * reference to the internal BrowserChannel array, so a copy should be made * if the caller desires a reference to the data. */ goog.net.BrowserChannel.Handler.prototype.channelSuccess = function( browserChannel, deliveredMaps) {}; /** * Indicates an error occurred on the BrowserChannel. * * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @param {goog.net.BrowserChannel.Error} error The error code. */ goog.net.BrowserChannel.Handler.prototype.channelError = function( browserChannel, error) {}; /** * Indicates the BrowserChannel is closed. Also notifies about which maps, * if any, that may not have been delivered to the server. * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @param {Array=} opt_pendingMaps The * array of pending maps, which may or may not have been delivered to the * server. * @param {Array=} opt_undeliveredMaps * The array of undelivered maps, which have definitely not been delivered * to the server. */ goog.net.BrowserChannel.Handler.prototype.channelClosed = function( browserChannel, opt_pendingMaps, opt_undeliveredMaps) {}; /** * Gets any parameters that should be added at the time another connection is * made to the server. * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @return {!Object} Extra parameter keys and values to add to the * requests. */ goog.net.BrowserChannel.Handler.prototype.getAdditionalParams = function( browserChannel) { return {}; }; /** * Gets the URI of an image that can be used to test network connectivity. * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @return {goog.Uri?} A custom URI to load for the network test. */ goog.net.BrowserChannel.Handler.prototype.getNetworkTestImageUri = function( browserChannel) { return null; }; /** * Gets whether this channel is currently active. This is used to determine the * length of time to wait before retrying. * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @return {boolean} Whether the channel is currently active. */ goog.net.BrowserChannel.Handler.prototype.isActive = function(browserChannel) { return true; }; /** * Called by the channel if enumeration of the map throws an exception. * @param {goog.net.BrowserChannel} browserChannel The browser channel. * @param {Object} map The map that can't be enumerated. */ goog.net.BrowserChannel.Handler.prototype.badMapError = function( browserChannel, map) { return; }; /** * Allows the handler to override a host prefix provided by the server. Will * be called whenever the channel has received such a prefix and is considering * its use. * @param {?string} serverHostPrefix The host prefix provided by the server. * @return {?string} The host prefix the client should use. */ goog.net.BrowserChannel.Handler.prototype.correctHostPrefix = function( serverHostPrefix) { return serverHostPrefix; };