Update wmts-hidpi, add nicer-api-docs

This commit is contained in:
Andreas Hocevar
2014-05-06 13:02:46 -05:00
parent b3ac1afd00
commit 1e25fc5585
2239 changed files with 3726515 additions and 37010 deletions

View File

@@ -0,0 +1,639 @@
// 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 Base TestChannel implementation.
*
*/
goog.provide('goog.labs.net.webChannel.BaseTestChannel');
goog.require('goog.json.EvalJsonProcessor');
goog.require('goog.labs.net.webChannel.Channel');
goog.require('goog.labs.net.webChannel.WebChannelRequest');
goog.require('goog.labs.net.webChannel.requestStats');
goog.require('goog.labs.net.webChannel.requestStats.ServerReachability');
goog.require('goog.labs.net.webChannel.requestStats.Stat');
goog.require('goog.net.tmpnetwork');
/**
* A TestChannel is used during the first part of channel negotiation
* with the server to create the channel. It helps us determine whether we're
* behind a buffering proxy. It also runs the logic to see if the channel
* has been blocked by a network administrator.
*
* @constructor
* @param {!goog.labs.net.webChannel.Channel} channel The channel
* that owns this test channel.
* @param {!goog.labs.net.webChannel.WebChannelDebug} channelDebug A
* WebChannelDebug instance to use for logging.
* @implements {goog.labs.net.webChannel.Channel}
*/
goog.labs.net.webChannel.BaseTestChannel = function(channel, channelDebug) {
/**
* The channel that owns this test channel
* @type {!goog.labs.net.webChannel.Channel}
* @private
*/
this.channel_ = channel;
/**
* The channel debug to use for logging
* @type {!goog.labs.net.webChannel.WebChannelDebug}
* @private
*/
this.channelDebug_ = channelDebug;
/**
* Parser for a response payload. Defaults to use
* {@code goog.json.unsafeParse}. The parser should return an array.
* @type {goog.string.Parser}
* @private
*/
this.parser_ = new goog.json.EvalJsonProcessor(null, true);
};
goog.scope(function() {
var BaseTestChannel = goog.labs.net.webChannel.BaseTestChannel;
var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;
var WebChannelRequest = goog.labs.net.webChannel.WebChannelRequest;
var requestStats = goog.labs.net.webChannel.requestStats;
var Channel = goog.labs.net.webChannel.Channel;
/**
* Extra HTTP headers to add to all the requests sent to the server.
* @type {Object}
* @private
*/
BaseTestChannel.prototype.extraHeaders_ = null;
/**
* The test request.
* @type {WebChannelRequest}
* @private
*/
BaseTestChannel.prototype.request_ = null;
/**
* Whether we have received the first result as an intermediate result. This
* helps us determine whether we're behind a buffering proxy.
* @type {boolean}
* @private
*/
BaseTestChannel.prototype.receivedIntermediateResult_ = false;
/**
* The time when the test request was started. We use timing in IE as
* a heuristic for whether we're behind a buffering proxy.
* @type {?number}
* @private
*/
BaseTestChannel.prototype.startTime_ = null;
/**
* The time for of the first result part. We use timing in IE as a
* heuristic for whether we're behind a buffering proxy.
* @type {?number}
* @private
*/
BaseTestChannel.prototype.firstTime_ = null;
/**
* The time for of the last result part. We use timing in IE as a
* heuristic for whether we're behind a buffering proxy.
* @type {?number}
* @private
*/
BaseTestChannel.prototype.lastTime_ = null;
/**
* The relative path for test requests.
* @type {?string}
* @private
*/
BaseTestChannel.prototype.path_ = null;
/**
* The state of the state machine for this object.
*
* @type {?number}
* @private
*/
BaseTestChannel.prototype.state_ = null;
/**
* The last status code received.
* @type {number}
* @private
*/
BaseTestChannel.prototype.lastStatusCode_ = -1;
/**
* A subdomain prefix for using a subdomain in IE for the backchannel
* requests.
* @type {?string}
* @private
*/
BaseTestChannel.prototype.hostPrefix_ = null;
/**
* A subdomain prefix for testing whether the channel was disabled by
* a network administrator;
* @type {?string}
* @private
*/
BaseTestChannel.prototype.blockedPrefix_ = null;
/**
* Enum type for the test channel state machine
* @enum {number}
* @private
*/
BaseTestChannel.State_ = {
/**
* The state for the TestChannel state machine where we making the
* initial call to get the server configured parameters.
*/
INIT: 0,
/**
* The state for the TestChannel state machine where we're checking to
* see if the channel has been blocked.
*/
CHECKING_BLOCKED: 1,
/**
* The state for the TestChannel state machine where we're checking to
* se if we're behind a buffering proxy.
*/
CONNECTION_TESTING: 2
};
/**
* Time in MS for waiting for the request to see if the channel is blocked.
* If the response takes longer than this many ms, we assume the request has
* failed.
* @type {number}
* @private
*/
BaseTestChannel.BLOCKED_TIMEOUT_ = 5000;
/**
* Number of attempts to try to see if the check to see if we're blocked
* succeeds. Sometimes the request can fail because of flaky network conditions
* and checking multiple times reduces false positives.
* @type {number}
* @private
*/
BaseTestChannel.BLOCKED_RETRIES_ = 3;
/**
* Time in ms between retries of the blocked request
* @type {number}
* @private
*/
BaseTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_ = 2000;
/**
* Time between chunks in the test connection that indicates that we
* are not behind a buffering proxy. This value should be less than or
* equals to the time between chunks sent from the server.
* @type {number}
* @private
*/
BaseTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_ = 500;
/**
* Sets extra HTTP headers to add to all the requests sent to the server.
*
* @param {Object} extraHeaders The HTTP headers.
*/
BaseTestChannel.prototype.setExtraHeaders = function(extraHeaders) {
this.extraHeaders_ = extraHeaders;
};
/**
* Sets a new parser for the response payload. A custom parser may be set to
* avoid using eval(), for example.
* By default, the parser uses {@code goog.json.unsafeParse}.
* @param {!goog.string.Parser} parser Parser.
*/
BaseTestChannel.prototype.setParser = function(parser) {
this.parser_ = parser;
};
/**
* Starts the test channel. This initiates connections to the server.
*
* @param {string} path The relative uri for the test connection.
*/
BaseTestChannel.prototype.connect = function(path) {
this.path_ = path;
var sendDataUri = this.channel_.getForwardChannelUri(this.path_);
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_START);
this.startTime_ = goog.now();
// If the channel already has the result of the first test, then skip it.
var firstTestResults = this.channel_.getFirstTestResults();
if (goog.isDefAndNotNull(firstTestResults)) {
this.hostPrefix_ = this.channel_.correctHostPrefix(firstTestResults[0]);
this.blockedPrefix_ = firstTestResults[1];
if (this.blockedPrefix_) {
this.state_ = BaseTestChannel.State_.CHECKING_BLOCKED;
this.checkBlocked_();
} else {
this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
this.connectStage2_();
}
return;
}
// the first request returns server specific parameters
sendDataUri.setParameterValues('MODE', 'init');
this.request_ = WebChannelRequest.createChannelRequest(
this, this.channelDebug_);
this.request_.setExtraHeaders(this.extraHeaders_);
this.request_.xmlHttpGet(sendDataUri, false /* decodeChunks */,
null /* hostPrefix */, true /* opt_noClose */);
this.state_ = BaseTestChannel.State_.INIT;
};
/**
* Checks to see whether the channel is blocked. This is for implementing the
* feature that allows network administrators to block Gmail Chat. The
* strategy to determine if we're blocked is to try to load an image off a
* special subdomain that network administrators will block access to if they
* are trying to block chat. For Gmail Chat, the subdomain is
* chatenabled.mail.google.com.
* @private
*/
BaseTestChannel.prototype.checkBlocked_ = function() {
var uri = this.channel_.createDataUri(this.blockedPrefix_,
'/mail/images/cleardot.gif');
uri.makeUnique();
goog.net.tmpnetwork.testLoadImageWithRetries(uri.toString(),
BaseTestChannel.BLOCKED_TIMEOUT_,
goog.bind(this.checkBlockedCallback_, this),
BaseTestChannel.BLOCKED_RETRIES_,
BaseTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_);
requestStats.notifyServerReachabilityEvent(
requestStats.ServerReachability.REQUEST_MADE);
};
/**
* Callback for testLoadImageWithRetries to check if a channel is blocked.
* @param {boolean} succeeded Whether the request succeeded.
* @private
*/
BaseTestChannel.prototype.checkBlockedCallback_ = function(
succeeded) {
if (succeeded) {
this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
this.connectStage2_();
} else {
requestStats.notifyStatEvent(requestStats.Stat.CHANNEL_BLOCKED);
this.channel_.testConnectionBlocked(this);
}
// We don't dispatch a REQUEST_FAILED server reachability event when the
// block request fails, as such a failure is not a good signal that the
// server has actually become unreachable.
if (succeeded) {
requestStats.notifyServerReachabilityEvent(
requestStats.ServerReachability.REQUEST_SUCCEEDED);
}
};
/**
* Begins the second stage of the test channel where we test to see if we're
* behind a buffering proxy. The server sends back a multi-chunked response
* with the first chunk containing the content '1' and then two seconds later
* sending the second chunk containing the content '2'. Depending on how we
* receive the content, we can tell if we're behind a buffering proxy.
* @private
*/
BaseTestChannel.prototype.connectStage2_ = function() {
this.channelDebug_.debug('TestConnection: starting stage 2');
// If the second test results are available, skip its execution.
var secondTestResults = this.channel_.getSecondTestResults();
if (goog.isDefAndNotNull(secondTestResults)) {
this.channelDebug_.debug(
'TestConnection: skipping stage 2, precomputed result is ' +
secondTestResults ? 'Buffered' : 'Unbuffered');
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
if (secondTestResults) { // Buffered/Proxy connection
requestStats.notifyStatEvent(requestStats.Stat.PROXY);
this.channel_.testConnectionFinished(this, false);
} else { // Unbuffered/NoProxy connection
requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
this.channel_.testConnectionFinished(this, true);
}
return; // Skip the test
}
this.request_ = WebChannelRequest.createChannelRequest(
this, this.channelDebug_);
this.request_.setExtraHeaders(this.extraHeaders_);
var recvDataUri = this.channel_.getBackChannelUri(this.hostPrefix_,
/** @type {string} */ (this.path_));
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
if (!WebChannelRequest.supportsXhrStreaming()) {
recvDataUri.setParameterValues('TYPE', 'html');
this.request_.tridentGet(recvDataUri, Boolean(this.hostPrefix_));
} else {
recvDataUri.setParameterValues('TYPE', 'xmlhttp');
this.request_.xmlHttpGet(recvDataUri, false /** decodeChunks */,
this.hostPrefix_, false /** opt_noClose */);
}
};
/**
* @override
*/
BaseTestChannel.prototype.createXhrIo = function(hostPrefix) {
return this.channel_.createXhrIo(hostPrefix);
};
/**
* Aborts the test channel.
*/
BaseTestChannel.prototype.abort = function() {
if (this.request_) {
this.request_.cancel();
this.request_ = null;
}
this.lastStatusCode_ = -1;
};
/**
* Returns whether the test channel is closed. The ChannelRequest object expects
* this method to be implemented on its handler.
*
* @return {boolean} Whether the channel is closed.
* @override
*/
BaseTestChannel.prototype.isClosed = function() {
return false;
};
/**
* Callback from ChannelRequest for when new data is received
*
* @param {WebChannelRequest} req The request object.
* @param {string} responseText The text of the response.
* @override
*/
BaseTestChannel.prototype.onRequestData = function(req, responseText) {
this.lastStatusCode_ = req.getLastStatusCode();
if (this.state_ == BaseTestChannel.State_.INIT) {
this.channelDebug_.debug('TestConnection: Got data for stage 1');
if (!responseText) {
this.channelDebug_.debug('TestConnection: Null responseText');
// The server should always send text; something is wrong here
this.channel_.testConnectionFailure(this,
WebChannelRequest.Error.BAD_DATA);
return;
}
/** @preserveTry */
try {
var respArray = this.parser_.parse(responseText);
} catch (e) {
this.channelDebug_.dumpException(e);
this.channel_.testConnectionFailure(this,
WebChannelRequest.Error.BAD_DATA);
return;
}
this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);
this.blockedPrefix_ = respArray[1];
} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
if (this.receivedIntermediateResult_) {
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_TWO);
this.lastTime_ = goog.now();
} else {
// '11111' is used instead of '1' to prevent a small amount of buffering
// by Safari.
if (responseText == '11111') {
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_ONE);
this.receivedIntermediateResult_ = true;
this.firstTime_ = goog.now();
if (this.checkForEarlyNonBuffered_()) {
// If early chunk detection is on, and we passed the tests,
// assume HTTP_OK, cancel the test and turn on noproxy mode.
this.lastStatusCode_ = 200;
this.request_.cancel();
this.channelDebug_.debug(
'Test connection succeeded; using streaming connection');
requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
this.channel_.testConnectionFinished(this, true);
}
} else {
requestStats.notifyStatEvent(
requestStats.Stat.TEST_STAGE_TWO_DATA_BOTH);
this.firstTime_ = this.lastTime_ = goog.now();
this.receivedIntermediateResult_ = false;
}
}
}
};
/**
* Callback from ChannelRequest that indicates a request has completed.
*
* @param {WebChannelRequest} req The request object.
* @override
*/
BaseTestChannel.prototype.onRequestComplete = function(req) {
this.lastStatusCode_ = this.request_.getLastStatusCode();
if (!this.request_.getSuccess()) {
this.channelDebug_.debug(
'TestConnection: request failed, in state ' + this.state_);
if (this.state_ == BaseTestChannel.State_.INIT) {
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_FAILED);
} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_FAILED);
}
this.channel_.testConnectionFailure(this,
/** @type {WebChannelRequest.Error} */
(this.request_.getLastError()));
return;
}
if (this.state_ == BaseTestChannel.State_.INIT) {
this.channelDebug_.debug(
'TestConnection: request complete for initial check');
if (this.blockedPrefix_) {
this.state_ = BaseTestChannel.State_.CHECKING_BLOCKED;
this.checkBlocked_();
} else {
this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
this.connectStage2_();
}
} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
this.channelDebug_.debug('TestConnection: request complete for stage 2');
var goodConn = false;
if (!WebChannelRequest.supportsXhrStreaming()) {
// we always get Trident responses in separate calls to
// onRequestData, so we have to check the time they came
var ms = this.lastTime_ - this.firstTime_;
if (ms < 200) {
// TODO: need to empirically verify that this number is OK
// for slow computers
goodConn = false;
} else {
goodConn = true;
}
} else {
goodConn = this.receivedIntermediateResult_;
}
if (goodConn) {
this.channelDebug_.debug(
'Test connection succeeded; using streaming connection');
requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
this.channel_.testConnectionFinished(this, true);
} else {
this.channelDebug_.debug(
'Test connection failed; not using streaming');
requestStats.notifyStatEvent(requestStats.Stat.PROXY);
this.channel_.testConnectionFinished(this, false);
}
}
};
/**
* Returns the last status code received for a request.
* @return {number} The last status code received for a request.
*/
BaseTestChannel.prototype.getLastStatusCode = function() {
return this.lastStatusCode_;
};
/**
* @return {boolean} Whether we should be using secondary domains when the
* server instructs us to do so.
* @override
*/
BaseTestChannel.prototype.shouldUseSecondaryDomains = function() {
return this.channel_.shouldUseSecondaryDomains();
};
/**
* @override
*/
BaseTestChannel.prototype.isActive = function() {
return this.channel_.isActive();
};
/**
* @return {boolean} True if test stage 2 detected a non-buffered
* channel early and early no buffering detection is enabled.
* @private
*/
BaseTestChannel.prototype.checkForEarlyNonBuffered_ = function() {
var ms = this.firstTime_ - this.startTime_;
// we always get Trident responses in separate calls to
// onRequestData, so we have to check the time that the first came in
// and verify that the data arrived before the second portion could
// have been sent. For all other browser's we skip the timing test.
return WebChannelRequest.supportsXhrStreaming() ||
ms < BaseTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_;
};
/**
* @override
*/
BaseTestChannel.prototype.getForwardChannelUri = goog.abstractMethod;
/**
* @override
*/
BaseTestChannel.prototype.getBackChannelUri = goog.abstractMethod;
/**
* @override
*/
BaseTestChannel.prototype.correctHostPrefix = goog.abstractMethod;
/**
* @override
*/
BaseTestChannel.prototype.createDataUri = goog.abstractMethod;
/**
* @override
*/
BaseTestChannel.prototype.testConnectionBlocked = goog.abstractMethod;
/**
* @override
*/
BaseTestChannel.prototype.testConnectionFinished = goog.abstractMethod;
/**
* @override
*/
BaseTestChannel.prototype.testConnectionFailure = goog.abstractMethod;
/**
* @override
*/
BaseTestChannel.prototype.getFirstTestResults = goog.abstractMethod;
}); // goog.scope

View File

@@ -0,0 +1,195 @@
// Copyright 2013 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 A shared interface for WebChannelBase and BaseTestChannel.
*
* @visibility {//visibility:private}
*/
goog.provide('goog.labs.net.webChannel.Channel');
/**
* Shared interface between Channel and TestChannel to support callbacks
* between WebChannelBase and BaseTestChannel and between Channel and
* ChannelRequest.
*
* @interface
*/
goog.labs.net.webChannel.Channel = function() {};
goog.scope(function() {
var Channel = goog.labs.net.webChannel.Channel;
/**
* 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
*/
Channel.prototype.shouldUseSecondaryDomains = goog.abstractMethod;
/**
* Called when creating 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.
*/
Channel.prototype.createXhrIo = goog.abstractMethod;
/**
* Callback from ChannelRequest that indicates a request has completed.
* @param {goog.labs.net.webChannel.WebChannelRequest} request
* The request object.
*/
Channel.prototype.onRequestComplete = goog.abstractMethod;
/**
* Returns whether the channel is closed
* @return {boolean} true if the channel is closed.
*/
Channel.prototype.isClosed = goog.abstractMethod;
/**
* Callback from ChannelRequest for when new data is received
* @param {goog.labs.net.webChannel.WebChannelRequest} request
* The request object.
* @param {string} responseText The text of the response.
*/
Channel.prototype.onRequestData = goog.abstractMethod;
/**
* 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.
*/
Channel.prototype.isActive = goog.abstractMethod;
/**
* Not needed for testchannel.
*
* 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.
*/
Channel.prototype.getForwardChannelUri = goog.abstractMethod;
/**
* Not needed for testchannel.
*
* 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.
*/
Channel.prototype.getBackChannelUri = goog.abstractMethod;
/**
* Not needed for testchannel.
*
* 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.
*/
Channel.prototype.correctHostPrefix = goog.abstractMethod;
/**
* Not needed for testchannel.
*
* 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.
*/
Channel.prototype.createDataUri = goog.abstractMethod;
/**
* Not needed for testchannel.
*
* Callback from TestChannel for when the channel is blocked.
* @param {goog.labs.net.webChannel.BaseTestChannel} testChannel
* The TestChannel.
*/
Channel.prototype.testConnectionBlocked = goog.abstractMethod;
/**
* Not needed for testchannel.
*
* Callback from TestChannel for when the channel is finished.
* @param {goog.labs.net.webChannel.BaseTestChannel} testChannel
* The TestChannel.
* @param {boolean} useChunked Whether we can chunk responses.
*/
Channel.prototype.testConnectionFinished = goog.abstractMethod;
/**
* Not needed for testchannel.
*
* Callback from TestChannel for when the channel has an error.
* @param {goog.labs.net.webChannel.BaseTestChannel} testChannel
* The TestChannel.
* @param {goog.labs.net.webChannel.WebChannelRequest.Error} errorCode
* The error code of the failure.
*/
Channel.prototype.testConnectionFailure = goog.abstractMethod;
/**
* Not needed for testchannel.
* Gets the results for the first channel test
* @return {Array.<string>} The results.
*/
Channel.prototype.getFirstTestResults = goog.abstractMethod;
}); // goog.scope

View File

@@ -0,0 +1,391 @@
// Copyright 2013 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 Static utilities for collecting stats associated with
* WebChannelRequest.
*
* @visibility {//visibility:private}
*/
goog.provide('goog.labs.net.webChannel.requestStats');
goog.provide('goog.labs.net.webChannel.requestStats.Event');
goog.provide('goog.labs.net.webChannel.requestStats.ServerReachability');
goog.provide('goog.labs.net.webChannel.requestStats.ServerReachabilityEvent');
goog.provide('goog.labs.net.webChannel.requestStats.Stat');
goog.provide('goog.labs.net.webChannel.requestStats.StatEvent');
goog.provide('goog.labs.net.webChannel.requestStats.TimingEvent');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
goog.scope(function() {
var requestStats = goog.labs.net.webChannel.requestStats;
/**
* Events fired.
* @type {Object}
*/
requestStats.Event = {};
/**
* Singleton event target for firing stat events
* @type {goog.events.EventTarget}
* @private
*/
requestStats.statEventTarget_ = new goog.events.EventTarget();
/**
* The type of event that occurs every time some information about how reachable
* the server is is discovered.
*/
requestStats.Event.SERVER_REACHABILITY_EVENT = 'serverreachability';
/**
* Types of events which reveal information about the reachability of the
* server.
* @enum {number}
*/
requestStats.ServerReachability = {
REQUEST_MADE: 1,
REQUEST_SUCCEEDED: 2,
REQUEST_FAILED: 3,
BACK_CHANNEL_ACTIVITY: 4
};
/**
* Event class for SERVER_REACHABILITY_EVENT.
*
* @param {goog.events.EventTarget} target The stat event target for
the channel.
* @param {requestStats.ServerReachability} reachabilityType
* The reachability event type.
* @constructor
* @extends {goog.events.Event}
*/
requestStats.ServerReachabilityEvent = function(target, reachabilityType) {
goog.events.Event.call(this,
requestStats.Event.SERVER_REACHABILITY_EVENT, target);
/**
* @type {requestStats.ServerReachability}
*/
this.reachabilityType = reachabilityType;
};
goog.inherits(requestStats.ServerReachabilityEvent, goog.events.Event);
/**
* Notify the channel that a particular fine grained network event has occurred.
* Should be considered package-private.
* @param {requestStats.ServerReachability} reachabilityType
* The reachability event type.
*/
requestStats.notifyServerReachabilityEvent = function(reachabilityType) {
var target = requestStats.statEventTarget_;
target.dispatchEvent(
new requestStats.ServerReachabilityEvent(target, reachabilityType));
};
/**
* Stat Event that fires when things of interest happen that may be useful for
* applications to know about for stats or debugging purposes.
*/
requestStats.Event.STAT_EVENT = 'statevent';
/**
* Enum that identifies events for statistics that are interesting to track.
* TODO(user) - Change name not to use Event or use EventTarget
* @enum {number}
*/
requestStats.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
* recieved 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
};
/**
* Event class for STAT_EVENT.
*
* @param {goog.events.EventTarget} eventTarget The stat event target for
the channel.
* @param {requestStats.Stat} stat The stat.
* @constructor
* @extends {goog.events.Event}
*/
requestStats.StatEvent = function(eventTarget, stat) {
goog.events.Event.call(this, requestStats.Event.STAT_EVENT, eventTarget);
/**
* The stat
* @type {requestStats.Stat}
*/
this.stat = stat;
};
goog.inherits(requestStats.StatEvent, goog.events.Event);
/**
* Returns the singleton event target for stat events.
* @return {goog.events.EventTarget} The event target for stat events.
*/
requestStats.getStatEventTarget = function() {
return requestStats.statEventTarget_;
};
/**
* Helper function to call the stat event callback.
* @param {requestStats.Stat} stat The stat.
*/
requestStats.notifyStatEvent = function(stat) {
var target = requestStats.statEventTarget_;
target.dispatchEvent(new requestStats.StatEvent(target, stat));
};
/**
* An event that fires when POST requests complete successfully, indicating
* the size of the POST and the round trip time.
*/
requestStats.Event.TIMING_EVENT = 'timingevent';
/**
* Event class for requestStats.Event.TIMING_EVENT
*
* @param {goog.events.EventTarget} target The stat event target for
the 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}
*/
requestStats.TimingEvent = function(target, size, rtt, retries) {
goog.events.Event.call(this,
requestStats.Event.TIMING_EVENT, target);
/**
* @type {number}
*/
this.size = size;
/**
* @type {number}
*/
this.rtt = rtt;
/**
* @type {number}
*/
this.retries = retries;
};
goog.inherits(requestStats.TimingEvent, goog.events.Event);
/**
* 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.
*/
requestStats.notifyTimingEvent = function(size, rtt, retries) {
var target = requestStats.statEventTarget_;
target.dispatchEvent(
new requestStats.TimingEvent(
target, size, rtt, retries));
};
/**
* Allows the application to set an execution hooks for when a channel
* 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.
*/
requestStats.setStartThreadExecutionHook = function(startHook) {
requestStats.startExecutionHook_ = startHook;
};
/**
* Allows the application to set an execution hooks for when a channel
* 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.
*/
requestStats.setEndThreadExecutionHook = function(endHook) {
requestStats.endExecutionHook_ = endHook;
};
/**
* Application provided execution hook for the start hook.
*
* @type {Function}
* @private
*/
requestStats.startExecutionHook_ = function() { };
/**
* Application provided execution hook for the end hook.
*
* @type {Function}
* @private
*/
requestStats.endExecutionHook_ = function() { };
/**
* Helper function to call the start hook
*/
requestStats.onStartExecution = function() {
requestStats.startExecutionHook_();
};
/**
* Helper function to call the end hook
*/
requestStats.onEndExecution = function() {
requestStats.endExecutionHook_();
};
/**
* 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.
*/
requestStats.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() {
requestStats.onStartExecution();
try {
fn();
} finally {
requestStats.onEndExecution();
}
}, ms);
};
}); // goog.scope

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
// Copyright 2013 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 Implementation of a WebChannel transport using WebChannelBase.
*
* When WebChannelBase is used as the underlying transport, the capabilities
* of the WebChannel are limited to what's supported by the implementation.
* Particularly, multiplexing is not possible, and only strings are
* supported as message types.
*
*/
goog.provide('goog.labs.net.webChannel.WebChannelBaseTransport');
goog.require('goog.asserts');
goog.require('goog.events.EventTarget');
goog.require('goog.labs.net.webChannel.WebChannelBase');
goog.require('goog.log');
goog.require('goog.net.WebChannel');
goog.require('goog.net.WebChannelTransport');
goog.require('goog.string.path');
/**
* Implementation of {@link goog.net.WebChannelTransport} with
* {@link goog.labs.net.webChannel.WebChannelBase} as the underlying channel
* implementation.
*
* @constructor
* @implements {goog.net.WebChannelTransport}
*/
goog.labs.net.webChannel.WebChannelBaseTransport = function() {};
goog.scope(function() {
var WebChannelBaseTransport = goog.labs.net.webChannel.WebChannelBaseTransport;
var WebChannelBase = goog.labs.net.webChannel.WebChannelBase;
/**
* @override
*/
WebChannelBaseTransport.prototype.createWebChannel = function(
url, opt_options) {
return new WebChannelBaseTransport.Channel(url, opt_options);
};
/**
* Implementation of the {@link goog.net.WebChannel} interface.
*
* @param {string} url The URL path for the new WebChannel instance.
* @param {!goog.net.WebChannel.Options=} opt_options Configuration for the
* new WebChannel instance.
*
* @constructor
* @implements {goog.net.WebChannel}
* @extends {goog.events.EventTarget}
*/
WebChannelBaseTransport.Channel = function(url, opt_options) {
goog.base(this);
/**
* The underlying channel object.
*
* @type {!WebChannelBase}
* @private
*/
this.channel_ = new WebChannelBase();
/**
* The URL of the target server end-point.
*
* @type {string}
* @private
*/
this.url_ = url;
/**
* The channel options.
*
* @type {?goog.net.WebChannel.Options}
* @private
*/
this.options_ = opt_options || null;
/**
* The logger for this class.
* @type {goog.log.Logger}
* @private
*/
this.logger_ = goog.log.getLogger(
'goog.labs.net.webChannel.WebChannelBaseTransport');
};
goog.inherits(WebChannelBaseTransport.Channel, goog.events.EventTarget);
/**
* The channel handler.
*
* @type {WebChannelBase.Handler}
* @private
*/
WebChannelBaseTransport.Channel.prototype.channelHandler_ = null;
/**
* Test path is always set to "/url/test".
*
* TODO(user): The test path may be made configurable via the options.
*
* @override
*/
WebChannelBaseTransport.Channel.prototype.open = function() {
var testUrl = goog.string.path.join(this.url_, 'test');
this.channel_.connect(testUrl, this.url_);
this.channelHandler_ = new WebChannelBaseTransport.Channel.Handler_(this);
this.channel_.setHandler(this.channelHandler_);
};
/**
* @override
*/
WebChannelBaseTransport.Channel.prototype.close = function() {
this.channel_.disconnect();
};
/**
* The WebChannelBase only supports object types.
*
* @param {!goog.net.WebChannel.MessageData} message The message to send.
* @override
*/
WebChannelBaseTransport.Channel.prototype.send = function(message) {
goog.asserts.assert(goog.isObject(message), 'only object type expected');
this.channel_.sendMap(message);
};
/**
* @override
*/
WebChannelBaseTransport.Channel.prototype.disposeInternal = function() {
this.channel_.setHandler(null);
delete this.channelHandler_;
this.channel_.disconnect();
delete this.channel_;
goog.base(this, 'disposeInternal');
};
/**
* The message event.
*
* @param {!Array} array The data array from the underlying channel.
* @constructor
* @extends {goog.net.WebChannel.MessageEvent}
*/
WebChannelBaseTransport.Channel.MessageEvent = function(array) {
goog.base(this);
this.data = array;
};
goog.inherits(WebChannelBaseTransport.Channel.MessageEvent,
goog.net.WebChannel.MessageEvent);
/**
* The error event.
*
* @param {WebChannelBase.Error} error The error code.
* @constructor
* @extends {goog.net.WebChannel.ErrorEvent}
*/
WebChannelBaseTransport.Channel.ErrorEvent = function(error) {
goog.base(this);
/**
* Transport specific error code is not to be propagated with the event.
*/
this.status = goog.net.WebChannel.ErrorStatus.NETWORK_ERROR;
};
goog.inherits(WebChannelBaseTransport.Channel.ErrorEvent,
goog.net.WebChannel.ErrorEvent);
/**
* Implementation of the {@link WebChannelBase.Handler} interface.
*
* @param {!WebChannelBaseTransport.Channel} channel The enclosing WebChannel.
*
* @constructor
* @extends {WebChannelBase.Handler}
* @private
*/
WebChannelBaseTransport.Channel.Handler_ = function(channel) {
goog.base(this);
/**
* @type {!WebChannelBaseTransport.Channel}
* @private
*/
this.channel_ = channel;
};
goog.inherits(WebChannelBaseTransport.Channel.Handler_, WebChannelBase.Handler);
/**
* @override
*/
WebChannelBaseTransport.Channel.Handler_.prototype.channelOpened = function(
channel) {
goog.log.info(this.channel_.logger_,
'WebChannel opened on ' + this.channel_.url_);
this.channel_.dispatchEvent(goog.net.WebChannel.EventType.OPEN);
};
/**
* @override
*/
WebChannelBaseTransport.Channel.Handler_.prototype.channelHandleArray =
function(channel, array) {
goog.asserts.assert(array, 'array expected to be defined');
this.channel_.dispatchEvent(
new WebChannelBaseTransport.Channel.MessageEvent(array));
};
/**
* @override
*/
WebChannelBaseTransport.Channel.Handler_.prototype.channelError = function(
channel, error) {
goog.log.info(this.channel_.logger_,
'WebChannel aborted on ' + this.channel_.url_ +
' due to channel error: ' + error);
this.channel_.dispatchEvent(
new WebChannelBaseTransport.Channel.ErrorEvent(error));
};
/**
* @override
*/
WebChannelBaseTransport.Channel.Handler_.prototype.channelClosed = function(
channel, opt_pendingMaps, opt_undeliveredMaps) {
goog.log.info(this.channel_.logger_,
'WebChannel closed on ' + this.channel_.url_);
this.channel_.dispatchEvent(goog.net.WebChannel.EventType.CLOSE);
};
}); // goog.scope

View File

@@ -0,0 +1,298 @@
// 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 Provides a utility for tracing and debugging WebChannel
* requests.
*
* @visibility {//visibility:private}
*/
goog.provide('goog.labs.net.webChannel.WebChannelDebug');
goog.require('goog.json');
goog.require('goog.log');
/**
* Logs and keeps a buffer of debugging info for the Channel.
*
* @constructor
*/
goog.labs.net.webChannel.WebChannelDebug = function() {
/**
* The logger instance.
* @const
* @private
*/
this.logger_ = goog.log.getLogger('goog.labs.net.webChannel.WebChannelDebug');
};
goog.scope(function() {
var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;
/**
* Gets the logger used by this ChannelDebug.
* @return {goog.debug.Logger} The logger used by this WebChannelDebug.
*/
WebChannelDebug.prototype.getLogger = function() {
return this.logger_;
};
/**
* Logs that the browser went offline during the lifetime of a request.
* @param {goog.Uri} url The URL being requested.
*/
WebChannelDebug.prototype.browserOfflineResponse = function(url) {
this.info('BROWSER_OFFLINE: ' + url);
};
/**
* Logs an XmlHttp request..
* @param {string} verb The request type (GET/POST).
* @param {goog.Uri} uri The request destination.
* @param {string|number|undefined} id The request id.
* @param {number} attempt Which attempt # the request was.
* @param {?string} postData The data posted in the request.
*/
WebChannelDebug.prototype.xmlHttpChannelRequest =
function(verb, uri, id, attempt, postData) {
this.info(
'XMLHTTP REQ (' + id + ') [attempt ' + attempt + ']: ' +
verb + '\n' + uri + '\n' +
this.maybeRedactPostData_(postData));
};
/**
* Logs the meta data received from an XmlHttp request.
* @param {string} verb The request type (GET/POST).
* @param {goog.Uri} uri The request destination.
* @param {string|number|undefined} id The request id.
* @param {number} attempt Which attempt # the request was.
* @param {goog.net.XmlHttp.ReadyState} readyState The ready state.
* @param {number} statusCode The HTTP status code.
*/
WebChannelDebug.prototype.xmlHttpChannelResponseMetaData =
function(verb, uri, id, attempt, readyState, statusCode) {
this.info(
'XMLHTTP RESP (' + id + ') [ attempt ' + attempt + ']: ' +
verb + '\n' + uri + '\n' + readyState + ' ' + statusCode);
};
/**
* Logs the response data received from an XmlHttp request.
* @param {string|number|undefined} id The request id.
* @param {?string} responseText The response text.
* @param {?string=} opt_desc Optional request description.
*/
WebChannelDebug.prototype.xmlHttpChannelResponseText =
function(id, responseText, opt_desc) {
this.info(
'XMLHTTP TEXT (' + id + '): ' +
this.redactResponse_(responseText) +
(opt_desc ? ' ' + opt_desc : ''));
};
/**
* Logs a Trident ActiveX request.
* @param {string} verb The request type (GET/POST).
* @param {goog.Uri} uri The request destination.
* @param {string|number|undefined} id The request id.
* @param {number} attempt Which attempt # the request was.
*/
WebChannelDebug.prototype.tridentChannelRequest =
function(verb, uri, id, attempt) {
this.info(
'TRIDENT REQ (' + id + ') [ attempt ' + attempt + ']: ' +
verb + '\n' + uri);
};
/**
* Logs the response text received from a Trident ActiveX request.
* @param {string|number|undefined} id The request id.
* @param {string} responseText The response text.
*/
WebChannelDebug.prototype.tridentChannelResponseText =
function(id, responseText) {
this.info(
'TRIDENT TEXT (' + id + '): ' +
this.redactResponse_(responseText));
};
/**
* Logs the done response received from a Trident ActiveX request.
* @param {string|number|undefined} id The request id.
* @param {boolean} successful Whether the request was successful.
*/
WebChannelDebug.prototype.tridentChannelResponseDone =
function(id, successful) {
this.info(
'TRIDENT TEXT (' + id + '): ' + successful ? 'success' : 'failure');
};
/**
* Logs a request timeout.
* @param {goog.Uri} uri The uri that timed out.
*/
WebChannelDebug.prototype.timeoutResponse = function(uri) {
this.info('TIMEOUT: ' + uri);
};
/**
* Logs a debug message.
* @param {string} text The message.
*/
WebChannelDebug.prototype.debug = function(text) {
this.info(text);
};
/**
* Logs an exception
* @param {Error} e The error or error event.
* @param {string=} opt_msg The optional message, defaults to 'Exception'.
*/
WebChannelDebug.prototype.dumpException = function(e, opt_msg) {
this.severe((opt_msg || 'Exception') + e);
};
/**
* Logs an info message.
* @param {string} text The message.
*/
WebChannelDebug.prototype.info = function(text) {
goog.log.info(this.logger_, text);
};
/**
* Logs a warning message.
* @param {string} text The message.
*/
WebChannelDebug.prototype.warning = function(text) {
goog.log.warning(this.logger_, text);
};
/**
* Logs a severe message.
* @param {string} text The message.
*/
WebChannelDebug.prototype.severe = function(text) {
goog.log.error(this.logger_, text);
};
/**
* Removes potentially private data from a response so that we don't
* accidentally save private and personal data to the server logs.
* @param {?string} responseText A JSON response to clean.
* @return {?string} The cleaned response.
* @private
*/
WebChannelDebug.prototype.redactResponse_ = function(responseText) {
if (!responseText) {
return null;
}
/** @preserveTry */
try {
var responseArray = goog.json.unsafeParse(responseText);
if (responseArray) {
for (var i = 0; i < responseArray.length; i++) {
if (goog.isArray(responseArray[i])) {
this.maybeRedactArray_(responseArray[i]);
}
}
}
return goog.json.serialize(responseArray);
} catch (e) {
this.debug('Exception parsing expected JS array - probably was not JS');
return responseText;
}
};
/**
* Removes data from a response array that may be sensitive.
* @param {!Array} array The array to clean.
* @private
*/
WebChannelDebug.prototype.maybeRedactArray_ = function(array) {
if (array.length < 2) {
return;
}
var dataPart = array[1];
if (!goog.isArray(dataPart)) {
return;
}
if (dataPart.length < 1) {
return;
}
var type = dataPart[0];
if (type != 'noop' && type != 'stop') {
// redact all fields in the array
for (var i = 1; i < dataPart.length; i++) {
dataPart[i] = '';
}
}
};
/**
* Removes potentially private data from a request POST body so that we don't
* accidentally save private and personal data to the server logs.
* @param {?string} data The data string to clean.
* @return {?string} The data string with sensitive data replaced by 'redacted'.
* @private
*/
WebChannelDebug.prototype.maybeRedactPostData_ = function(data) {
if (!data) {
return null;
}
var out = '';
var params = data.split('&');
for (var i = 0; i < params.length; i++) {
var param = params[i];
var keyValue = param.split('=');
if (keyValue.length > 1) {
var key = keyValue[0];
var value = keyValue[1];
var keyParts = key.split('_');
if (keyParts.length >= 2 && keyParts[1] == 'type') {
out += key + '=' + value + '&';
} else {
out += key + '=' + 'redacted' + '&';
}
}
}
return out;
};
}); // goog.scope