Adding float-no-zero branch hosted build
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,618 @@
|
||||
// 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 BrowserTestChannel class. A
|
||||
* BrowserTestChannel 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. This class is part of the
|
||||
* BrowserChannel implementation and is not for use by normal application code.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
goog.provide('goog.net.BrowserTestChannel');
|
||||
|
||||
goog.require('goog.json.EvalJsonProcessor');
|
||||
goog.require('goog.net.ChannelRequest');
|
||||
goog.require('goog.net.ChannelRequest.Error');
|
||||
goog.require('goog.net.tmpnetwork');
|
||||
goog.require('goog.string.Parser');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulates the logic for a single BrowserTestChannel.
|
||||
*
|
||||
* @constructor
|
||||
* @param {goog.net.BrowserChannel} channel The BrowserChannel that owns this
|
||||
* test channel.
|
||||
* @param {goog.net.ChannelDebug} channelDebug A ChannelDebug to use for
|
||||
* logging.
|
||||
*/
|
||||
goog.net.BrowserTestChannel = function(channel, channelDebug) {
|
||||
/**
|
||||
* The BrowserChannel that owns this test channel
|
||||
* @type {goog.net.BrowserChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* The channel debug to use for logging
|
||||
* @type {goog.net.ChannelDebug}
|
||||
* @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);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extra HTTP headers to add to all the requests sent to the server.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.extraHeaders_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The test request.
|
||||
* @type {goog.net.ChannelRequest}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.lastTime_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The relative path for test requests.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.path_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The state of the state machine for this object.
|
||||
*
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.state_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The last status code received.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.lastStatusCode_ = -1;
|
||||
|
||||
|
||||
/**
|
||||
* A subdomain prefix for using a subdomain in IE for the backchannel
|
||||
* requests.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.hostPrefix_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A subdomain prefix for testing whether the channel was disabled by
|
||||
* a network administrator;
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.blockedPrefix_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Enum type for the browser test channel state machine
|
||||
* @enum {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.State_ = {
|
||||
/**
|
||||
* The state for the BrowserTestChannel state machine where we making the
|
||||
* initial call to get the server configured parameters.
|
||||
*/
|
||||
INIT: 0,
|
||||
|
||||
/**
|
||||
* The state for the BrowserTestChannel state machine where we're checking to
|
||||
* see if the channel has been blocked.
|
||||
*/
|
||||
CHECKING_BLOCKED: 1,
|
||||
|
||||
/**
|
||||
* The state for the BrowserTestChannel 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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.BLOCKED_RETRIES_ = 3;
|
||||
|
||||
|
||||
/**
|
||||
* Time in ms between retries of the blocked request
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.connect = function(path) {
|
||||
this.path_ = path;
|
||||
var sendDataUri = this.channel_.getForwardChannelUri(this.path_);
|
||||
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.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_ = goog.net.BrowserTestChannel.State_.CHECKING_BLOCKED;
|
||||
this.checkBlocked_();
|
||||
} else {
|
||||
this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
|
||||
this.connectStage2_();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// the first request returns server specific parameters
|
||||
sendDataUri.setParameterValues('MODE', 'init');
|
||||
this.request_ = goog.net.BrowserChannel.createChannelRequest(
|
||||
this, this.channelDebug_);
|
||||
this.request_.setExtraHeaders(this.extraHeaders_);
|
||||
this.request_.xmlHttpGet(sendDataUri, false /* decodeChunks */,
|
||||
null /* hostPrefix */, true /* opt_noClose */);
|
||||
this.state_ = goog.net.BrowserTestChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.checkBlocked_ = function() {
|
||||
var uri = this.channel_.createDataUri(this.blockedPrefix_,
|
||||
'/mail/images/cleardot.gif');
|
||||
uri.makeUnique();
|
||||
goog.net.tmpnetwork.testLoadImageWithRetries(uri.toString(),
|
||||
goog.net.BrowserTestChannel.BLOCKED_TIMEOUT_,
|
||||
goog.bind(this.checkBlockedCallback_, this),
|
||||
goog.net.BrowserTestChannel.BLOCKED_RETRIES_,
|
||||
goog.net.BrowserTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_);
|
||||
this.notifyServerReachabilityEvent(
|
||||
goog.net.BrowserChannel.ServerReachability.REQUEST_MADE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for testLoadImageWithRetries to check if browser channel is
|
||||
* blocked.
|
||||
* @param {boolean} succeeded Whether the request succeeded.
|
||||
* @private
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.checkBlockedCallback_ = function(
|
||||
succeeded) {
|
||||
if (succeeded) {
|
||||
this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
|
||||
this.connectStage2_();
|
||||
} else {
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.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) {
|
||||
this.notifyServerReachabilityEvent(
|
||||
goog.net.BrowserChannel.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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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');
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_START);
|
||||
if (secondTestResults) { // Buffered/Proxy connection
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.PROXY);
|
||||
this.channel_.testConnectionFinished(this, false);
|
||||
} else { // Unbuffered/NoProxy connection
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.NOPROXY);
|
||||
this.channel_.testConnectionFinished(this, true);
|
||||
}
|
||||
return; // Skip the test
|
||||
}
|
||||
this.request_ = goog.net.BrowserChannel.createChannelRequest(
|
||||
this, this.channelDebug_);
|
||||
this.request_.setExtraHeaders(this.extraHeaders_);
|
||||
var recvDataUri = this.channel_.getBackChannelUri(this.hostPrefix_,
|
||||
/** @type {string} */ (this.path_));
|
||||
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_START);
|
||||
if (!goog.net.ChannelRequest.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 */);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Factory method for XhrIo objects.
|
||||
* @param {?string} hostPrefix The host prefix, if we need an XhrIo object
|
||||
* capable of calling a secondary domain.
|
||||
* @return {!goog.net.XhrIo} New XhrIo object.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.createXhrIo = function(hostPrefix) {
|
||||
return this.channel_.createXhrIo(hostPrefix);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Aborts the test channel.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.isClosed = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback from ChannelRequest for when new data is received
|
||||
*
|
||||
* @param {goog.net.ChannelRequest} req The request object.
|
||||
* @param {string} responseText The text of the response.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.onRequestData =
|
||||
function(req, responseText) {
|
||||
this.lastStatusCode_ = req.getLastStatusCode();
|
||||
if (this.state_ == goog.net.BrowserTestChannel.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,
|
||||
goog.net.ChannelRequest.Error.BAD_DATA);
|
||||
return;
|
||||
}
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var respArray = this.parser_.parse(responseText);
|
||||
} catch (e) {
|
||||
this.channelDebug_.dumpException(e);
|
||||
this.channel_.testConnectionFailure(this,
|
||||
goog.net.ChannelRequest.Error.BAD_DATA);
|
||||
return;
|
||||
}
|
||||
this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);
|
||||
this.blockedPrefix_ = respArray[1];
|
||||
} else if (this.state_ ==
|
||||
goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
|
||||
if (this.receivedIntermediateResult_) {
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.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') {
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.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');
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.NOPROXY);
|
||||
this.channel_.testConnectionFinished(this, true);
|
||||
}
|
||||
} else {
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.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 {goog.net.ChannelRequest} req The request object.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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_ == goog.net.BrowserTestChannel.State_.INIT) {
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.TEST_STAGE_ONE_FAILED);
|
||||
} else if (this.state_ ==
|
||||
goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_FAILED);
|
||||
}
|
||||
this.channel_.testConnectionFailure(this,
|
||||
/** @type {goog.net.ChannelRequest.Error} */
|
||||
(this.request_.getLastError()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state_ == goog.net.BrowserTestChannel.State_.INIT) {
|
||||
this.channelDebug_.debug(
|
||||
'TestConnection: request complete for initial check');
|
||||
if (this.blockedPrefix_) {
|
||||
this.state_ = goog.net.BrowserTestChannel.State_.CHECKING_BLOCKED;
|
||||
this.checkBlocked_();
|
||||
} else {
|
||||
this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
|
||||
this.connectStage2_();
|
||||
}
|
||||
} else if (this.state_ ==
|
||||
goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
|
||||
this.channelDebug_.debug('TestConnection: request complete for stage 2');
|
||||
var goodConn = false;
|
||||
|
||||
if (!goog.net.ChannelRequest.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');
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.Stat.NOPROXY);
|
||||
this.channel_.testConnectionFinished(this, true);
|
||||
} else {
|
||||
this.channelDebug_.debug(
|
||||
'Test connection failed; not using streaming');
|
||||
goog.net.BrowserChannel.notifyStatEvent(
|
||||
goog.net.BrowserChannel.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.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.getLastStatusCode = function() {
|
||||
return this.lastStatusCode_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether we should be using secondary domains when the
|
||||
* server instructs us to do so.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.shouldUseSecondaryDomains = function() {
|
||||
return this.channel_.shouldUseSecondaryDomains();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.BrowserTestChannel.prototype.isActive =
|
||||
function(browserChannel) {
|
||||
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
|
||||
*/
|
||||
goog.net.BrowserTestChannel.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 goog.net.ChannelRequest.supportsXhrStreaming() ||
|
||||
ms < goog.net.BrowserTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the channel of a fine grained network event.
|
||||
* @param {goog.net.BrowserChannel.ServerReachability} reachabilityType The
|
||||
* reachability event type.
|
||||
*/
|
||||
goog.net.BrowserTestChannel.prototype.notifyServerReachabilityEvent =
|
||||
function(reachabilityType) {
|
||||
this.channel_.notifyServerReachabilityEvent(reachabilityType);
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2008 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 Loads a list of URIs in bulk. All requests must be a success
|
||||
* in order for the load to be considered a success.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.BulkLoader');
|
||||
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.net.BulkLoaderHelper');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.net.XhrIo');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class used to load multiple URIs.
|
||||
* @param {Array.<string|goog.Uri>} uris The URIs to load.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.net.BulkLoader = function(uris) {
|
||||
goog.events.EventTarget.call(this);
|
||||
|
||||
/**
|
||||
* The bulk loader helper.
|
||||
* @type {goog.net.BulkLoaderHelper}
|
||||
* @private
|
||||
*/
|
||||
this.helper_ = new goog.net.BulkLoaderHelper(uris);
|
||||
|
||||
/**
|
||||
* The handler for managing events.
|
||||
* @type {goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
};
|
||||
goog.inherits(goog.net.BulkLoader, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* A logger.
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.logger_ =
|
||||
goog.log.getLogger('goog.net.BulkLoader');
|
||||
|
||||
|
||||
/**
|
||||
* Gets the response texts, in order.
|
||||
* @return {Array.<string>} The response texts.
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.getResponseTexts = function() {
|
||||
return this.helper_.getResponseTexts();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the request Uris.
|
||||
* @return {Array.<string>} The request URIs, in order.
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.getRequestUris = function() {
|
||||
return this.helper_.getUris();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the process of loading the URIs.
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.load = function() {
|
||||
var eventHandler = this.eventHandler_;
|
||||
var uris = this.helper_.getUris();
|
||||
goog.log.info(this.logger_,
|
||||
'Starting load of code with ' + uris.length + ' uris.');
|
||||
|
||||
for (var i = 0; i < uris.length; i++) {
|
||||
var xhrIo = new goog.net.XhrIo();
|
||||
eventHandler.listen(xhrIo,
|
||||
goog.net.EventType.COMPLETE,
|
||||
goog.bind(this.handleEvent_, this, i));
|
||||
|
||||
xhrIo.send(uris[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles all events fired by the XhrManager.
|
||||
* @param {number} id The id of the request.
|
||||
* @param {goog.events.Event} e The event.
|
||||
* @private
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.handleEvent_ = function(id, e) {
|
||||
goog.log.info(this.logger_, 'Received event "' + e.type + '" for id ' + id +
|
||||
' with uri ' + this.helper_.getUri(id));
|
||||
var xhrIo = /** @type {goog.net.XhrIo} */ (e.target);
|
||||
if (xhrIo.isSuccess()) {
|
||||
this.handleSuccess_(id, xhrIo);
|
||||
} else {
|
||||
this.handleError_(id, xhrIo);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles when a request is successful (i.e., completed and response received).
|
||||
* Stores thhe responseText and checks if loading is complete.
|
||||
* @param {number} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo objects that was used.
|
||||
* @private
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.handleSuccess_ = function(
|
||||
id, xhrIo) {
|
||||
// Save the response text.
|
||||
this.helper_.setResponseText(id, xhrIo.getResponseText());
|
||||
|
||||
// Check if all response texts have been received.
|
||||
if (this.helper_.isLoadComplete()) {
|
||||
this.finishLoad_();
|
||||
}
|
||||
xhrIo.dispose();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles when a request has ended in error (i.e., all retries completed and
|
||||
* none were successful). Cancels loading of the URI's.
|
||||
* @param {number|string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo objects that was used.
|
||||
* @private
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.handleError_ = function(
|
||||
id, xhrIo) {
|
||||
// TODO(user): Abort all pending requests.
|
||||
|
||||
// Dispatch the ERROR event.
|
||||
this.dispatchEvent(goog.net.EventType.ERROR);
|
||||
xhrIo.dispose();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finishes the load of the URI's. Dispatches the SUCCESS event.
|
||||
* @private
|
||||
*/
|
||||
goog.net.BulkLoader.prototype.finishLoad_ = function() {
|
||||
goog.log.info(this.logger_, 'All uris loaded.');
|
||||
|
||||
// Dispatch the SUCCESS event.
|
||||
this.dispatchEvent(goog.net.EventType.SUCCESS);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.BulkLoader.prototype.disposeInternal = function() {
|
||||
goog.net.BulkLoader.superClass_.disposeInternal.call(this);
|
||||
|
||||
this.eventHandler_.dispose();
|
||||
this.eventHandler_ = null;
|
||||
|
||||
this.helper_.dispose();
|
||||
this.helper_ = null;
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2008 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 Helper class to load a list of URIs in bulk. All URIs
|
||||
* must be a successfully loaded in order for the entire load to be considered
|
||||
* a success.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.BulkLoaderHelper');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.log');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Helper class used to load multiple URIs.
|
||||
* @param {Array.<string|goog.Uri>} uris The URIs to load.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.net.BulkLoaderHelper = function(uris) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The URIs to load.
|
||||
* @type {Array.<string|goog.Uri>}
|
||||
* @private
|
||||
*/
|
||||
this.uris_ = uris;
|
||||
|
||||
/**
|
||||
* The response from the XHR's.
|
||||
* @type {Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
this.responseTexts_ = [];
|
||||
};
|
||||
goog.inherits(goog.net.BulkLoaderHelper, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* A logger.
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.net.BulkLoaderHelper.prototype.logger_ =
|
||||
goog.log.getLogger('goog.net.BulkLoaderHelper');
|
||||
|
||||
|
||||
/**
|
||||
* Gets the URI by id.
|
||||
* @param {number} id The id.
|
||||
* @return {string|goog.Uri} The URI specified by the id.
|
||||
*/
|
||||
goog.net.BulkLoaderHelper.prototype.getUri = function(id) {
|
||||
return this.uris_[id];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the URIs.
|
||||
* @return {Array.<string|goog.Uri>} The URIs.
|
||||
*/
|
||||
goog.net.BulkLoaderHelper.prototype.getUris = function() {
|
||||
return this.uris_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the response texts.
|
||||
* @return {Array.<string>} The response texts.
|
||||
*/
|
||||
goog.net.BulkLoaderHelper.prototype.getResponseTexts = function() {
|
||||
return this.responseTexts_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the response text by id.
|
||||
* @param {number} id The id.
|
||||
* @param {string} responseText The response texts.
|
||||
*/
|
||||
goog.net.BulkLoaderHelper.prototype.setResponseText = function(
|
||||
id, responseText) {
|
||||
this.responseTexts_[id] = responseText;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the load of the URIs is complete.
|
||||
* @return {boolean} TRUE iff the load is complete.
|
||||
*/
|
||||
goog.net.BulkLoaderHelper.prototype.isLoadComplete = function() {
|
||||
var responseTexts = this.responseTexts_;
|
||||
if (responseTexts.length == this.uris_.length) {
|
||||
for (var i = 0; i < responseTexts.length; i++) {
|
||||
if (!goog.isDefAndNotNull(responseTexts[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.BulkLoaderHelper.prototype.disposeInternal = function() {
|
||||
goog.net.BulkLoaderHelper.superClass_.disposeInternal.call(this);
|
||||
|
||||
this.uris_ = null;
|
||||
this.responseTexts_ = null;
|
||||
};
|
||||
@@ -0,0 +1,300 @@
|
||||
// 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 ChannelDebug class. ChannelDebug provides
|
||||
* a utility for tracing and debugging the BrowserChannel requests.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Namespace for BrowserChannel
|
||||
*/
|
||||
goog.provide('goog.net.ChannelDebug');
|
||||
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.log');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Logs and keeps a buffer of debugging info for the Channel.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.ChannelDebug = function() {
|
||||
/**
|
||||
* The logger instance.
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
this.logger_ = goog.log.getLogger('goog.net.BrowserChannel');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the logger used by this ChannelDebug.
|
||||
* @return {goog.debug.Logger} The logger used by this ChannelDebug.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.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.
|
||||
*/
|
||||
goog.net.ChannelDebug.prototype.timeoutResponse = function(uri) {
|
||||
this.info('TIMEOUT: ' + uri);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a debug message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
goog.net.ChannelDebug.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'.
|
||||
*/
|
||||
goog.net.ChannelDebug.prototype.dumpException = function(e, opt_msg) {
|
||||
this.severe((opt_msg || 'Exception') + e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs an info message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
goog.net.ChannelDebug.prototype.info = function(text) {
|
||||
goog.log.info(this.logger_, text);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a warning message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
goog.net.ChannelDebug.prototype.warning = function(text) {
|
||||
goog.log.warning(this.logger_, text);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a severe message.
|
||||
* @param {string} text The message.
|
||||
*/
|
||||
goog.net.ChannelDebug.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
|
||||
*/
|
||||
goog.net.ChannelDebug.prototype.redactResponse_ = function(responseText) {
|
||||
// first check if it's not JS - the only non-JS should be the magic cookie
|
||||
if (!responseText ||
|
||||
/** @suppress {missingRequire}. The require creates a circular
|
||||
* dependency.
|
||||
*/
|
||||
responseText == goog.net.BrowserChannel.MAGIC_RESPONSE_COOKIE) {
|
||||
return responseText;
|
||||
}
|
||||
/** @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
|
||||
*/
|
||||
goog.net.ChannelDebug.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
|
||||
*/
|
||||
goog.net.ChannelDebug.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;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,370 @@
|
||||
// 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 Functions for setting, getting and deleting cookies.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.Cookies');
|
||||
goog.provide('goog.net.cookies');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for handling browser cookies.
|
||||
* @param {Document} context The context document to get/set cookies on.
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.Cookies = function(context) {
|
||||
/**
|
||||
* The context document to get/set cookies on
|
||||
* @type {Document}
|
||||
* @private
|
||||
*/
|
||||
this.document_ = context;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static constant for the size of cookies. Per the spec, there's a 4K limit
|
||||
* to the size of a cookie. To make sure users can't break this limit, we
|
||||
* should truncate long cookies at 3950 bytes, to be extra careful with dumb
|
||||
* browsers/proxies that interpret 4K as 4000 rather than 4096.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.net.Cookies.MAX_COOKIE_LENGTH = 3950;
|
||||
|
||||
|
||||
/**
|
||||
* RegExp used to split the cookies string.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.SPLIT_RE_ = /\s*;\s*/;
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if cookies are enabled.
|
||||
* @return {boolean} True if cookies are enabled.
|
||||
*/
|
||||
goog.net.Cookies.prototype.isEnabled = function() {
|
||||
return navigator.cookieEnabled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* We do not allow '=', ';', or white space in the name.
|
||||
*
|
||||
* NOTE: The following are allowed by this method, but should be avoided for
|
||||
* cookies handled by the server.
|
||||
* - any name starting with '$'
|
||||
* - 'Comment'
|
||||
* - 'Domain'
|
||||
* - 'Expires'
|
||||
* - 'Max-Age'
|
||||
* - 'Path'
|
||||
* - 'Secure'
|
||||
* - 'Version'
|
||||
*
|
||||
* @param {string} name Cookie name.
|
||||
* @return {boolean} Whether name is valid.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2965">RFC 2965</a>
|
||||
*/
|
||||
goog.net.Cookies.prototype.isValidName = function(name) {
|
||||
return !(/[;=\s]/.test(name));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* We do not allow ';' or line break in the value.
|
||||
*
|
||||
* Spec does not mention any illegal characters, but in practice semi-colons
|
||||
* break parsing and line breaks truncate the name.
|
||||
*
|
||||
* @param {string} value Cookie value.
|
||||
* @return {boolean} Whether value is valid.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2965">RFC 2965</a>
|
||||
*/
|
||||
goog.net.Cookies.prototype.isValidValue = function(value) {
|
||||
return !(/[;\r\n]/.test(value));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a cookie. The max_age can be -1 to set a session cookie. To remove and
|
||||
* expire cookies, use remove() instead.
|
||||
*
|
||||
* Neither the {@code name} nor the {@code value} are encoded in any way. It is
|
||||
* up to the callers of {@code get} and {@code set} (as well as all the other
|
||||
* methods) to handle any possible encoding and decoding.
|
||||
*
|
||||
* @throws {!Error} If the {@code name} fails #goog.net.cookies.isValidName.
|
||||
* @throws {!Error} If the {@code value} fails #goog.net.cookies.isValidValue.
|
||||
*
|
||||
* @param {string} name The cookie name.
|
||||
* @param {string} value The cookie value.
|
||||
* @param {number=} opt_maxAge The max age in seconds (from now). Use -1 to
|
||||
* set a session cookie. If not provided, the default is -1
|
||||
* (i.e. set a session cookie).
|
||||
* @param {?string=} opt_path The path of the cookie. If not present then this
|
||||
* uses the full request path.
|
||||
* @param {?string=} opt_domain The domain of the cookie, or null to not
|
||||
* specify a domain attribute (browser will use the full request host name).
|
||||
* If not provided, the default is null (i.e. let browser use full request
|
||||
* host name).
|
||||
* @param {boolean=} opt_secure Whether the cookie should only be sent over
|
||||
* a secure channel.
|
||||
*/
|
||||
goog.net.Cookies.prototype.set = function(
|
||||
name, value, opt_maxAge, opt_path, opt_domain, opt_secure) {
|
||||
if (!this.isValidName(name)) {
|
||||
throw Error('Invalid cookie name "' + name + '"');
|
||||
}
|
||||
if (!this.isValidValue(value)) {
|
||||
throw Error('Invalid cookie value "' + value + '"');
|
||||
}
|
||||
|
||||
if (!goog.isDef(opt_maxAge)) {
|
||||
opt_maxAge = -1;
|
||||
}
|
||||
|
||||
var domainStr = opt_domain ? ';domain=' + opt_domain : '';
|
||||
var pathStr = opt_path ? ';path=' + opt_path : '';
|
||||
var secureStr = opt_secure ? ';secure' : '';
|
||||
|
||||
var expiresStr;
|
||||
|
||||
// Case 1: Set a session cookie.
|
||||
if (opt_maxAge < 0) {
|
||||
expiresStr = '';
|
||||
|
||||
// Case 2: Expire the cookie.
|
||||
// Note: We don't tell people about this option in the function doc because
|
||||
// we prefer people to use ExpireCookie() to expire cookies.
|
||||
} else if (opt_maxAge == 0) {
|
||||
// Note: Don't use Jan 1, 1970 for date because NS 4.76 will try to convert
|
||||
// it to local time, and if the local time is before Jan 1, 1970, then the
|
||||
// browser will ignore the Expires attribute altogether.
|
||||
var pastDate = new Date(1970, 1 /*Feb*/, 1); // Feb 1, 1970
|
||||
expiresStr = ';expires=' + pastDate.toUTCString();
|
||||
|
||||
// Case 3: Set a persistent cookie.
|
||||
} else {
|
||||
var futureDate = new Date(goog.now() + opt_maxAge * 1000);
|
||||
expiresStr = ';expires=' + futureDate.toUTCString();
|
||||
}
|
||||
|
||||
this.setCookie_(name + '=' + value + domainStr + pathStr +
|
||||
expiresStr + secureStr);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value for the first cookie with the given name.
|
||||
* @param {string} name The name of the cookie to get.
|
||||
* @param {string=} opt_default If not found this is returned instead.
|
||||
* @return {string|undefined} The value of the cookie. If no cookie is set this
|
||||
* returns opt_default or undefined if opt_default is not provided.
|
||||
*/
|
||||
goog.net.Cookies.prototype.get = function(name, opt_default) {
|
||||
var nameEq = name + '=';
|
||||
var parts = this.getParts_();
|
||||
for (var i = 0, part; part = parts[i]; i++) {
|
||||
// startsWith
|
||||
if (part.lastIndexOf(nameEq, 0) == 0) {
|
||||
return part.substr(nameEq.length);
|
||||
}
|
||||
if (part == name) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return opt_default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes and expires a cookie.
|
||||
* @param {string} name The cookie name.
|
||||
* @param {string=} opt_path The path of the cookie, or null to expire a cookie
|
||||
* set at the full request path. If not provided, the default is '/'
|
||||
* (i.e. path=/).
|
||||
* @param {string=} opt_domain The domain of the cookie, or null to expire a
|
||||
* cookie set at the full request host name. If not provided, the default is
|
||||
* null (i.e. cookie at full request host name).
|
||||
* @return {boolean} Whether the cookie existed before it was removed.
|
||||
*/
|
||||
goog.net.Cookies.prototype.remove = function(name, opt_path, opt_domain) {
|
||||
var rv = this.containsKey(name);
|
||||
this.set(name, '', 0, opt_path, opt_domain);
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the names for all the cookies.
|
||||
* @return {Array.<string>} An array with the names of the cookies.
|
||||
*/
|
||||
goog.net.Cookies.prototype.getKeys = function() {
|
||||
return this.getKeyValues_().keys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the values for all the cookies.
|
||||
* @return {Array.<string>} An array with the values of the cookies.
|
||||
*/
|
||||
goog.net.Cookies.prototype.getValues = function() {
|
||||
return this.getKeyValues_().values;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether there are any cookies for this document.
|
||||
*/
|
||||
goog.net.Cookies.prototype.isEmpty = function() {
|
||||
return !this.getCookie_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of cookies for this document.
|
||||
*/
|
||||
goog.net.Cookies.prototype.getCount = function() {
|
||||
var cookie = this.getCookie_();
|
||||
if (!cookie) {
|
||||
return 0;
|
||||
}
|
||||
return this.getParts_().length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there is a cookie with the given name.
|
||||
* @param {string} key The name of the cookie to test for.
|
||||
* @return {boolean} Whether there is a cookie by that name.
|
||||
*/
|
||||
goog.net.Cookies.prototype.containsKey = function(key) {
|
||||
// substring will return empty string if the key is not found, so the get
|
||||
// function will only return undefined
|
||||
return goog.isDef(this.get(key));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there is a cookie with the given value. (This is an O(n)
|
||||
* operation.)
|
||||
* @param {string} value The value to check for.
|
||||
* @return {boolean} Whether there is a cookie with that value.
|
||||
*/
|
||||
goog.net.Cookies.prototype.containsValue = function(value) {
|
||||
// this O(n) in any case so lets do the trivial thing.
|
||||
var values = this.getKeyValues_().values;
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (values[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all cookies for this document. Note that this will only remove
|
||||
* cookies from the current path and domain. If there are cookies set using a
|
||||
* subpath and/or another domain these will still be there.
|
||||
*/
|
||||
goog.net.Cookies.prototype.clear = function() {
|
||||
var keys = this.getKeyValues_().keys;
|
||||
for (var i = keys.length - 1; i >= 0; i--) {
|
||||
this.remove(keys[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper function to allow testing cookies without depending on the
|
||||
* browser.
|
||||
* @param {string} s The cookie string to set.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.setCookie_ = function(s) {
|
||||
this.document_.cookie = s;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper function to allow testing cookies without depending on the
|
||||
* browser. IE6 can return null here.
|
||||
* @return {?string} Returns the {@code document.cookie}.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.getCookie_ = function() {
|
||||
return this.document_.cookie;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array.<string>} The cookie split on semi colons.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.getParts_ = function() {
|
||||
return (this.getCookie_() || '').
|
||||
split(goog.net.Cookies.SPLIT_RE_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the names and values for all the cookies.
|
||||
* @return {Object} An object with keys and values.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.getKeyValues_ = function() {
|
||||
var parts = this.getParts_();
|
||||
var keys = [], values = [], index, part;
|
||||
for (var i = 0; part = parts[i]; i++) {
|
||||
index = part.indexOf('=');
|
||||
|
||||
if (index == -1) { // empty name
|
||||
keys.push('');
|
||||
values.push(part);
|
||||
} else {
|
||||
keys.push(part.substring(0, index));
|
||||
values.push(part.substring(index + 1));
|
||||
}
|
||||
}
|
||||
return {keys: keys, values: values};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A static default instance.
|
||||
* @type {goog.net.Cookies}
|
||||
*/
|
||||
goog.net.cookies = new goog.net.Cookies(document);
|
||||
|
||||
|
||||
/**
|
||||
* Define the constant on the instance in order not to break many references to
|
||||
* it.
|
||||
* @type {number}
|
||||
* @deprecated Use goog.net.Cookies.MAX_COOKIE_LENGTH instead.
|
||||
*/
|
||||
goog.net.cookies.MAX_COOKIE_LENGTH = goog.net.Cookies.MAX_COOKIE_LENGTH;
|
||||
@@ -0,0 +1,849 @@
|
||||
// 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 Cross domain RPC library using the <a
|
||||
* href="http://go/xd2_design" target="_top">XD2 approach</a>.
|
||||
*
|
||||
* <h5>Protocol</h5>
|
||||
* Client sends a request across domain via a form submission. Server
|
||||
* receives these parameters: "xdpe:request-id", "xdpe:dummy-uri" ("xdpe" for
|
||||
* "cross domain parameter to echo back") and other user parameters prefixed
|
||||
* with "xdp" (for "cross domain parameter"). Headers are passed as parameters
|
||||
* prefixed with "xdh" (for "cross domain header"). Only strings are supported
|
||||
* for parameters and headers. A GET method is mapped to a form GET. All
|
||||
* other methods are mapped to a POST. Server is expected to produce a
|
||||
* HTML response such as the following:
|
||||
* <pre>
|
||||
* <body>
|
||||
* <script type="text/javascript"
|
||||
* src="path-to-crossdomainrpc.js"></script>
|
||||
* var currentDirectory = location.href.substring(
|
||||
* 0, location.href.lastIndexOf('/')
|
||||
* );
|
||||
*
|
||||
* // echo all parameters prefixed with "xdpe:"
|
||||
* var echo = {};
|
||||
* echo[goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID] =
|
||||
* <value of parameter "xdpe:request-id">;
|
||||
* echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI] =
|
||||
* <value of parameter "xdpe:dummy-uri">;
|
||||
*
|
||||
* goog.net.CrossDomainRpc.sendResponse(
|
||||
* '({"result":"<responseInJSON"})',
|
||||
* true, // is JSON
|
||||
* echo, // parameters to echo back
|
||||
* status, // response status code
|
||||
* headers // response headers
|
||||
* );
|
||||
* </script>
|
||||
* </body>
|
||||
* </pre>
|
||||
*
|
||||
* <h5>Server Side</h5>
|
||||
* For an example of the server side, refer to the following files:
|
||||
* <ul>
|
||||
* <li>http://go/xdservletfilter.java</li>
|
||||
* <li>http://go/xdservletrequest.java</li>
|
||||
* <li>http://go/xdservletresponse.java</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h5>System Requirements</h5>
|
||||
* Tested on IE6, IE7, Firefox 2.0 and Safari nightly r23841.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.CrossDomainRpc');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.net.HttpStatus');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of cross domain RPC
|
||||
* @extends {goog.events.EventTarget}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.CrossDomainRpc = function() {
|
||||
goog.events.EventTarget.call(this);
|
||||
};
|
||||
goog.inherits(goog.net.CrossDomainRpc, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Cross-domain response iframe marker.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.RESPONSE_MARKER_ = 'xdrp';
|
||||
|
||||
|
||||
/**
|
||||
* Use a fallback dummy resource if none specified or detected.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.useFallBackDummyResource_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if we are executing inside a response iframe. This is the
|
||||
* case when this page is used as a dummy resource to gain caller's domain.
|
||||
* @return {*} True if we are executing inside a response iframe; false
|
||||
* otherwise.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.isInResponseIframe_ = function() {
|
||||
return window.location && (window.location.hash.indexOf(
|
||||
goog.net.CrossDomainRpc.RESPONSE_MARKER_) == 1 ||
|
||||
window.location.search.indexOf(
|
||||
goog.net.CrossDomainRpc.RESPONSE_MARKER_) == 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops execution of the rest of the page if this page is loaded inside a
|
||||
* response iframe.
|
||||
*/
|
||||
if (goog.net.CrossDomainRpc.isInResponseIframe_()) {
|
||||
if (goog.userAgent.IE) {
|
||||
document.execCommand('Stop');
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
window.stop();
|
||||
} else {
|
||||
throw Error('stopped');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the URI for a dummy resource on caller's domain. This function is
|
||||
* used for specifying a particular resource to use rather than relying on
|
||||
* auto detection.
|
||||
* @param {string} dummyResourceUri URI to dummy resource on the same domain
|
||||
* of caller's page.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.setDummyResourceUri = function(dummyResourceUri) {
|
||||
goog.net.CrossDomainRpc.dummyResourceUri_ = dummyResourceUri;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether a fallback dummy resource ("/robots.txt" on Firefox and Safari
|
||||
* and current page on IE) should be used when a suitable dummy resource is
|
||||
* not available.
|
||||
* @param {boolean} useFallBack Whether to use fallback or not.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.setUseFallBackDummyResource = function(useFallBack) {
|
||||
goog.net.CrossDomainRpc.useFallBackDummyResource_ = useFallBack;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a request across domain.
|
||||
* @param {string} uri Uri to make request to.
|
||||
* @param {Function=} opt_continuation Continuation function to be called
|
||||
* when request is completed. Takes one argument of an event object
|
||||
* whose target has the following properties: "status" is the HTTP
|
||||
* response status code, "responseText" is the response text,
|
||||
* and "headers" is an object with all response headers. The event
|
||||
* target's getResponseJson() method returns a JavaScript object evaluated
|
||||
* from the JSON response or undefined if response is not JSON.
|
||||
* @param {string=} opt_method Method of request. Default is POST.
|
||||
* @param {Object=} opt_params Parameters. Each property is turned into a
|
||||
* request parameter.
|
||||
* @param {Object=} opt_headers Map of headers of the request.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.send =
|
||||
function(uri, opt_continuation, opt_method, opt_params, opt_headers) {
|
||||
var xdrpc = new goog.net.CrossDomainRpc();
|
||||
if (opt_continuation) {
|
||||
goog.events.listen(xdrpc, goog.net.EventType.COMPLETE, opt_continuation);
|
||||
}
|
||||
goog.events.listen(xdrpc, goog.net.EventType.READY, xdrpc.reset);
|
||||
xdrpc.sendRequest(uri, opt_method, opt_params, opt_headers);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets debug mode to true or false. When debug mode is on, response iframes
|
||||
* are visible and left behind after their use is finished.
|
||||
* @param {boolean} flag Flag to indicate intention to turn debug model on
|
||||
* (true) or off (false).
|
||||
*/
|
||||
goog.net.CrossDomainRpc.setDebugMode = function(flag) {
|
||||
goog.net.CrossDomainRpc.debugMode_ = flag;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logger for goog.net.CrossDomainRpc
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.logger_ =
|
||||
goog.log.getLogger('goog.net.CrossDomainRpc');
|
||||
|
||||
|
||||
/**
|
||||
* Creates the HTML of an input element
|
||||
* @param {string} name Name of input element.
|
||||
* @param {*} value Value of input element.
|
||||
* @return {string} HTML of input element with that name and value.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.createInputHtml_ = function(name, value) {
|
||||
return '<textarea name="' + name + '">' +
|
||||
goog.net.CrossDomainRpc.escapeAmpersand_(value) + '</textarea>';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Escapes ampersand so that XML/HTML entities are submitted as is because
|
||||
* browser unescapes them when they are put into a text area.
|
||||
* @param {*} value Value to escape.
|
||||
* @return {*} Value with ampersand escaped, if value is a string;
|
||||
* otherwise the value itself is returned.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.escapeAmpersand_ = function(value) {
|
||||
return value && (goog.isString(value) || value.constructor == String) ?
|
||||
value.replace(/&/g, '&') : value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds a dummy resource that can be used by response to gain domain of
|
||||
* requester's page.
|
||||
* @return {string} URI of the resource to use.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.getDummyResourceUri_ = function() {
|
||||
if (goog.net.CrossDomainRpc.dummyResourceUri_) {
|
||||
return goog.net.CrossDomainRpc.dummyResourceUri_;
|
||||
}
|
||||
|
||||
// find a style sheet if not on IE, which will attempt to save style sheet
|
||||
if (goog.userAgent.GECKO) {
|
||||
var links = document.getElementsByTagName('link');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
// find a link which is on the same domain as this page
|
||||
// cannot use one with '?' or '#' in its URL as it will confuse
|
||||
// goog.net.CrossDomainRpc.getFramePayload_()
|
||||
if (link.rel == 'stylesheet' &&
|
||||
goog.Uri.haveSameDomain(link.href, window.location.href) &&
|
||||
link.href.indexOf('?') < 0) {
|
||||
return goog.net.CrossDomainRpc.removeHash_(link.href);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var images = document.getElementsByTagName('img');
|
||||
for (var i = 0; i < images.length; i++) {
|
||||
var image = images[i];
|
||||
// find a link which is on the same domain as this page
|
||||
// cannot use one with '?' or '#' in its URL as it will confuse
|
||||
// goog.net.CrossDomainRpc.getFramePayload_()
|
||||
if (goog.Uri.haveSameDomain(image.src, window.location.href) &&
|
||||
image.src.indexOf('?') < 0) {
|
||||
return goog.net.CrossDomainRpc.removeHash_(image.src);
|
||||
}
|
||||
}
|
||||
|
||||
if (!goog.net.CrossDomainRpc.useFallBackDummyResource_) {
|
||||
throw Error(
|
||||
'No suitable dummy resource specified or detected for this page');
|
||||
}
|
||||
|
||||
if (goog.userAgent.IE) {
|
||||
// use this page as the dummy resource; remove hash from URL if any
|
||||
return goog.net.CrossDomainRpc.removeHash_(window.location.href);
|
||||
} else {
|
||||
/**
|
||||
* Try to use "http://<this-domain>/robots.txt" which may exist. Even if
|
||||
* it does not, an error page is returned and is a good dummy resource to
|
||||
* use on Firefox and Safari. An existing resource is faster because it
|
||||
* is cached.
|
||||
*/
|
||||
var locationHref = window.location.href;
|
||||
var rootSlash = locationHref.indexOf('/', locationHref.indexOf('//') + 2);
|
||||
var rootHref = locationHref.substring(0, rootSlash);
|
||||
return rootHref + '/robots.txt';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes everything at and after hash from URI
|
||||
* @param {string} uri Uri to to remove hash.
|
||||
* @return {string} Uri with its hash and all characters after removed.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.removeHash_ = function(uri) {
|
||||
return uri.split('#')[0];
|
||||
};
|
||||
|
||||
|
||||
// ------------
|
||||
// request side
|
||||
|
||||
|
||||
/**
|
||||
* next request id used to support multiple XD requests at the same time
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.nextRequestId_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Header prefix.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.CrossDomainRpc.HEADER = 'xdh:';
|
||||
|
||||
|
||||
/**
|
||||
* Parameter prefix.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.CrossDomainRpc.PARAM = 'xdp:';
|
||||
|
||||
|
||||
/**
|
||||
* Parameter to echo prefix.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.CrossDomainRpc.PARAM_ECHO = 'xdpe:';
|
||||
|
||||
|
||||
/**
|
||||
* Parameter to echo: request id
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID =
|
||||
goog.net.CrossDomainRpc.PARAM_ECHO + 'request-id';
|
||||
|
||||
|
||||
/**
|
||||
* Parameter to echo: dummy resource URI
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI =
|
||||
goog.net.CrossDomainRpc.PARAM_ECHO + 'dummy-uri';
|
||||
|
||||
|
||||
/**
|
||||
* Cross-domain request marker.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.REQUEST_MARKER_ = 'xdrq';
|
||||
|
||||
|
||||
/**
|
||||
* Sends a request across domain.
|
||||
* @param {string} uri Uri to make request to.
|
||||
* @param {string=} opt_method Method of request. Default is POST.
|
||||
* @param {Object=} opt_params Parameters. Each property is turned into a
|
||||
* request parameter.
|
||||
* @param {Object=} opt_headers Map of headers of the request.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.prototype.sendRequest =
|
||||
function(uri, opt_method, opt_params, opt_headers) {
|
||||
// create request frame
|
||||
var requestFrame = this.requestFrame_ = document.createElement('iframe');
|
||||
var requestId = goog.net.CrossDomainRpc.nextRequestId_++;
|
||||
requestFrame.id = goog.net.CrossDomainRpc.REQUEST_MARKER_ + '-' + requestId;
|
||||
if (!goog.net.CrossDomainRpc.debugMode_) {
|
||||
requestFrame.style.position = 'absolute';
|
||||
requestFrame.style.top = '-5000px';
|
||||
requestFrame.style.left = '-5000px';
|
||||
}
|
||||
document.body.appendChild(requestFrame);
|
||||
|
||||
// build inputs
|
||||
var inputs = [];
|
||||
|
||||
// add request id
|
||||
inputs.push(goog.net.CrossDomainRpc.createInputHtml_(
|
||||
goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID, requestId));
|
||||
|
||||
// add dummy resource uri
|
||||
var dummyUri = goog.net.CrossDomainRpc.getDummyResourceUri_();
|
||||
goog.log.fine(goog.net.CrossDomainRpc.logger_,
|
||||
'dummyUri: ' + dummyUri);
|
||||
inputs.push(goog.net.CrossDomainRpc.createInputHtml_(
|
||||
goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI, dummyUri));
|
||||
|
||||
// add parameters
|
||||
if (opt_params) {
|
||||
for (var name in opt_params) {
|
||||
var value = opt_params[name];
|
||||
inputs.push(goog.net.CrossDomainRpc.createInputHtml_(
|
||||
goog.net.CrossDomainRpc.PARAM + name, value));
|
||||
}
|
||||
}
|
||||
|
||||
// add headers
|
||||
if (opt_headers) {
|
||||
for (var name in opt_headers) {
|
||||
var value = opt_headers[name];
|
||||
inputs.push(goog.net.CrossDomainRpc.createInputHtml_(
|
||||
goog.net.CrossDomainRpc.HEADER + name, value));
|
||||
}
|
||||
}
|
||||
|
||||
var requestFrameContent = '<body><form method="' +
|
||||
(opt_method == 'GET' ? 'GET' : 'POST') + '" action="' +
|
||||
uri + '">' + inputs.join('') + '</form></body>';
|
||||
var requestFrameDoc = goog.dom.getFrameContentDocument(requestFrame);
|
||||
requestFrameDoc.open();
|
||||
requestFrameDoc.write(requestFrameContent);
|
||||
requestFrameDoc.close();
|
||||
|
||||
requestFrameDoc.forms[0].submit();
|
||||
requestFrameDoc = null;
|
||||
|
||||
this.loadListenerKey_ = goog.events.listen(
|
||||
requestFrame, goog.events.EventType.LOAD, function() {
|
||||
goog.log.fine(goog.net.CrossDomainRpc.logger_, 'response ready');
|
||||
this.responseReady_ = true;
|
||||
}, false, this);
|
||||
|
||||
this.receiveResponse_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* period of response polling (ms)
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_ = 50;
|
||||
|
||||
|
||||
/**
|
||||
* timeout from response comes back to sendResponse is called (ms)
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_ = 500;
|
||||
|
||||
|
||||
/**
|
||||
* Receives response by polling to check readiness of response and then
|
||||
* reads response frames and assembles response data
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.prototype.receiveResponse_ = function() {
|
||||
this.timeWaitedAfterResponseReady_ = 0;
|
||||
var responseDetectorHandle = window.setInterval(goog.bind(function() {
|
||||
this.detectResponse_(responseDetectorHandle);
|
||||
}, this), goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Detects response inside request frame
|
||||
* @param {number} responseDetectorHandle Handle of detector.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.prototype.detectResponse_ =
|
||||
function(responseDetectorHandle) {
|
||||
var requestFrameWindow = this.requestFrame_.contentWindow;
|
||||
var grandChildrenLength = requestFrameWindow.frames.length;
|
||||
var responseInfoFrame = null;
|
||||
if (grandChildrenLength > 0 &&
|
||||
goog.net.CrossDomainRpc.isResponseInfoFrame_(responseInfoFrame =
|
||||
requestFrameWindow.frames[grandChildrenLength - 1])) {
|
||||
goog.log.fine(goog.net.CrossDomainRpc.logger_,
|
||||
'xd response ready');
|
||||
|
||||
var responseInfoPayload = goog.net.CrossDomainRpc.getFramePayload_(
|
||||
responseInfoFrame).substring(1);
|
||||
var params = new goog.Uri.QueryData(responseInfoPayload);
|
||||
|
||||
var chunks = [];
|
||||
var numChunks = Number(params.get('n'));
|
||||
goog.log.fine(goog.net.CrossDomainRpc.logger_,
|
||||
'xd response number of chunks: ' + numChunks);
|
||||
for (var i = 0; i < numChunks; i++) {
|
||||
var responseFrame = requestFrameWindow.frames[i];
|
||||
if (!responseFrame || !responseFrame.location ||
|
||||
!responseFrame.location.href) {
|
||||
// On Safari 3.0, it is sometimes the case that the
|
||||
// iframe exists but doesn't have a same domain href yet.
|
||||
goog.log.fine(goog.net.CrossDomainRpc.logger_,
|
||||
'xd response iframe not ready');
|
||||
return;
|
||||
}
|
||||
var responseChunkPayload =
|
||||
goog.net.CrossDomainRpc.getFramePayload_(responseFrame);
|
||||
// go past "chunk="
|
||||
var chunkIndex = responseChunkPayload.indexOf(
|
||||
goog.net.CrossDomainRpc.PARAM_CHUNK_) +
|
||||
goog.net.CrossDomainRpc.PARAM_CHUNK_.length + 1;
|
||||
var chunk = responseChunkPayload.substring(chunkIndex);
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
window.clearInterval(responseDetectorHandle);
|
||||
|
||||
var responseData = chunks.join('');
|
||||
// Payload is not encoded to begin with on IE. Decode in other cases only.
|
||||
if (!goog.userAgent.IE) {
|
||||
responseData = decodeURIComponent(responseData);
|
||||
}
|
||||
|
||||
this.status = Number(params.get('status'));
|
||||
this.responseText = responseData;
|
||||
this.responseTextIsJson_ = params.get('isDataJson') == 'true';
|
||||
this.responseHeaders = goog.json.unsafeParse(
|
||||
/** @type {string} */ (params.get('headers')));
|
||||
|
||||
this.dispatchEvent(goog.net.EventType.READY);
|
||||
this.dispatchEvent(goog.net.EventType.COMPLETE);
|
||||
} else {
|
||||
if (this.responseReady_) {
|
||||
/* The response has come back. But the first response iframe has not
|
||||
* been created yet. If this lasts long enough, it is an error.
|
||||
*/
|
||||
this.timeWaitedAfterResponseReady_ +=
|
||||
goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_;
|
||||
if (this.timeWaitedAfterResponseReady_ >
|
||||
goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_) {
|
||||
goog.log.fine(goog.net.CrossDomainRpc.logger_,
|
||||
'xd response timed out');
|
||||
window.clearInterval(responseDetectorHandle);
|
||||
|
||||
this.status = goog.net.HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
this.responseText = 'response timed out';
|
||||
|
||||
this.dispatchEvent(goog.net.EventType.READY);
|
||||
this.dispatchEvent(goog.net.EventType.ERROR);
|
||||
this.dispatchEvent(goog.net.EventType.COMPLETE);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a frame is response info frame.
|
||||
* @param {Object} frame Frame to check.
|
||||
* @return {boolean} True if frame is a response info frame; false otherwise.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.isResponseInfoFrame_ = function(frame) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
return goog.net.CrossDomainRpc.getFramePayload_(frame).indexOf(
|
||||
goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_) == 1;
|
||||
} catch (e) {
|
||||
// frame not ready for same-domain access yet
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the payload of a frame (value after # or ? on the URL). This value
|
||||
* is URL encoded except IE, where the value is not encoded to begin with.
|
||||
* @param {Object} frame Frame.
|
||||
* @return {string} Payload of that frame.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.getFramePayload_ = function(frame) {
|
||||
var href = frame.location.href;
|
||||
var question = href.indexOf('?');
|
||||
var hash = href.indexOf('#');
|
||||
// On IE, beucase the URL is not encoded, we can have a case where ?
|
||||
// is the delimiter before payload and # in payload or # as the delimiter
|
||||
// and ? in payload. So here we treat whoever is the first as the delimiter.
|
||||
var delimiter = question < 0 ? hash :
|
||||
hash < 0 ? question : Math.min(question, hash);
|
||||
return href.substring(delimiter);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* If response is JSON, evaluates it to a JavaScript object and
|
||||
* returns it; otherwise returns undefined.
|
||||
* @return {Object|undefined} JavaScript object if response is in JSON
|
||||
* or undefined.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.prototype.getResponseJson = function() {
|
||||
return this.responseTextIsJson_ ?
|
||||
goog.json.unsafeParse(this.responseText) : undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the request completed with a success.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.prototype.isSuccess = function() {
|
||||
// Definition similar to goog.net.XhrIo.prototype.isSuccess.
|
||||
switch (this.status) {
|
||||
case goog.net.HttpStatus.OK:
|
||||
case goog.net.HttpStatus.NOT_MODIFIED:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes request iframe used.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.prototype.reset = function() {
|
||||
if (!goog.net.CrossDomainRpc.debugMode_) {
|
||||
goog.log.fine(goog.net.CrossDomainRpc.logger_,
|
||||
'request frame removed: ' + this.requestFrame_.id);
|
||||
goog.events.unlistenByKey(this.loadListenerKey_);
|
||||
this.requestFrame_.parentNode.removeChild(this.requestFrame_);
|
||||
}
|
||||
delete this.requestFrame_;
|
||||
};
|
||||
|
||||
|
||||
// -------------
|
||||
// response side
|
||||
|
||||
|
||||
/**
|
||||
* Name of response info iframe.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ =
|
||||
goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '-info';
|
||||
|
||||
|
||||
/**
|
||||
* Maximal chunk size. IE can only handle 4095 bytes on its URL.
|
||||
* 16MB has been tested on Firefox. But 1MB is a practical size.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ =
|
||||
goog.userAgent.IE ? 4095 : 1024 * 1024;
|
||||
|
||||
|
||||
/**
|
||||
* Query parameter 'chunk'.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.PARAM_CHUNK_ = 'chunk';
|
||||
|
||||
|
||||
/**
|
||||
* Prefix before data chunk for passing other parameters.
|
||||
* type String
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.CHUNK_PREFIX_ =
|
||||
goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '=1&' +
|
||||
goog.net.CrossDomainRpc.PARAM_CHUNK_ + '=';
|
||||
|
||||
|
||||
/**
|
||||
* Makes response available for grandparent (requester)'s receiveResponse
|
||||
* call to pick up by creating a series of iframes pointed to the dummy URI
|
||||
* with a payload (value after either ? or #) carrying a chunk of response
|
||||
* data and a response info iframe that tells the grandparent (requester) the
|
||||
* readiness of response.
|
||||
* @param {string} data Response data (string or JSON string).
|
||||
* @param {boolean} isDataJson true if data is a JSON string; false if just a
|
||||
* string.
|
||||
* @param {Object} echo Parameters to echo back
|
||||
* "xdpe:request-id": Server that produces the response needs to
|
||||
* copy it here to support multiple current XD requests on the same page.
|
||||
* "xdpe:dummy-uri": URI to a dummy resource that response
|
||||
* iframes point to to gain the domain of the client. This can be an
|
||||
* image (IE) or a CSS file (FF) found on the requester's page.
|
||||
* Server should copy value from request parameter "xdpe:dummy-uri".
|
||||
* @param {number} status HTTP response status code.
|
||||
* @param {string} headers Response headers in JSON format.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.sendResponse =
|
||||
function(data, isDataJson, echo, status, headers) {
|
||||
var dummyUri = echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI];
|
||||
|
||||
// since the dummy-uri can be specified by the user, verify that it doesn't
|
||||
// use any other protocols. (Specifically we don't want users to use a
|
||||
// dummy-uri beginning with "javascript:").
|
||||
if (!goog.string.caseInsensitiveStartsWith(dummyUri, 'http://') &&
|
||||
!goog.string.caseInsensitiveStartsWith(dummyUri, 'https://')) {
|
||||
dummyUri = 'http://' + dummyUri;
|
||||
}
|
||||
|
||||
// usable chunk size is max less dummy URI less chunk prefix length
|
||||
// TODO(user): Figure out why we need to do "- 1" below
|
||||
var chunkSize = goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ - dummyUri.length -
|
||||
1 - // payload delimiter ('#' or '?')
|
||||
goog.net.CrossDomainRpc.CHUNK_PREFIX_.length - 1;
|
||||
|
||||
/*
|
||||
* Here we used to do URI encoding of data before we divide it into chunks
|
||||
* and decode on the receiving end. We don't do this any more on IE for the
|
||||
* following reasons.
|
||||
*
|
||||
* 1) On IE, calling decodeURIComponent on a relatively large string is
|
||||
* extremely slow (~22s for 160KB). So even a moderate amount of data
|
||||
* makes this library pretty much useless. Fortunately, we can actually
|
||||
* put unencoded data on IE's URL and get it back reliably. So we are
|
||||
* completely skipping encoding and decoding on IE. When we call
|
||||
* getFrameHash_ to get it back, the value is still intact(*) and unencoded.
|
||||
* 2) On Firefox, we have to call decodeURIComponent because location.hash
|
||||
* does decoding by itself. Fortunately, decodeURIComponent is not slow
|
||||
* on Firefox.
|
||||
* 3) Safari automatically encodes everything you put on URL and it does not
|
||||
* automatically decode when you access it via location.hash or
|
||||
* location.href. So we encode it here and decode it in detectResponse_().
|
||||
*
|
||||
* Note(*): IE actually does encode only space to %20 and decodes that
|
||||
* automatically when you do location.href or location.hash.
|
||||
*/
|
||||
if (!goog.userAgent.IE) {
|
||||
data = encodeURIComponent(data);
|
||||
}
|
||||
|
||||
var numChunksToSend = Math.ceil(data.length / chunkSize);
|
||||
if (numChunksToSend == 0) {
|
||||
goog.net.CrossDomainRpc.createResponseInfo_(
|
||||
dummyUri, numChunksToSend, isDataJson, status, headers);
|
||||
} else {
|
||||
var numChunksSent = 0;
|
||||
var checkToCreateResponseInfo_ = function() {
|
||||
if (++numChunksSent == numChunksToSend) {
|
||||
goog.net.CrossDomainRpc.createResponseInfo_(
|
||||
dummyUri, numChunksToSend, isDataJson, status, headers);
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < numChunksToSend; i++) {
|
||||
var chunkStart = i * chunkSize;
|
||||
var chunkEnd = chunkStart + chunkSize;
|
||||
var chunk = chunkEnd > data.length ?
|
||||
data.substring(chunkStart) :
|
||||
data.substring(chunkStart, chunkEnd);
|
||||
|
||||
var responseFrame = document.createElement('iframe');
|
||||
responseFrame.src = dummyUri +
|
||||
goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +
|
||||
goog.net.CrossDomainRpc.CHUNK_PREFIX_ + chunk;
|
||||
document.body.appendChild(responseFrame);
|
||||
|
||||
// We used to call the function below when handling load event of
|
||||
// responseFrame. But that event does not fire on IE when current
|
||||
// page is used as the dummy resource (because its loading is stopped?).
|
||||
// It also does not fire sometimes on Firefox. So now we call it
|
||||
// directly.
|
||||
checkToCreateResponseInfo_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a response info iframe to indicate completion of sendResponse
|
||||
* @param {string} dummyUri URI to a dummy resource.
|
||||
* @param {number} numChunks Total number of chunks.
|
||||
* @param {boolean} isDataJson Whether response is a JSON string or just string.
|
||||
* @param {number} status HTTP response status code.
|
||||
* @param {string} headers Response headers in JSON format.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.createResponseInfo_ =
|
||||
function(dummyUri, numChunks, isDataJson, status, headers) {
|
||||
var responseInfoFrame = document.createElement('iframe');
|
||||
document.body.appendChild(responseInfoFrame);
|
||||
responseInfoFrame.src = dummyUri +
|
||||
goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +
|
||||
goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ +
|
||||
'=1&n=' + numChunks + '&isDataJson=' + isDataJson + '&status=' + status +
|
||||
'&headers=' + encodeURIComponent(headers);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns payload delimiter, either "#" when caller's page is not used as
|
||||
* the dummy resource or "?" when it is, in which case caching issues prevent
|
||||
* response frames to gain the caller's domain.
|
||||
* @param {string} dummyUri URI to resource being used as dummy resource.
|
||||
* @return {string} Either "?" when caller's page is used as dummy resource or
|
||||
* "#" if it is not.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.getPayloadDelimiter_ = function(dummyUri) {
|
||||
return goog.net.CrossDomainRpc.REFERRER_ == dummyUri ? '?' : '#';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parameters (after ? or #) from URI.
|
||||
* @param {string} uri URI to remove parameters from.
|
||||
* @return {string} URI with all parameters removed.
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.removeUriParams_ = function(uri) {
|
||||
// remove everything after question mark
|
||||
var question = uri.indexOf('?');
|
||||
if (question > 0) {
|
||||
uri = uri.substring(0, question);
|
||||
}
|
||||
|
||||
// remove everything after hash mark
|
||||
var hash = uri.indexOf('#');
|
||||
if (hash > 0) {
|
||||
uri = uri.substring(0, hash);
|
||||
}
|
||||
|
||||
return uri;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a response header.
|
||||
* @param {string} name Name of response header.
|
||||
* @return {string|undefined} Value of response header; undefined if not found.
|
||||
*/
|
||||
goog.net.CrossDomainRpc.prototype.getResponseHeader = function(name) {
|
||||
return goog.isObject(this.responseHeaders) ?
|
||||
this.responseHeaders[name] : undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Referrer of current document with all parameters after "?" and "#" stripped.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.CrossDomainRpc.REFERRER_ =
|
||||
goog.net.CrossDomainRpc.removeUriParams_(document.referrer);
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2007 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 Error codes shared between goog.net.IframeIo and
|
||||
* goog.net.XhrIo.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.ErrorCode');
|
||||
|
||||
|
||||
/**
|
||||
* Error codes
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.ErrorCode = {
|
||||
|
||||
/**
|
||||
* There is no error condition.
|
||||
*/
|
||||
NO_ERROR: 0,
|
||||
|
||||
/**
|
||||
* The most common error from iframeio, unfortunately, is that the browser
|
||||
* responded with an error page that is classed as a different domain. The
|
||||
* situations, are when a browser error page is shown -- 404, access denied,
|
||||
* DNS failure, connection reset etc.)
|
||||
*
|
||||
*/
|
||||
ACCESS_DENIED: 1,
|
||||
|
||||
/**
|
||||
* Currently the only case where file not found will be caused is when the
|
||||
* code is running on the local file system and a non-IE browser makes a
|
||||
* request to a file that doesn't exist.
|
||||
*/
|
||||
FILE_NOT_FOUND: 2,
|
||||
|
||||
/**
|
||||
* If Firefox shows a browser error page, such as a connection reset by
|
||||
* server or access denied, then it will fail silently without the error or
|
||||
* load handlers firing.
|
||||
*/
|
||||
FF_SILENT_ERROR: 3,
|
||||
|
||||
/**
|
||||
* Custom error provided by the client through the error check hook.
|
||||
*/
|
||||
CUSTOM_ERROR: 4,
|
||||
|
||||
/**
|
||||
* Exception was thrown while processing the request.
|
||||
*/
|
||||
EXCEPTION: 5,
|
||||
|
||||
/**
|
||||
* The Http response returned a non-successful http status code.
|
||||
*/
|
||||
HTTP_ERROR: 6,
|
||||
|
||||
/**
|
||||
* The request was aborted.
|
||||
*/
|
||||
ABORT: 7,
|
||||
|
||||
/**
|
||||
* The request timed out.
|
||||
*/
|
||||
TIMEOUT: 8,
|
||||
|
||||
/**
|
||||
* The resource is not available offline.
|
||||
*/
|
||||
OFFLINE: 9
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a friendly error message for an error code. These messages are for
|
||||
* debugging and are not localized.
|
||||
* @param {goog.net.ErrorCode} errorCode An error code.
|
||||
* @return {string} A message for debugging.
|
||||
*/
|
||||
goog.net.ErrorCode.getDebugMessage = function(errorCode) {
|
||||
switch (errorCode) {
|
||||
case goog.net.ErrorCode.NO_ERROR:
|
||||
return 'No Error';
|
||||
|
||||
case goog.net.ErrorCode.ACCESS_DENIED:
|
||||
return 'Access denied to content document';
|
||||
|
||||
case goog.net.ErrorCode.FILE_NOT_FOUND:
|
||||
return 'File not found';
|
||||
|
||||
case goog.net.ErrorCode.FF_SILENT_ERROR:
|
||||
return 'Firefox silently errored';
|
||||
|
||||
case goog.net.ErrorCode.CUSTOM_ERROR:
|
||||
return 'Application custom error';
|
||||
|
||||
case goog.net.ErrorCode.EXCEPTION:
|
||||
return 'An exception occurred';
|
||||
|
||||
case goog.net.ErrorCode.HTTP_ERROR:
|
||||
return 'Http response at 400 or 500 level';
|
||||
|
||||
case goog.net.ErrorCode.ABORT:
|
||||
return 'Request was aborted';
|
||||
|
||||
case goog.net.ErrorCode.TIMEOUT:
|
||||
return 'Request timed out';
|
||||
|
||||
case goog.net.ErrorCode.OFFLINE:
|
||||
return 'The resource is not available offline';
|
||||
|
||||
default:
|
||||
return 'Unrecognized error code';
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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 Common events for the network classes.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.EventType');
|
||||
|
||||
|
||||
/**
|
||||
* Event names for network events
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.net.EventType = {
|
||||
COMPLETE: 'complete',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error',
|
||||
ABORT: 'abort',
|
||||
READY: 'ready',
|
||||
READY_STATE_CHANGE: 'readystatechange',
|
||||
TIMEOUT: 'timeout',
|
||||
INCREMENTAL_DATA: 'incrementaldata',
|
||||
PROGRESS: 'progress'
|
||||
};
|
||||
@@ -0,0 +1,743 @@
|
||||
// Copyright 2011 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 class for downloading remote files and storing them
|
||||
* locally using the HTML5 FileSystem API.
|
||||
*
|
||||
* The directory structure is of the form /HASH/URL/BASENAME:
|
||||
*
|
||||
* The HASH portion is a three-character slice of the hash of the URL. Since the
|
||||
* filesystem has a limit of about 5000 files per directory, this should divide
|
||||
* the downloads roughly evenly among about 5000 directories, thus allowing for
|
||||
* at most 5000^2 downloads.
|
||||
*
|
||||
* The URL portion is the (sanitized) full URL used for downloading the file.
|
||||
* This is used to ensure that each file ends up in a different location, even
|
||||
* if the HASH and BASENAME are the same.
|
||||
*
|
||||
* The BASENAME portion is the basename of the URL. It's used for the filename
|
||||
* proper so that the local filesystem: URL will be downloaded to a file with a
|
||||
* recognizable name.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.FileDownloader');
|
||||
goog.provide('goog.net.FileDownloader.Error');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.crypt.hash32');
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.fs');
|
||||
goog.require('goog.fs.DirectoryEntry');
|
||||
goog.require('goog.fs.Error');
|
||||
goog.require('goog.fs.FileSaver');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.net.XhrIoPool');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for downloading remote files and storing them locally using the
|
||||
* HTML5 filesystem API.
|
||||
*
|
||||
* @param {!goog.fs.DirectoryEntry} dir The directory in which the downloaded
|
||||
* files are stored. This directory should be solely managed by
|
||||
* FileDownloader.
|
||||
* @param {goog.net.XhrIoPool=} opt_pool The pool of XhrIo objects to use for
|
||||
* downloading files.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.net.FileDownloader = function(dir, opt_pool) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* The directory in which the downloaded files are stored.
|
||||
* @type {!goog.fs.DirectoryEntry}
|
||||
* @private
|
||||
*/
|
||||
this.dir_ = dir;
|
||||
|
||||
/**
|
||||
* The pool of XHRs to use for capturing.
|
||||
* @type {!goog.net.XhrIoPool}
|
||||
* @private
|
||||
*/
|
||||
this.pool_ = opt_pool || new goog.net.XhrIoPool();
|
||||
|
||||
/**
|
||||
* A map from URLs to active downloads running for those URLs.
|
||||
* @type {!Object.<!goog.net.FileDownloader.Download_>}
|
||||
* @private
|
||||
*/
|
||||
this.downloads_ = {};
|
||||
|
||||
/**
|
||||
* The handler for URL capturing events.
|
||||
* @type {!goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
};
|
||||
goog.inherits(goog.net.FileDownloader, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Download a remote file and save its contents to the filesystem. A given file
|
||||
* is uniquely identified by its URL string; this means that the relative and
|
||||
* absolute URLs for a single file are considered different for the purposes of
|
||||
* the FileDownloader.
|
||||
*
|
||||
* Returns a Deferred that will contain the downloaded blob. If there's an error
|
||||
* while downloading the URL, this Deferred will be passed the
|
||||
* {@link goog.net.FileDownloader.Error} object as an errback.
|
||||
*
|
||||
* If a download is already in progress for the given URL, this will return the
|
||||
* deferred blob for that download. If the URL has already been downloaded, this
|
||||
* will fail once it tries to save the downloaded blob.
|
||||
*
|
||||
* When a download is in progress, all Deferreds returned for that download will
|
||||
* be branches of a single parent. If all such branches are cancelled, or if one
|
||||
* is cancelled with opt_deepCancel set, then the download will be cancelled as
|
||||
* well.
|
||||
*
|
||||
* @param {string} url The URL of the file to download.
|
||||
* @return {!goog.async.Deferred} The deferred result blob.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.download = function(url) {
|
||||
if (this.isDownloading(url)) {
|
||||
return this.downloads_[url].deferred.branch(true /* opt_propagateCancel */);
|
||||
}
|
||||
|
||||
var download = new goog.net.FileDownloader.Download_(url, this);
|
||||
this.downloads_[url] = download;
|
||||
this.pool_.getObject(goog.bind(this.gotXhr_, this, download));
|
||||
return download.deferred.branch(true /* opt_propagateCancel */);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return a Deferred that will fire once no download is active for a given URL.
|
||||
* If there's no download active for that URL when this is called, the deferred
|
||||
* will fire immediately; otherwise, it will fire once the download is complete,
|
||||
* whether or not it succeeds.
|
||||
*
|
||||
* @param {string} url The URL of the download to wait for.
|
||||
* @return {!goog.async.Deferred} The Deferred that will fire when the download
|
||||
* is complete.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.waitForDownload = function(url) {
|
||||
var deferred = new goog.async.Deferred();
|
||||
if (this.isDownloading(url)) {
|
||||
this.downloads_[url].deferred.addBoth(function() {
|
||||
deferred.callback(null);
|
||||
}, this);
|
||||
} else {
|
||||
deferred.callback(null);
|
||||
}
|
||||
return deferred;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not there is an active download for a given URL.
|
||||
*
|
||||
* @param {string} url The URL of the download to check.
|
||||
* @return {boolean} Whether or not there is an active download for the URL.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.isDownloading = function(url) {
|
||||
return url in this.downloads_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load a downloaded blob from the filesystem. Will fire a deferred error if the
|
||||
* given URL has not yet been downloaded.
|
||||
*
|
||||
* @param {string} url The URL of the blob to load.
|
||||
* @return {!goog.async.Deferred} The deferred Blob object. The callback will be
|
||||
* passed the blob. If a file API error occurs while loading the blob, that
|
||||
* error will be passed to the errback.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.getDownloadedBlob = function(url) {
|
||||
return this.getFile_(url).
|
||||
addCallback(function(fileEntry) { return fileEntry.file(); });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the local filesystem: URL for a downloaded file. This is different from
|
||||
* the blob: URL that's available from getDownloadedBlob(). If the end user
|
||||
* accesses the filesystem: URL, the resulting file's name will be determined by
|
||||
* the download filename as opposed to an arbitrary GUID. In addition, the
|
||||
* filesystem: URL is connected to a filesystem location, so if the download is
|
||||
* removed then that URL will become invalid.
|
||||
*
|
||||
* Warning: in Chrome 12, some filesystem: URLs are opened inline. This means
|
||||
* that e.g. HTML pages given to the user via filesystem: URLs will be opened
|
||||
* and processed by the browser.
|
||||
*
|
||||
* @param {string} url The URL of the file to get the URL of.
|
||||
* @return {!goog.async.Deferred} The deferred filesystem: URL. The callback
|
||||
* will be passed the URL. If a file API error occurs while loading the
|
||||
* blob, that error will be passed to the errback.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.getLocalUrl = function(url) {
|
||||
return this.getFile_(url).
|
||||
addCallback(function(fileEntry) { return fileEntry.toUrl(); });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return (deferred) whether or not a URL has been downloaded. Will fire a
|
||||
* deferred error if something goes wrong when determining this.
|
||||
*
|
||||
* @param {string} url The URL to check.
|
||||
* @return {!goog.async.Deferred} The deferred boolean. The callback will be
|
||||
* passed the boolean. If a file API error occurs while checking the
|
||||
* existence of the downloaded URL, that error will be passed to the
|
||||
* errback.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.isDownloaded = function(url) {
|
||||
var deferred = new goog.async.Deferred();
|
||||
var blobDeferred = this.getDownloadedBlob(url);
|
||||
blobDeferred.addCallback(function() {
|
||||
deferred.callback(true);
|
||||
});
|
||||
blobDeferred.addErrback(function(err) {
|
||||
if (err.code == goog.fs.Error.ErrorCode.NOT_FOUND) {
|
||||
deferred.callback(false);
|
||||
} else {
|
||||
deferred.errback(err);
|
||||
}
|
||||
});
|
||||
return deferred;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove a URL from the FileDownloader.
|
||||
*
|
||||
* This returns a Deferred. If the removal is completed successfully, its
|
||||
* callback will be called without any value. If the removal fails, its errback
|
||||
* will be called with the {@link goog.fs.Error}.
|
||||
*
|
||||
* @param {string} url The URL to remove.
|
||||
* @return {!goog.async.Deferred} The deferred used for registering callbacks on
|
||||
* success or on error.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.remove = function(url) {
|
||||
return this.getDir_(url, goog.fs.DirectoryEntry.Behavior.DEFAULT).
|
||||
addCallback(function(dir) { return dir.removeRecursively(); });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save a blob for a given URL. This works just as through the blob were
|
||||
* downloaded form that URL, except you specify the blob and no HTTP request is
|
||||
* made.
|
||||
*
|
||||
* If the URL is currently being downloaded, it's indeterminate whether the blob
|
||||
* being set or the blob being downloaded will end up in the filesystem.
|
||||
* Whichever one doesn't get saved will have an error. To ensure that one or the
|
||||
* other takes precedence, use {@link #waitForDownload} to allow the download to
|
||||
* complete before setting the blob.
|
||||
*
|
||||
* @param {string} url The URL at which to set the blob.
|
||||
* @param {!Blob} blob The blob to set.
|
||||
* @param {string=} opt_name The name of the file. If this isn't given, it's
|
||||
* determined from the URL.
|
||||
* @return {!goog.async.Deferred} The deferred used for registering callbacks on
|
||||
* success or on error. This can be cancelled just like a {@link #download}
|
||||
* Deferred. The objects passed to the errback will be
|
||||
* {@link goog.net.FileDownloader.Error}s.
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.setBlob = function(url, blob, opt_name) {
|
||||
var name = this.sanitize_(opt_name || this.urlToName_(url));
|
||||
var download = new goog.net.FileDownloader.Download_(url, this);
|
||||
this.downloads_[url] = download;
|
||||
download.blob = blob;
|
||||
this.getDir_(download.url, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE).
|
||||
addCallback(function(dir) {
|
||||
return dir.getFile(
|
||||
name, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE);
|
||||
}).
|
||||
addCallback(goog.bind(this.fileSuccess_, this, download)).
|
||||
addErrback(goog.bind(this.error_, this, download));
|
||||
return download.deferred.branch(true /* opt_propagateCancel */);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The callback called when an XHR becomes available from the XHR pool.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download object for
|
||||
* this download.
|
||||
* @param {!goog.net.XhrIo} xhr The XhrIo object for downloading the page.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.gotXhr_ = function(download, xhr) {
|
||||
if (download.cancelled) {
|
||||
this.freeXhr_(xhr);
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventHandler_.listen(
|
||||
xhr, goog.net.EventType.SUCCESS,
|
||||
goog.bind(this.xhrSuccess_, this, download));
|
||||
this.eventHandler_.listen(
|
||||
xhr, [goog.net.EventType.ERROR, goog.net.EventType.ABORT],
|
||||
goog.bind(this.error_, this, download));
|
||||
this.eventHandler_.listen(
|
||||
xhr, goog.net.EventType.READY,
|
||||
goog.bind(this.freeXhr_, this, xhr));
|
||||
|
||||
download.xhr = xhr;
|
||||
xhr.setResponseType(goog.net.XhrIo.ResponseType.ARRAY_BUFFER);
|
||||
xhr.send(download.url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The callback called when an XHR succeeds in downloading a remote file.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download object for
|
||||
* this download.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.xhrSuccess_ = function(download) {
|
||||
if (download.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = this.sanitize_(this.getName_(
|
||||
/** @type {!goog.net.XhrIo} */ (download.xhr)));
|
||||
var resp = /** @type {ArrayBuffer} */ (download.xhr.getResponse());
|
||||
if (!resp) {
|
||||
// This should never happen - it indicates the XHR hasn't completed, has
|
||||
// failed or has been cleaned up. If it does happen (eg. due to a bug
|
||||
// somewhere) we don't want to pass null to getBlob - it's not valid and
|
||||
// triggers a bug in some versions of WebKit causing it to crash.
|
||||
this.error_(download);
|
||||
return;
|
||||
}
|
||||
|
||||
download.blob = goog.fs.getBlob(resp);
|
||||
delete download.xhr;
|
||||
|
||||
this.getDir_(download.url, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE).
|
||||
addCallback(function(dir) {
|
||||
return dir.getFile(
|
||||
name, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE);
|
||||
}).
|
||||
addCallback(goog.bind(this.fileSuccess_, this, download)).
|
||||
addErrback(goog.bind(this.error_, this, download));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The callback called when a file that will be used for saving a file is
|
||||
* successfully opened.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download object for
|
||||
* this download.
|
||||
* @param {!goog.fs.FileEntry} file The newly-opened file object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.fileSuccess_ = function(download, file) {
|
||||
if (download.cancelled) {
|
||||
file.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
download.file = file;
|
||||
file.createWriter().
|
||||
addCallback(goog.bind(this.fileWriterSuccess_, this, download)).
|
||||
addErrback(goog.bind(this.error_, this, download));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The callback called when a file writer is succesfully created for writing a
|
||||
* file to the filesystem.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download object for
|
||||
* this download.
|
||||
* @param {!goog.fs.FileWriter} writer The newly-created file writer object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.fileWriterSuccess_ = function(
|
||||
download, writer) {
|
||||
if (download.cancelled) {
|
||||
download.file.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
download.writer = writer;
|
||||
writer.write(/** @type {!Blob} */ (download.blob));
|
||||
this.eventHandler_.listenOnce(
|
||||
writer,
|
||||
goog.fs.FileSaver.EventType.WRITE_END,
|
||||
goog.bind(this.writeEnd_, this, download));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The callback called when file writing ends, whether or not it's successful.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download object for
|
||||
* this download.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.writeEnd_ = function(download) {
|
||||
if (download.cancelled || download.writer.getError()) {
|
||||
this.error_(download, download.writer.getError());
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.downloads_[download.url];
|
||||
download.deferred.callback(download.blob);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The error callback for all asynchronous operations. Ensures that all stages
|
||||
* of a given download are cleaned up, and emits the error event.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download object for
|
||||
* this download.
|
||||
* @param {goog.fs.Error=} opt_err The file error object. Only defined if the
|
||||
* error was raised by the file API.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.error_ = function(download, opt_err) {
|
||||
if (download.file) {
|
||||
download.file.remove();
|
||||
}
|
||||
|
||||
if (download.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.downloads_[download.url];
|
||||
download.deferred.errback(
|
||||
new goog.net.FileDownloader.Error(download, opt_err));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Abort the download of the given URL.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download to abort.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.cancel_ = function(download) {
|
||||
goog.dispose(download);
|
||||
delete this.downloads_[download.url];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the directory for a given URL. If the directory already exists when this
|
||||
* is called, it will contain exactly one file: the downloaded file.
|
||||
*
|
||||
* This not only calls the FileSystem API's getFile method, but attempts to
|
||||
* distribute the files so that they don't overload the filesystem. The spec
|
||||
* says directories can't contain more than 5000 files
|
||||
* (http://www.w3.org/TR/file-system-api/#directories), so this ensures that
|
||||
* each file is put into a subdirectory based on its SHA1 hash.
|
||||
*
|
||||
* All parameters are the same as in the FileSystem API's Entry#getFile method.
|
||||
*
|
||||
* @param {string} url The URL corresponding to the directory to get.
|
||||
* @param {goog.fs.DirectoryEntry.Behavior} behavior The behavior to pass to the
|
||||
* underlying method.
|
||||
* @return {!goog.async.Deferred} The deferred DirectoryEntry object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.getDir_ = function(url, behavior) {
|
||||
// 3 hex digits provide 16**3 = 4096 different possible dirnames, which is
|
||||
// less than the maximum of 5000 entries. Downloaded files should be
|
||||
// distributed roughly evenly throughout the directories due to the hash
|
||||
// function, allowing many more than 5000 files to be downloaded.
|
||||
//
|
||||
// The leading ` ensures that no illegal dirnames are accidentally used. % was
|
||||
// previously used, but Chrome has a bug (as of 12.0.725.0 dev) where
|
||||
// filenames are URL-decoded before checking their validity, so filenames
|
||||
// containing e.g. '%3f' (the URL-encoding of :, an invalid character) are
|
||||
// rejected.
|
||||
var dirname = '`' + Math.abs(goog.crypt.hash32.encodeString(url)).
|
||||
toString(16).substring(0, 3);
|
||||
|
||||
return this.dir_.
|
||||
getDirectory(dirname, goog.fs.DirectoryEntry.Behavior.CREATE).
|
||||
addCallback(function(dir) {
|
||||
return dir.getDirectory(this.sanitize_(url), behavior);
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the file for a given URL. This will only retrieve files that have already
|
||||
* been saved; it shouldn't be used for creating the file in the first place.
|
||||
* This is because the filename isn't necessarily determined by the URL, but by
|
||||
* the headers of the XHR response.
|
||||
*
|
||||
* @param {string} url The URL corresponding to the file to get.
|
||||
* @return {!goog.async.Deferred} The deferred FileEntry object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.getFile_ = function(url) {
|
||||
return this.getDir_(url, goog.fs.DirectoryEntry.Behavior.DEFAULT).
|
||||
addCallback(function(dir) {
|
||||
return dir.listDirectory().addCallback(function(files) {
|
||||
goog.asserts.assert(files.length == 1);
|
||||
// If the filesystem somehow gets corrupted and we end up with an
|
||||
// empty directory here, it makes sense to just return the normal
|
||||
// file-not-found error.
|
||||
return files[0] || dir.getFile('file');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sanitize a string so it can be safely used as a file or directory name for
|
||||
* the FileSystem API.
|
||||
*
|
||||
* @param {string} str The string to sanitize.
|
||||
* @return {string} The sanitized string.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.sanitize_ = function(str) {
|
||||
// Add a prefix, since certain prefixes are disallowed for paths. None of the
|
||||
// disallowed prefixes start with '`'. We use ` rather than % for escaping the
|
||||
// filename due to a Chrome bug (as of 12.0.725.0 dev) where filenames are
|
||||
// URL-decoded before checking their validity, so filenames containing e.g.
|
||||
// '%3f' (the URL-encoding of :, an invalid character) are rejected.
|
||||
return '`' + str.replace(/[\/\\<>:?*"|%`]/g, encodeURIComponent).
|
||||
replace(/%/g, '`');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the filename specified by the XHR. This first attempts to parse the
|
||||
* Content-Disposition header for a filename and, failing that, falls back on
|
||||
* deriving the filename from the URL.
|
||||
*
|
||||
* @param {!goog.net.XhrIo} xhr The XHR containing the response headers.
|
||||
* @return {string} The filename.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.getName_ = function(xhr) {
|
||||
var disposition = xhr.getResponseHeader('Content-Disposition');
|
||||
var match = disposition &&
|
||||
disposition.match(/^attachment *; *filename="(.*)"$/i);
|
||||
if (match) {
|
||||
// The Content-Disposition header allows for arbitrary backslash-escaped
|
||||
// characters (usually " and \). We want to unescape them before using them
|
||||
// in the filename.
|
||||
return match[1].replace(/\\(.)/g, '$1');
|
||||
}
|
||||
|
||||
return this.urlToName_(xhr.getLastUri());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the basename from a URL.
|
||||
*
|
||||
* @param {string} url The URL.
|
||||
* @return {string} The basename.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.urlToName_ = function(url) {
|
||||
var segments = url.split('/');
|
||||
return segments[segments.length - 1];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove all event listeners for an XHR and release it back into the pool.
|
||||
*
|
||||
* @param {!goog.net.XhrIo} xhr The XHR to free.
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.prototype.freeXhr_ = function(xhr) {
|
||||
goog.events.removeAll(xhr);
|
||||
this.pool_.addFreeObject(xhr);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.FileDownloader.prototype.disposeInternal = function() {
|
||||
delete this.dir_;
|
||||
goog.dispose(this.eventHandler_);
|
||||
delete this.eventHandler_;
|
||||
goog.object.forEach(this.downloads_, function(download) {
|
||||
download.deferred.cancel();
|
||||
}, this);
|
||||
delete this.downloads_;
|
||||
goog.dispose(this.pool_);
|
||||
delete this.pool_;
|
||||
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The error object for FileDownloader download errors.
|
||||
*
|
||||
* @param {!goog.net.FileDownloader.Download_} download The download object for
|
||||
* the download in question.
|
||||
* @param {goog.fs.Error=} opt_fsErr The file error object, if this was a file
|
||||
* error.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.net.FileDownloader.Error = function(download, opt_fsErr) {
|
||||
goog.base(this, 'Error capturing URL ' + download.url);
|
||||
|
||||
/**
|
||||
* The URL the event relates to.
|
||||
* @type {string}
|
||||
*/
|
||||
this.url = download.url;
|
||||
|
||||
if (download.xhr) {
|
||||
this.xhrStatus = download.xhr.getStatus();
|
||||
this.xhrErrorCode = download.xhr.getLastErrorCode();
|
||||
this.message += ': XHR failed with status ' + this.xhrStatus +
|
||||
' (error code ' + this.xhrErrorCode + ')';
|
||||
} else if (opt_fsErr) {
|
||||
this.fileError = opt_fsErr;
|
||||
this.message += ': file API failed (' + opt_fsErr.message + ')';
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.net.FileDownloader.Error, goog.debug.Error);
|
||||
|
||||
|
||||
/**
|
||||
* The status of the XHR. Only set if the error was caused by an XHR failure.
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
goog.net.FileDownloader.Error.prototype.xhrStatus;
|
||||
|
||||
|
||||
/**
|
||||
* The error code of the XHR. Only set if the error was caused by an XHR
|
||||
* failure.
|
||||
* @type {goog.net.ErrorCode|undefined}
|
||||
*/
|
||||
goog.net.FileDownloader.Error.prototype.xhrErrorCode;
|
||||
|
||||
|
||||
/**
|
||||
* The file API error. Only set if the error was caused by the file API.
|
||||
* @type {goog.fs.Error|undefined}
|
||||
*/
|
||||
goog.net.FileDownloader.Error.prototype.fileError;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A struct containing the data for a single download.
|
||||
*
|
||||
* @param {string} url The URL for the file being downloaded.
|
||||
* @param {!goog.net.FileDownloader} downloader The parent FileDownloader.
|
||||
* @extends {goog.Disposable}
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.net.FileDownloader.Download_ = function(url, downloader) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* The URL for the file being downloaded.
|
||||
* @type {string}
|
||||
*/
|
||||
this.url = url;
|
||||
|
||||
/**
|
||||
* The Deferred that will be fired when the download is complete.
|
||||
* @type {!goog.async.Deferred}
|
||||
*/
|
||||
this.deferred = new goog.async.Deferred(
|
||||
goog.bind(downloader.cancel_, downloader, this));
|
||||
|
||||
/**
|
||||
* Whether this download has been cancelled by the user.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.cancelled = false;
|
||||
|
||||
/**
|
||||
* The XhrIo object for downloading the file. Only set once it's been
|
||||
* retrieved from the pool.
|
||||
* @type {goog.net.XhrIo}
|
||||
*/
|
||||
this.xhr = null;
|
||||
|
||||
/**
|
||||
* The name of the blob being downloaded. Only sey once the XHR has completed,
|
||||
* if it completed successfully.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.name = null;
|
||||
|
||||
/**
|
||||
* The downloaded blob. Only set once the XHR has completed, if it completed
|
||||
* successfully.
|
||||
* @type {Blob}
|
||||
*/
|
||||
this.blob = null;
|
||||
|
||||
/**
|
||||
* The file entry where the blob is to be stored. Only set once it's been
|
||||
* loaded from the filesystem.
|
||||
* @type {goog.fs.FileEntry}
|
||||
*/
|
||||
this.file = null;
|
||||
|
||||
/**
|
||||
* The file writer for writing the blob to the filesystem. Only set once it's
|
||||
* been loaded from the filesystem.
|
||||
* @type {goog.fs.FileWriter}
|
||||
*/
|
||||
this.writer = null;
|
||||
};
|
||||
goog.inherits(goog.net.FileDownloader.Download_, goog.Disposable);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.FileDownloader.Download_.prototype.disposeInternal = function() {
|
||||
this.cancelled = true;
|
||||
if (this.xhr) {
|
||||
this.xhr.abort();
|
||||
} else if (this.writer && this.writer.getReadyState() ==
|
||||
goog.fs.FileSaver.ReadyState.WRITING) {
|
||||
this.writer.abort();
|
||||
}
|
||||
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2011 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 Constants for HTTP status codes.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.HttpStatus');
|
||||
|
||||
|
||||
/**
|
||||
* HTTP Status Codes defined in RFC 2616.
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.HttpStatus = {
|
||||
// Informational 1xx
|
||||
CONTINUE: 100,
|
||||
SWITCHING_PROTOCOLS: 101,
|
||||
|
||||
// Successful 2xx
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
ACCEPTED: 202,
|
||||
NON_AUTHORITATIVE_INFORMATION: 203,
|
||||
NO_CONTENT: 204,
|
||||
RESET_CONTENT: 205,
|
||||
PARTIAL_CONTENT: 206,
|
||||
|
||||
// Redirection 3xx
|
||||
MULTIPLE_CHOICES: 300,
|
||||
MOVED_PERMANENTLY: 301,
|
||||
FOUND: 302,
|
||||
SEE_OTHER: 303,
|
||||
NOT_MODIFIED: 304,
|
||||
USE_PROXY: 305,
|
||||
TEMPORARY_REDIRECT: 307,
|
||||
|
||||
// Client Error 4xx
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
PAYMENT_REQUIRED: 402,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
METHOD_NOT_ALLOWED: 405,
|
||||
NOT_ACCEPTABLE: 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED: 407,
|
||||
REQUEST_TIMEOUT: 408,
|
||||
CONFLICT: 409,
|
||||
GONE: 410,
|
||||
LENGTH_REQUIRED: 411,
|
||||
PRECONDITION_FAILED: 412,
|
||||
REQUEST_ENTITY_TOO_LARGE: 413,
|
||||
REQUEST_URI_TOO_LONG: 414,
|
||||
UNSUPPORTED_MEDIA_TYPE: 415,
|
||||
REQUEST_RANGE_NOT_SATISFIABLE: 416,
|
||||
EXPECTATION_FAILED: 417,
|
||||
|
||||
// Server Error 5xx
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
NOT_IMPLEMENTED: 501,
|
||||
BAD_GATEWAY: 502,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
GATEWAY_TIMEOUT: 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED: 505,
|
||||
|
||||
/*
|
||||
* IE returns this code for 204 due to its use of URLMon, which returns this
|
||||
* code for 'Operation Aborted'. The status text is 'Unknown', the response
|
||||
* headers are ''. Known to occur on IE 6 on XP through IE9 on Win7.
|
||||
*/
|
||||
QUIRK_IE_NO_CONTENT: 1223
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given status should be considered successful.
|
||||
*
|
||||
* Successful codes are OK (200), CREATED (201), ACCEPTED (202),
|
||||
* NO CONTENT (204), PARTIAL CONTENT (206), NOT MODIFIED (304),
|
||||
* and IE's no content code (1223).
|
||||
*
|
||||
* @param {number} status The status code to test.
|
||||
* @return {boolean} Whether the status code should be considered successful.
|
||||
*/
|
||||
goog.net.HttpStatus.isSuccess = function(status) {
|
||||
switch (status) {
|
||||
case goog.net.HttpStatus.OK:
|
||||
case goog.net.HttpStatus.CREATED:
|
||||
case goog.net.HttpStatus.ACCEPTED:
|
||||
case goog.net.HttpStatus.NO_CONTENT:
|
||||
case goog.net.HttpStatus.PARTIAL_CONTENT:
|
||||
case goog.net.HttpStatus.NOT_MODIFIED:
|
||||
case goog.net.HttpStatus.QUIRK_IE_NO_CONTENT:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,200 @@
|
||||
// Copyright 2008 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 Class that can be used to determine when an iframe is loaded.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.IframeLoadMonitor');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The correct way to determine whether an iframe has completed loading
|
||||
* is different in IE and Firefox. This class abstracts above these
|
||||
* differences, providing a consistent interface for:
|
||||
* <ol>
|
||||
* <li> Determing if an iframe is currently loaded
|
||||
* <li> Listening for an iframe that is not currently loaded, to finish loading
|
||||
* </ol>
|
||||
*
|
||||
* @param {HTMLIFrameElement} iframe An iframe.
|
||||
* @param {boolean=} opt_hasContent Does the loaded iframe have content.
|
||||
* @extends {goog.events.EventTarget}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.IframeLoadMonitor = function(iframe, opt_hasContent) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Iframe whose load state is monitored by this IframeLoadMonitor
|
||||
* @type {HTMLIFrameElement}
|
||||
* @private
|
||||
*/
|
||||
this.iframe_ = iframe;
|
||||
|
||||
/**
|
||||
* Whether or not the loaded iframe has any content.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hasContent_ = !!opt_hasContent;
|
||||
|
||||
/**
|
||||
* Whether or not the iframe is loaded.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isLoaded_ = this.isLoadedHelper_();
|
||||
|
||||
if (!this.isLoaded_) {
|
||||
// IE 6 (and lower?) does not reliably fire load events, so listen to
|
||||
// readystatechange.
|
||||
// IE 7 does not reliably fire readystatechange events but listening on load
|
||||
// seems to work just fine.
|
||||
var isIe6OrLess =
|
||||
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('7');
|
||||
var loadEvtType = isIe6OrLess ?
|
||||
goog.events.EventType.READYSTATECHANGE : goog.events.EventType.LOAD;
|
||||
this.onloadListenerKey_ = goog.events.listen(
|
||||
this.iframe_, loadEvtType, this.handleLoad_, false, this);
|
||||
|
||||
// Sometimes we still don't get the event callback, so we'll poll just to
|
||||
// be safe.
|
||||
this.intervalId_ = window.setInterval(
|
||||
goog.bind(this.handleLoad_, this),
|
||||
goog.net.IframeLoadMonitor.POLL_INTERVAL_MS_);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.net.IframeLoadMonitor, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Event type dispatched by a goog.net.IframeLoadMonitor when it internal iframe
|
||||
* finishes loading for the first time after construction of the
|
||||
* goog.net.IframeLoadMonitor
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.LOAD_EVENT = 'ifload';
|
||||
|
||||
|
||||
/**
|
||||
* Poll interval for polling iframe load states in milliseconds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.POLL_INTERVAL_MS_ = 100;
|
||||
|
||||
|
||||
/**
|
||||
* Key for iframe load listener, or null if not currently listening on the
|
||||
* iframe for a load event.
|
||||
* @type {goog.events.Key}
|
||||
* @private
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.prototype.onloadListenerKey_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the iframe is loaded.
|
||||
* @return {boolean} whether or not the iframe is loaded.
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.prototype.isLoaded = function() {
|
||||
return this.isLoaded_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the poll timer if this IframeLoadMonitor is currently polling.
|
||||
* @private
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.prototype.maybeStopTimer_ = function() {
|
||||
if (this.intervalId_) {
|
||||
window.clearInterval(this.intervalId_);
|
||||
this.intervalId_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the iframe whose load state this IframeLoader monitors.
|
||||
* @return {HTMLIFrameElement} the iframe whose load state this IframeLoader
|
||||
* monitors.
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.prototype.getIframe = function() {
|
||||
return this.iframe_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.IframeLoadMonitor.prototype.disposeInternal = function() {
|
||||
delete this.iframe_;
|
||||
this.maybeStopTimer_();
|
||||
goog.events.unlistenByKey(this.onloadListenerKey_);
|
||||
goog.net.IframeLoadMonitor.superClass_.disposeInternal.call(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the iframe is loaded. Determines this by inspecting
|
||||
* browser dependent properties of the iframe.
|
||||
* @return {boolean} whether or not the iframe is loaded.
|
||||
* @private
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.prototype.isLoadedHelper_ = function() {
|
||||
var isLoaded = false;
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// IE will reliably have readyState set to complete if the iframe is loaded
|
||||
// For everything else, the iframe is loaded if there is a body and if the
|
||||
// body should have content the firstChild exists. Firefox can fire
|
||||
// the LOAD event and then a few hundred ms later replace the
|
||||
// contentDocument once the content is loaded.
|
||||
isLoaded = goog.userAgent.IE ? this.iframe_.readyState == 'complete' :
|
||||
!!goog.dom.getFrameContentDocument(this.iframe_).body &&
|
||||
(!this.hasContent_ ||
|
||||
!!goog.dom.getFrameContentDocument(this.iframe_).body.firstChild);
|
||||
} catch (e) {
|
||||
// Ignore these errors. This just means that the iframe is not loaded
|
||||
// IE will throw error reading readyState if the iframe is not appended
|
||||
// to the dom yet.
|
||||
// Firefox will throw error getting the iframe body if the iframe is not
|
||||
// fully loaded.
|
||||
}
|
||||
return isLoaded;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles an event indicating that the loading status of the iframe has
|
||||
* changed. In Firefox this is a goog.events.EventType.LOAD event, in IE
|
||||
* this is a goog.events.EventType.READYSTATECHANGED
|
||||
* @private
|
||||
*/
|
||||
goog.net.IframeLoadMonitor.prototype.handleLoad_ = function() {
|
||||
// Only do the handler if the iframe is loaded.
|
||||
if (this.isLoadedHelper_()) {
|
||||
this.maybeStopTimer_();
|
||||
goog.events.unlistenByKey(this.onloadListenerKey_);
|
||||
this.onloadListenerKey_ = null;
|
||||
this.isLoaded_ = true;
|
||||
this.dispatchEvent(goog.net.IframeLoadMonitor.LOAD_EVENT);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,292 @@
|
||||
// Copyright 2008 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 Image loader utility class. Useful when an application needs
|
||||
* to preload multiple images, for example so they can be sized.
|
||||
*
|
||||
* @author attila@google.com (Attila Bodis)
|
||||
* @author zachlloyd@google.com (Zachary Lloyd)
|
||||
* @author jonemerson@google.com (Jon Emerson)
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.ImageLoader');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Image loader utility class. Raises a {@link goog.events.EventType.LOAD}
|
||||
* event for each image loaded, with an {@link Image} object as the target of
|
||||
* the event, normalized to have {@code naturalHeight} and {@code naturalWidth}
|
||||
* attributes.
|
||||
*
|
||||
* To use this class, run:
|
||||
*
|
||||
* <pre>
|
||||
* var imageLoader = new goog.net.ImageLoader();
|
||||
* goog.events.listen(imageLoader, goog.net.EventType.COMPLETE,
|
||||
* function(e) { ... });
|
||||
* imageLoader.addImage("image_id", "http://path/to/image.gif");
|
||||
* imageLoader.start();
|
||||
* </pre>
|
||||
*
|
||||
* The start() method must be called to start image loading. Images can be
|
||||
* added and removed after loading has started, but only those images added
|
||||
* before start() was called will be loaded until start() is called again.
|
||||
* A goog.net.EventType.COMPLETE event will be dispatched only once all
|
||||
* outstanding images have completed uploading.
|
||||
*
|
||||
* @param {Element=} opt_parent An optional parent element whose document object
|
||||
* should be used to load images.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.net.ImageLoader = function(opt_parent) {
|
||||
goog.events.EventTarget.call(this);
|
||||
|
||||
/**
|
||||
* Map of image IDs to their image src, used to keep track of the images to
|
||||
* load. Once images have started loading, they're removed from this map.
|
||||
* @type {!Object.<string, string>}
|
||||
* @private
|
||||
*/
|
||||
this.imageIdToUrlMap_ = {};
|
||||
|
||||
/**
|
||||
* Map of image IDs to their image element, used only for images that are in
|
||||
* the process of loading. Used to clean-up event listeners and to know
|
||||
* when we've completed loading images.
|
||||
* @type {!Object.<string, !Element>}
|
||||
* @private
|
||||
*/
|
||||
this.imageIdToImageMap_ = {};
|
||||
|
||||
/**
|
||||
* Event handler object, used to keep track of onload and onreadystatechange
|
||||
* listeners.
|
||||
* @type {!goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = new goog.events.EventHandler(this);
|
||||
|
||||
/**
|
||||
* The parent element whose document object will be used to load images.
|
||||
* Useful if you want to load the images from a window other than the current
|
||||
* window in order to control the Referer header sent when the image is
|
||||
* loaded.
|
||||
* @type {Element|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.parent_ = opt_parent;
|
||||
};
|
||||
goog.inherits(goog.net.ImageLoader, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* An array of event types to listen to on images. This is browser dependent.
|
||||
* Internet Explorer doesn't reliably raise LOAD events on images, so we must
|
||||
* use READY_STATE_CHANGE. If the image is cached locally, IE won't fire the
|
||||
* LOAD event while the onreadystate event is fired always. On the other hand,
|
||||
* the ERROR event is always fired whenever the image is not loaded successfully
|
||||
* no matter whether it's cached or not.
|
||||
* @type {!Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.ImageLoader.IMAGE_LOAD_EVENTS_ = [
|
||||
goog.userAgent.IE ? goog.net.EventType.READY_STATE_CHANGE :
|
||||
goog.events.EventType.LOAD,
|
||||
goog.net.EventType.ABORT,
|
||||
goog.net.EventType.ERROR
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Adds an image to the image loader, and associates it with the given ID
|
||||
* string. If an image with that ID already exists, it is silently replaced.
|
||||
* When the image in question is loaded, the target of the LOAD event will be
|
||||
* an {@code Image} object with {@code id} and {@code src} attributes based on
|
||||
* these arguments.
|
||||
* @param {string} id The ID of the image to load.
|
||||
* @param {string|Image} image Either the source URL of the image or the HTML
|
||||
* image element itself (or any object with a {@code src} property, really).
|
||||
*/
|
||||
goog.net.ImageLoader.prototype.addImage = function(id, image) {
|
||||
var src = goog.isString(image) ? image : image.src;
|
||||
if (src) {
|
||||
// For now, we just store the source URL for the image.
|
||||
this.imageIdToUrlMap_[id] = src;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the image associated with the given ID string from the image loader.
|
||||
* If the image was previously loading, removes any listeners for its events
|
||||
* and dispatches a COMPLETE event if all remaining images have now completed.
|
||||
* @param {string} id The ID of the image to remove.
|
||||
*/
|
||||
goog.net.ImageLoader.prototype.removeImage = function(id) {
|
||||
delete this.imageIdToUrlMap_[id];
|
||||
|
||||
var image = this.imageIdToImageMap_[id];
|
||||
if (image) {
|
||||
delete this.imageIdToImageMap_[id];
|
||||
|
||||
// Stop listening for events on the image.
|
||||
this.handler_.unlisten(image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_,
|
||||
this.onNetworkEvent_);
|
||||
|
||||
// If this was the last image, raise a COMPLETE event.
|
||||
if (goog.object.isEmpty(this.imageIdToImageMap_) &&
|
||||
goog.object.isEmpty(this.imageIdToUrlMap_)) {
|
||||
this.dispatchEvent(goog.net.EventType.COMPLETE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts loading all images in the image loader in parallel. Raises a LOAD
|
||||
* event each time an image finishes loading, and a COMPLETE event after all
|
||||
* images have finished loading.
|
||||
*/
|
||||
goog.net.ImageLoader.prototype.start = function() {
|
||||
// Iterate over the keys, rather than the full object, to essentially clone
|
||||
// the initial queued images in case any event handlers decide to add more
|
||||
// images before this loop has finished executing.
|
||||
var imageIdToUrlMap = this.imageIdToUrlMap_;
|
||||
goog.array.forEach(goog.object.getKeys(imageIdToUrlMap),
|
||||
function(id) {
|
||||
var src = imageIdToUrlMap[id];
|
||||
if (src) {
|
||||
delete imageIdToUrlMap[id];
|
||||
this.loadImage_(src, id);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an {@code Image} object with the specified ID and source URL, and
|
||||
* listens for network events raised as the image is loaded.
|
||||
* @param {string} src The image source URL.
|
||||
* @param {string} id The unique ID of the image to load.
|
||||
* @private
|
||||
*/
|
||||
goog.net.ImageLoader.prototype.loadImage_ = function(src, id) {
|
||||
if (this.isDisposed()) {
|
||||
// When loading an image in IE7 (and maybe IE8), the error handler
|
||||
// may fire before we yield JS control. If the error handler
|
||||
// dispose the ImageLoader, this method will throw exception.
|
||||
return;
|
||||
}
|
||||
|
||||
var image;
|
||||
if (this.parent_) {
|
||||
var dom = goog.dom.getDomHelper(this.parent_);
|
||||
image = dom.createDom('img');
|
||||
} else {
|
||||
image = new Image();
|
||||
}
|
||||
|
||||
this.handler_.listen(image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_,
|
||||
this.onNetworkEvent_);
|
||||
this.imageIdToImageMap_[id] = image;
|
||||
|
||||
image.id = id;
|
||||
image.src = src;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles net events (READY_STATE_CHANGE, LOAD, ABORT, and ERROR).
|
||||
* @param {goog.events.Event} evt The network event to handle.
|
||||
* @private
|
||||
*/
|
||||
goog.net.ImageLoader.prototype.onNetworkEvent_ = function(evt) {
|
||||
var image = /** @type {Element} */ (evt.currentTarget);
|
||||
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.type == goog.net.EventType.READY_STATE_CHANGE) {
|
||||
// This implies that the user agent is IE; see loadImage_().
|
||||
// Noe that this block is used to check whether the image is ready to
|
||||
// dispatch the COMPLETE event.
|
||||
if (image.readyState == goog.net.EventType.COMPLETE) {
|
||||
// This is the IE equivalent of a LOAD event.
|
||||
evt.type = goog.events.EventType.LOAD;
|
||||
} else {
|
||||
// This may imply that the load failed.
|
||||
// Note that the image has only the following states:
|
||||
// * uninitialized
|
||||
// * loading
|
||||
// * complete
|
||||
// When the ERROR or the ABORT event is fired, the readyState
|
||||
// will be either uninitialized or loading and we'd ignore those states
|
||||
// since they will be handled separately (eg: evt.type = 'ERROR').
|
||||
|
||||
// Notes from MSDN : The states through which an object passes are
|
||||
// determined by that object. An object can skip certain states
|
||||
// (for example, interactive) if the state does not apply to that object.
|
||||
// see http://msdn.microsoft.com/en-us/library/ms534359(VS.85).aspx
|
||||
|
||||
// The image is not loaded, ignore.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add natural width/height properties for non-Gecko browsers.
|
||||
if (typeof image.naturalWidth == 'undefined') {
|
||||
if (evt.type == goog.events.EventType.LOAD) {
|
||||
image.naturalWidth = image.width;
|
||||
image.naturalHeight = image.height;
|
||||
} else {
|
||||
// This implies that the image fails to be loaded.
|
||||
image.naturalWidth = 0;
|
||||
image.naturalHeight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Redispatch the event on behalf of the image. Note that the external
|
||||
// listener may dispose this instance.
|
||||
this.dispatchEvent({type: evt.type, target: image});
|
||||
|
||||
if (this.isDisposed()) {
|
||||
// If instance was disposed by listener, exit this function.
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeImage(image.id);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.ImageLoader.prototype.disposeInternal = function() {
|
||||
delete this.imageIdToUrlMap_;
|
||||
delete this.imageIdToImageMap_;
|
||||
goog.dispose(this.handler_);
|
||||
|
||||
goog.net.ImageLoader.superClass_.disposeInternal.call(this);
|
||||
};
|
||||
@@ -0,0 +1,511 @@
|
||||
// Copyright 2011 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 This file contains classes to handle IPv4 and IPv6 addresses.
|
||||
* This implementation is mostly based on Google's project:
|
||||
* http://code.google.com/p/ipaddr-py/.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.IpAddress');
|
||||
goog.provide('goog.net.Ipv4Address');
|
||||
goog.provide('goog.net.Ipv6Address');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math.Integer');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class defining an IP Address.
|
||||
*
|
||||
* Please use goog.net.IpAddress static methods or
|
||||
* goog.net.Ipv4Address/Ipv6Address classes.
|
||||
*
|
||||
* @param {!goog.math.Integer} address The Ip Address.
|
||||
* @param {number} version The version number (4, 6).
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.IpAddress = function(address, version) {
|
||||
/**
|
||||
* The IP Address.
|
||||
* @type {!goog.math.Integer}
|
||||
* @private
|
||||
*/
|
||||
this.ip_ = address;
|
||||
|
||||
/**
|
||||
* The IP Address version.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.version_ = version;
|
||||
|
||||
/**
|
||||
* The IPAddress, as string.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.ipStr_ = '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The IP Address version.
|
||||
*/
|
||||
goog.net.IpAddress.prototype.getVersion = function() {
|
||||
return this.version_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!goog.net.IpAddress} other The other IP Address.
|
||||
* @return {boolean} true if the IP Addresses are equal.
|
||||
*/
|
||||
goog.net.IpAddress.prototype.equals = function(other) {
|
||||
return (this.version_ == other.getVersion() &&
|
||||
this.ip_.equals(other.toInteger()));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.math.Integer} The IP Address, as an Integer.
|
||||
*/
|
||||
goog.net.IpAddress.prototype.toInteger = function() {
|
||||
return /** @type {goog.math.Integer} */ (goog.object.clone(this.ip_));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The IP Address, as an URI string following RFC 3986.
|
||||
*/
|
||||
goog.net.IpAddress.prototype.toUriString = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The IP Address, as a string.
|
||||
* @override
|
||||
*/
|
||||
goog.net.IpAddress.prototype.toString = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Parses an IP Address in a string.
|
||||
* If the string is malformed, the function will simply return null
|
||||
* instead of raising an exception.
|
||||
*
|
||||
* @param {string} address The IP Address.
|
||||
* @see {goog.net.Ipv4Address}
|
||||
* @see {goog.net.Ipv6Address}
|
||||
* @return {goog.net.IpAddress} The IP Address or null.
|
||||
*/
|
||||
goog.net.IpAddress.fromString = function(address) {
|
||||
try {
|
||||
if (address.indexOf(':') != -1) {
|
||||
return new goog.net.Ipv6Address(address);
|
||||
}
|
||||
|
||||
return new goog.net.Ipv4Address(address);
|
||||
} catch (e) {
|
||||
// Both constructors raise exception if the address is malformed (ie.
|
||||
// invalid). The user of this function should not care about catching
|
||||
// the exception, espcially if it's used to validate an user input.
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tries to parse a string represented as a host portion of an URI.
|
||||
* See RFC 3986 for more details on IPv6 addresses inside URI.
|
||||
* If the string is malformed, the function will simply return null
|
||||
* instead of raising an exception.
|
||||
*
|
||||
* @param {string} address A RFC 3986 encoded IP address.
|
||||
* @see {goog.net.Ipv4Address}
|
||||
* @see {goog.net.Ipv6Address}
|
||||
* @return {goog.net.IpAddress} The IP Address.
|
||||
*/
|
||||
goog.net.IpAddress.fromUriString = function(address) {
|
||||
try {
|
||||
if (goog.string.startsWith(address, '[') &&
|
||||
goog.string.endsWith(address, ']')) {
|
||||
return new goog.net.Ipv6Address(
|
||||
address.substring(1, address.length - 1));
|
||||
}
|
||||
|
||||
return new goog.net.Ipv4Address(address);
|
||||
} catch (e) {
|
||||
// Both constructors raise exception if the address is malformed (ie.
|
||||
// invalid). The user of this function should not care about catching
|
||||
// the exception, espcially if it's used to validate an user input.
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Takes a string or a number and returns a IPv4 Address.
|
||||
*
|
||||
* This constructor accepts strings and instance of goog.math.Integer.
|
||||
* If you pass a goog.math.Integer, make sure that its sign is set to positive.
|
||||
* @param {(string|!goog.math.Integer)} address The address to store.
|
||||
* @extends {goog.net.IpAddress}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.Ipv4Address = function(address) {
|
||||
var ip = goog.math.Integer.ZERO;
|
||||
if (address instanceof goog.math.Integer) {
|
||||
if (address.getSign() != 0 ||
|
||||
address.lessThan(goog.math.Integer.ZERO) ||
|
||||
address.greaterThan(goog.net.Ipv4Address.MAX_ADDRESS_)) {
|
||||
throw Error('The address does not look like an IPv4.');
|
||||
} else {
|
||||
ip = goog.object.clone(address);
|
||||
}
|
||||
} else {
|
||||
if (!goog.net.Ipv4Address.REGEX_.test(address)) {
|
||||
throw Error(address + ' does not look like an IPv4 address.');
|
||||
}
|
||||
|
||||
var octets = address.split('.');
|
||||
if (octets.length != 4) {
|
||||
throw Error(address + ' does not look like an IPv4 address.');
|
||||
}
|
||||
|
||||
for (var i = 0; i < octets.length; i++) {
|
||||
var parsedOctet = goog.string.toNumber(octets[i]);
|
||||
if (isNaN(parsedOctet) ||
|
||||
parsedOctet < 0 || parsedOctet > 255 ||
|
||||
(octets[i].length != 1 && goog.string.startsWith(octets[i], '0'))) {
|
||||
throw Error('In ' + address + ', octet ' + i + ' is not valid');
|
||||
}
|
||||
var intOctet = goog.math.Integer.fromNumber(parsedOctet);
|
||||
ip = ip.shiftLeft(8).or(intOctet);
|
||||
}
|
||||
}
|
||||
goog.base(this, /** @type {!goog.math.Integer} */ (ip), 4);
|
||||
};
|
||||
goog.inherits(goog.net.Ipv4Address, goog.net.IpAddress);
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression matching all the allowed chars for IPv4.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
goog.net.Ipv4Address.REGEX_ = /^[0-9.]*$/;
|
||||
|
||||
|
||||
/**
|
||||
* The Maximum length for a netmask (aka, the number of bits for IPv4).
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
goog.net.Ipv4Address.MAX_NETMASK_LENGTH = 32;
|
||||
|
||||
|
||||
/**
|
||||
* The Maximum address possible for IPv4.
|
||||
* @type {goog.math.Integer}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
goog.net.Ipv4Address.MAX_ADDRESS_ = goog.math.Integer.ONE.shiftLeft(
|
||||
goog.net.Ipv4Address.MAX_NETMASK_LENGTH).subtract(goog.math.Integer.ONE);
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.net.Ipv4Address.prototype.toString = function() {
|
||||
if (this.ipStr_) {
|
||||
return this.ipStr_;
|
||||
}
|
||||
|
||||
var ip = this.ip_.getBitsUnsigned(0);
|
||||
var octets = [];
|
||||
for (var i = 3; i >= 0; i--) {
|
||||
octets[i] = String((ip & 0xff));
|
||||
ip = ip >>> 8;
|
||||
}
|
||||
|
||||
this.ipStr_ = octets.join('.');
|
||||
|
||||
return this.ipStr_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.net.Ipv4Address.prototype.toUriString = function() {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Takes a string or a number and returns an IPv6 Address.
|
||||
*
|
||||
* This constructor accepts strings and instance of goog.math.Integer.
|
||||
* If you pass a goog.math.Integer, make sure that its sign is set to positive.
|
||||
* @param {(string|!goog.math.Integer)} address The address to store.
|
||||
* @constructor
|
||||
* @extends {goog.net.IpAddress}
|
||||
*/
|
||||
goog.net.Ipv6Address = function(address) {
|
||||
var ip = goog.math.Integer.ZERO;
|
||||
if (address instanceof goog.math.Integer) {
|
||||
if (address.getSign() != 0 ||
|
||||
address.lessThan(goog.math.Integer.ZERO) ||
|
||||
address.greaterThan(goog.net.Ipv6Address.MAX_ADDRESS_)) {
|
||||
throw Error('The address does not look like a valid IPv6.');
|
||||
} else {
|
||||
ip = goog.object.clone(address);
|
||||
}
|
||||
} else {
|
||||
if (!goog.net.Ipv6Address.REGEX_.test(address)) {
|
||||
throw Error(address + ' is not a valid IPv6 address.');
|
||||
}
|
||||
|
||||
var splitColon = address.split(':');
|
||||
if (splitColon[splitColon.length - 1].indexOf('.') != -1) {
|
||||
var newHextets = goog.net.Ipv6Address.dottedQuadtoHextets_(
|
||||
splitColon[splitColon.length - 1]);
|
||||
goog.array.removeAt(splitColon, splitColon.length - 1);
|
||||
goog.array.extend(splitColon, newHextets);
|
||||
address = splitColon.join(':');
|
||||
}
|
||||
|
||||
var splitDoubleColon = address.split('::');
|
||||
if (splitDoubleColon.length > 2 ||
|
||||
(splitDoubleColon.length == 1 && splitColon.length != 8)) {
|
||||
throw Error(address + ' is not a valid IPv6 address.');
|
||||
}
|
||||
|
||||
var ipArr;
|
||||
if (splitDoubleColon.length > 1) {
|
||||
ipArr = goog.net.Ipv6Address.explode_(splitDoubleColon);
|
||||
} else {
|
||||
ipArr = splitColon;
|
||||
}
|
||||
|
||||
if (ipArr.length != 8) {
|
||||
throw Error(address + ' is not a valid IPv6 address');
|
||||
}
|
||||
|
||||
for (var i = 0; i < ipArr.length; i++) {
|
||||
var parsedHextet = goog.math.Integer.fromString(ipArr[i], 16);
|
||||
if (parsedHextet.lessThan(goog.math.Integer.ZERO) ||
|
||||
parsedHextet.greaterThan(goog.net.Ipv6Address.MAX_HEXTET_VALUE_)) {
|
||||
throw Error(ipArr[i] + ' in ' + address + ' is not a valid hextet.');
|
||||
}
|
||||
ip = ip.shiftLeft(16).or(parsedHextet);
|
||||
}
|
||||
}
|
||||
goog.base(this, /** @type {!goog.math.Integer} */ (ip), 6);
|
||||
};
|
||||
goog.inherits(goog.net.Ipv6Address, goog.net.IpAddress);
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression matching all allowed chars for an IPv6.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
goog.net.Ipv6Address.REGEX_ = /^([a-fA-F0-9]*:){2}[a-fA-F0-9:.]*$/;
|
||||
|
||||
|
||||
/**
|
||||
* The Maximum length for a netmask (aka, the number of bits for IPv6).
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
goog.net.Ipv6Address.MAX_NETMASK_LENGTH = 128;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum value of a hextet.
|
||||
* @type {goog.math.Integer}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
goog.net.Ipv6Address.MAX_HEXTET_VALUE_ = goog.math.Integer.fromInt(65535);
|
||||
|
||||
|
||||
/**
|
||||
* The Maximum address possible for IPv6.
|
||||
* @type {goog.math.Integer}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
goog.net.Ipv6Address.MAX_ADDRESS_ = goog.math.Integer.ONE.shiftLeft(
|
||||
goog.net.Ipv6Address.MAX_NETMASK_LENGTH).subtract(goog.math.Integer.ONE);
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.net.Ipv6Address.prototype.toString = function() {
|
||||
if (this.ipStr_) {
|
||||
return this.ipStr_;
|
||||
}
|
||||
|
||||
var outputArr = [];
|
||||
for (var i = 3; i >= 0; i--) {
|
||||
var bits = this.ip_.getBitsUnsigned(i);
|
||||
var firstHextet = bits >>> 16;
|
||||
var secondHextet = bits & 0xffff;
|
||||
outputArr.push(firstHextet.toString(16));
|
||||
outputArr.push(secondHextet.toString(16));
|
||||
}
|
||||
|
||||
outputArr = goog.net.Ipv6Address.compress_(outputArr);
|
||||
this.ipStr_ = outputArr.join(':');
|
||||
return this.ipStr_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.net.Ipv6Address.prototype.toUriString = function() {
|
||||
return '[' + this.toString() + ']';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This method is in charge of expanding/exploding an IPv6 string from its
|
||||
* compressed form.
|
||||
* @private
|
||||
* @param {!Array.<string>} address An IPv6 address split around '::'.
|
||||
* @return {Array.<string>} The expanded version of the IPv6.
|
||||
*/
|
||||
goog.net.Ipv6Address.explode_ = function(address) {
|
||||
var basePart = address[0].split(':');
|
||||
var secondPart = address[1].split(':');
|
||||
|
||||
if (basePart.length == 1 && basePart[0] == '') {
|
||||
basePart = [];
|
||||
}
|
||||
if (secondPart.length == 1 && secondPart[0] == '') {
|
||||
secondPart = [];
|
||||
}
|
||||
|
||||
// Now we fill the gap with 0.
|
||||
var gap = 8 - (basePart.length + secondPart.length);
|
||||
|
||||
if (gap < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
goog.array.extend(basePart, goog.array.repeat('0', gap));
|
||||
|
||||
// Now we merge the basePart + gap + secondPart
|
||||
goog.array.extend(basePart, secondPart);
|
||||
|
||||
return basePart;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This method is in charge of compressing an expanded IPv6 array of hextets.
|
||||
* @private
|
||||
* @param {!Array.<string>} hextets The array of hextet.
|
||||
* @return {Array.<string>} The compressed version of this array.
|
||||
*/
|
||||
goog.net.Ipv6Address.compress_ = function(hextets) {
|
||||
var bestStart = -1;
|
||||
var start = -1;
|
||||
var bestSize = 0;
|
||||
var size = 0;
|
||||
for (var i = 0; i < hextets.length; i++) {
|
||||
if (hextets[i] == '0') {
|
||||
size++;
|
||||
if (start == -1) {
|
||||
start = i;
|
||||
}
|
||||
if (size > bestSize) {
|
||||
bestSize = size;
|
||||
bestStart = start;
|
||||
}
|
||||
} else {
|
||||
start = -1;
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestSize > 0) {
|
||||
if ((bestStart + bestSize) == hextets.length) {
|
||||
hextets.push('');
|
||||
}
|
||||
hextets.splice(bestStart, bestSize, '');
|
||||
|
||||
if (bestStart == 0) {
|
||||
hextets = [''].concat(hextets);
|
||||
}
|
||||
}
|
||||
return hextets;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This method will convert an IPv4 to a list of 2 hextets.
|
||||
*
|
||||
* For instance, 1.2.3.4 will be converted to ['0102', '0304'].
|
||||
* @private
|
||||
* @param {string} quads An IPv4 as a string.
|
||||
* @return {Array.<string>} A list of 2 hextets.
|
||||
*/
|
||||
goog.net.Ipv6Address.dottedQuadtoHextets_ = function(quads) {
|
||||
var ip4 = new goog.net.Ipv4Address(quads).toInteger();
|
||||
var bits = ip4.getBitsUnsigned(0);
|
||||
var hextets = [];
|
||||
|
||||
hextets.push(((bits >>> 16) & 0xffff).toString(16));
|
||||
hextets.push((bits & 0xffff).toString(16));
|
||||
|
||||
return hextets;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} true if the IPv6 contains a mapped IPv4.
|
||||
*/
|
||||
goog.net.Ipv6Address.prototype.isMappedIpv4Address = function() {
|
||||
return (this.ip_.getBitsUnsigned(3) == 0 &&
|
||||
this.ip_.getBitsUnsigned(2) == 0 &&
|
||||
this.ip_.getBitsUnsigned(1) == 0xffff);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Will return the mapped IPv4 address in this IPv6 address.
|
||||
* @return {goog.net.Ipv4Address} an IPv4 or null.
|
||||
*/
|
||||
goog.net.Ipv6Address.prototype.getMappedIpv4Address = function() {
|
||||
if (!this.isMappedIpv4Address()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var newIpv4 = new goog.math.Integer([this.ip_.getBitsUnsigned(0)], 0);
|
||||
return new goog.net.Ipv4Address(newIpv4);
|
||||
};
|
||||
@@ -0,0 +1,366 @@
|
||||
// Copyright 2011 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 utility to load JavaScript files via DOM script tags.
|
||||
* Refactored from goog.net.Jsonp. Works cross-domain.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.jsloader');
|
||||
goog.provide('goog.net.jsloader.Error');
|
||||
goog.provide('goog.net.jsloader.ErrorCode');
|
||||
goog.provide('goog.net.jsloader.Options');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
|
||||
|
||||
/**
|
||||
* The name of the property of goog.global under which the JavaScript
|
||||
* verification object is stored by the loaded script.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
|
||||
|
||||
|
||||
/**
|
||||
* The default length of time, in milliseconds, we are prepared to wait for a
|
||||
* load request to complete.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.net.jsloader.DEFAULT_TIMEOUT = 5000;
|
||||
|
||||
|
||||
/**
|
||||
* Optional parameters for goog.net.jsloader.send.
|
||||
* timeout: The length of time, in milliseconds, we are prepared to wait
|
||||
* for a load request to complete. Default it 5 seconds.
|
||||
* document: The HTML document under which to load the JavaScript. Default is
|
||||
* the current document.
|
||||
* cleanupWhenDone: If true clean up the script tag after script completes to
|
||||
* load. This is important if you just want to read data from the JavaScript
|
||||
* and then throw it away. Default is false.
|
||||
*
|
||||
* @typedef {{
|
||||
* timeout: (number|undefined),
|
||||
* document: (HTMLDocument|undefined),
|
||||
* cleanupWhenDone: (boolean|undefined)
|
||||
* }}
|
||||
*/
|
||||
goog.net.jsloader.Options;
|
||||
|
||||
|
||||
/**
|
||||
* Scripts (URIs) waiting to be loaded.
|
||||
* @type {Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.jsloader.scriptsToLoad_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Loads and evaluates the JavaScript files at the specified URIs, guaranteeing
|
||||
* the order of script loads.
|
||||
*
|
||||
* Because we have to load the scripts in serial (load script 1, exec script 1,
|
||||
* load script 2, exec script 2, and so on), this will be slower than doing
|
||||
* the network fetches in parallel.
|
||||
*
|
||||
* If you need to load a large number of scripts but dependency order doesn't
|
||||
* matter, you should just call goog.net.jsloader.load N times.
|
||||
*
|
||||
* If you need to load a large number of scripts on the same domain,
|
||||
* you may want to use goog.module.ModuleLoader.
|
||||
*
|
||||
* @param {Array.<string>} uris The URIs to load.
|
||||
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
|
||||
* goog.net.jsloader.options documentation for details.
|
||||
*/
|
||||
goog.net.jsloader.loadMany = function(uris, opt_options) {
|
||||
// Loading the scripts in serial introduces asynchronosity into the flow.
|
||||
// Therefore, there are race conditions where client A can kick off the load
|
||||
// sequence for client B, even though client A's scripts haven't all been
|
||||
// loaded yet.
|
||||
//
|
||||
// To work around this issue, all module loads share a queue.
|
||||
if (!uris.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length;
|
||||
goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris);
|
||||
if (isAnotherModuleLoading) {
|
||||
// jsloader is still loading some other scripts.
|
||||
// In order to prevent the race condition noted above, we just add
|
||||
// these URIs to the end of the scripts' queue and return.
|
||||
return;
|
||||
}
|
||||
|
||||
uris = goog.net.jsloader.scriptsToLoad_;
|
||||
var popAndLoadNextScript = function() {
|
||||
var uri = uris.shift();
|
||||
var deferred = goog.net.jsloader.load(uri, opt_options);
|
||||
if (uris.length) {
|
||||
deferred.addBoth(popAndLoadNextScript);
|
||||
}
|
||||
};
|
||||
popAndLoadNextScript();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads and evaluates a JavaScript file.
|
||||
* When the script loads, a user callback is called.
|
||||
* It is the client's responsibility to verify that the script ran successfully.
|
||||
*
|
||||
* @param {string} uri The URI of the JavaScript.
|
||||
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
|
||||
* goog.net.jsloader.Options documentation for details.
|
||||
* @return {!goog.async.Deferred} The deferred result, that may be used to add
|
||||
* callbacks and/or cancel the transmission.
|
||||
* The error callback will be called with a single goog.net.jsloader.Error
|
||||
* parameter.
|
||||
*/
|
||||
goog.net.jsloader.load = function(uri, opt_options) {
|
||||
var options = opt_options || {};
|
||||
var doc = options.document || document;
|
||||
|
||||
var script = goog.dom.createElement(goog.dom.TagName.SCRIPT);
|
||||
var request = {script_: script, timeout_: undefined};
|
||||
var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request);
|
||||
|
||||
// Set a timeout.
|
||||
var timeout = null;
|
||||
var timeoutDuration = goog.isDefAndNotNull(options.timeout) ?
|
||||
options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT;
|
||||
if (timeoutDuration > 0) {
|
||||
timeout = window.setTimeout(function() {
|
||||
goog.net.jsloader.cleanup_(script, true);
|
||||
deferred.errback(new goog.net.jsloader.Error(
|
||||
goog.net.jsloader.ErrorCode.TIMEOUT,
|
||||
'Timeout reached for loading script ' + uri));
|
||||
}, timeoutDuration);
|
||||
request.timeout_ = timeout;
|
||||
}
|
||||
|
||||
// Hang the user callback to be called when the script completes to load.
|
||||
// NOTE(user): This callback will be called in IE even upon error. In any
|
||||
// case it is the client's responsibility to verify that the script ran
|
||||
// successfully.
|
||||
script.onload = script.onreadystatechange = function() {
|
||||
if (!script.readyState || script.readyState == 'loaded' ||
|
||||
script.readyState == 'complete') {
|
||||
var removeScriptNode = options.cleanupWhenDone || false;
|
||||
goog.net.jsloader.cleanup_(script, removeScriptNode, timeout);
|
||||
deferred.callback(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Add an error callback.
|
||||
// NOTE(user): Not supported in IE.
|
||||
script.onerror = function() {
|
||||
goog.net.jsloader.cleanup_(script, true, timeout);
|
||||
deferred.errback(new goog.net.jsloader.Error(
|
||||
goog.net.jsloader.ErrorCode.LOAD_ERROR,
|
||||
'Error while loading script ' + uri));
|
||||
};
|
||||
|
||||
// Add the script element to the document.
|
||||
goog.dom.setProperties(script, {
|
||||
'type': 'text/javascript',
|
||||
'charset': 'UTF-8',
|
||||
// NOTE(user): Safari never loads the script if we don't set
|
||||
// the src attribute before appending.
|
||||
'src': uri
|
||||
});
|
||||
var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);
|
||||
scriptParent.appendChild(script);
|
||||
|
||||
return deferred;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Loads a JavaScript file and verifies it was evaluated successfully, using a
|
||||
* verification object.
|
||||
* The verification object is set by the loaded JavaScript at the end of the
|
||||
* script.
|
||||
* We verify this object was set and return its value in the success callback.
|
||||
* If the object is not defined we trigger an error callback.
|
||||
*
|
||||
* @param {string} uri The URI of the JavaScript.
|
||||
* @param {string} verificationObjName The name of the verification object that
|
||||
* the loaded script should set.
|
||||
* @param {goog.net.jsloader.Options} options Optional parameters. See
|
||||
* goog.net.jsloader.Options documentation for details.
|
||||
* @return {!goog.async.Deferred} The deferred result, that may be used to add
|
||||
* callbacks and/or cancel the transmission.
|
||||
* The success callback will be called with a single parameter containing
|
||||
* the value of the verification object.
|
||||
* The error callback will be called with a single goog.net.jsloader.Error
|
||||
* parameter.
|
||||
*/
|
||||
goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) {
|
||||
// Define the global objects variable.
|
||||
if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) {
|
||||
goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {};
|
||||
}
|
||||
var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_];
|
||||
|
||||
// Verify that the expected object does not exist yet.
|
||||
if (goog.isDef(verifyObjs[verificationObjName])) {
|
||||
// TODO(user): Error or reset variable?
|
||||
return goog.async.Deferred.fail(new goog.net.jsloader.Error(
|
||||
goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS,
|
||||
'Verification object ' + verificationObjName + ' already defined.'));
|
||||
}
|
||||
|
||||
// Send request to load the JavaScript.
|
||||
var sendDeferred = goog.net.jsloader.load(uri, options);
|
||||
|
||||
// Create a deferred object wrapping the send result.
|
||||
var deferred = new goog.async.Deferred(
|
||||
goog.bind(sendDeferred.cancel, sendDeferred));
|
||||
|
||||
// Call user back with object that was set by the script.
|
||||
sendDeferred.addCallback(function() {
|
||||
var result = verifyObjs[verificationObjName];
|
||||
if (goog.isDef(result)) {
|
||||
deferred.callback(result);
|
||||
delete verifyObjs[verificationObjName];
|
||||
} else {
|
||||
// Error: script was not loaded properly.
|
||||
deferred.errback(new goog.net.jsloader.Error(
|
||||
goog.net.jsloader.ErrorCode.VERIFY_ERROR,
|
||||
'Script ' + uri + ' loaded, but verification object ' +
|
||||
verificationObjName + ' was not defined.'));
|
||||
}
|
||||
});
|
||||
|
||||
// Pass error to new deferred object.
|
||||
sendDeferred.addErrback(function(error) {
|
||||
if (goog.isDef(verifyObjs[verificationObjName])) {
|
||||
delete verifyObjs[verificationObjName];
|
||||
}
|
||||
deferred.errback(error);
|
||||
});
|
||||
|
||||
return deferred;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the DOM element under which we should add new script elements.
|
||||
* How? Take the first head element, and if not found take doc.documentElement,
|
||||
* which always exists.
|
||||
*
|
||||
* @param {!HTMLDocument} doc The relevant document.
|
||||
* @return {!Element} The script parent element.
|
||||
* @private
|
||||
*/
|
||||
goog.net.jsloader.getScriptParentElement_ = function(doc) {
|
||||
var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD);
|
||||
if (!headElements || goog.array.isEmpty(headElements)) {
|
||||
return doc.documentElement;
|
||||
} else {
|
||||
return headElements[0];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cancels a given request.
|
||||
* @this {{script_: Element, timeout_: number}} The request context.
|
||||
* @private
|
||||
*/
|
||||
goog.net.jsloader.cancel_ = function() {
|
||||
var request = this;
|
||||
if (request && request.script_) {
|
||||
var scriptNode = request.script_;
|
||||
if (scriptNode && scriptNode.tagName == 'SCRIPT') {
|
||||
goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the script node and the timeout.
|
||||
*
|
||||
* @param {Node} scriptNode The node to be cleaned up.
|
||||
* @param {boolean} removeScriptNode If true completely remove the script node.
|
||||
* @param {?number=} opt_timeout The timeout handler to cleanup.
|
||||
* @private
|
||||
*/
|
||||
goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode,
|
||||
opt_timeout) {
|
||||
if (goog.isDefAndNotNull(opt_timeout)) {
|
||||
goog.global.clearTimeout(opt_timeout);
|
||||
}
|
||||
|
||||
scriptNode.onload = goog.nullFunction;
|
||||
scriptNode.onerror = goog.nullFunction;
|
||||
scriptNode.onreadystatechange = goog.nullFunction;
|
||||
|
||||
// Do this after a delay (removing the script node of a running script can
|
||||
// confuse older IEs).
|
||||
if (removeScriptNode) {
|
||||
window.setTimeout(function() {
|
||||
goog.dom.removeNode(scriptNode);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Possible error codes for jsloader.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.jsloader.ErrorCode = {
|
||||
LOAD_ERROR: 0,
|
||||
TIMEOUT: 1,
|
||||
VERIFY_ERROR: 2,
|
||||
VERIFY_OBJECT_ALREADY_EXISTS: 3
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A jsloader error.
|
||||
*
|
||||
* @param {goog.net.jsloader.ErrorCode} code The error code.
|
||||
* @param {string=} opt_message Additional message.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.net.jsloader.Error = function(code, opt_message) {
|
||||
var msg = 'Jsloader error (code #' + code + ')';
|
||||
if (opt_message) {
|
||||
msg += ': ' + opt_message;
|
||||
}
|
||||
goog.base(this, msg);
|
||||
|
||||
/**
|
||||
* The code for this error.
|
||||
*
|
||||
* @type {goog.net.jsloader.ErrorCode}
|
||||
*/
|
||||
this.code = code;
|
||||
};
|
||||
goog.inherits(goog.net.jsloader.Error, goog.debug.Error);
|
||||
@@ -0,0 +1,338 @@
|
||||
// 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.
|
||||
|
||||
// The original file lives here: http://go/cross_domain_channel.js
|
||||
|
||||
/**
|
||||
* @fileoverview Implements a cross-domain communication channel. A
|
||||
* typical web page is prevented by browser security from sending
|
||||
* request, such as a XMLHttpRequest, to other servers than the ones
|
||||
* from which it came. The Jsonp class provides a workound, by
|
||||
* using dynamically generated script tags. Typical usage:.
|
||||
*
|
||||
* var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
|
||||
* var payload = { 'foo': 1, 'bar': true };
|
||||
* jsonp.send(payload, function(reply) { alert(reply) });
|
||||
*
|
||||
* This script works in all browsers that are currently supported by
|
||||
* the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
|
||||
* Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.Jsonp');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.net.jsloader');
|
||||
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
//
|
||||
// This class allows us (Google) to send data from non-Google and thus
|
||||
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
|
||||
// anything sensitive, such as session or cookie specific data. Return
|
||||
// only data that you want parties external to Google to have. Also
|
||||
// NEVER use this method to send data from web pages to untrusted
|
||||
// servers, or redirects to unknown servers (www.google.com/cache,
|
||||
// /q=xx&btnl, /url, www.googlepages.com, etc.)
|
||||
//
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new cross domain channel that sends data to the specified
|
||||
* host URL. By default, if no reply arrives within 5s, the channel
|
||||
* assumes the call failed to complete successfully.
|
||||
*
|
||||
* @param {goog.Uri|string} uri The Uri of the server side code that receives
|
||||
* data posted through this channel (e.g.,
|
||||
* "http://maps.google.com/maps/geo").
|
||||
*
|
||||
* @param {string=} opt_callbackParamName The parameter name that is used to
|
||||
* specify the callback. Defaults to "callback".
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.Jsonp = function(uri, opt_callbackParamName) {
|
||||
/**
|
||||
* The uri_ object will be used to encode the payload that is sent to the
|
||||
* server.
|
||||
* @type {goog.Uri}
|
||||
* @private
|
||||
*/
|
||||
this.uri_ = new goog.Uri(uri);
|
||||
|
||||
/**
|
||||
* This is the callback parameter name that is added to the uri.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.callbackParamName_ = opt_callbackParamName ?
|
||||
opt_callbackParamName : 'callback';
|
||||
|
||||
/**
|
||||
* The length of time, in milliseconds, this channel is prepared
|
||||
* to wait for for a request to complete. The default value is 5 seconds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.timeout_ = 5000;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The name of the property of goog.global under which the callback is
|
||||
* stored.
|
||||
*/
|
||||
goog.net.Jsonp.CALLBACKS = '_callbacks_';
|
||||
|
||||
|
||||
/**
|
||||
* Used to generate unique callback IDs. The counter must be global because
|
||||
* all channels share a common callback object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Jsonp.scriptCounter_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the length of time, in milliseconds, this channel is prepared
|
||||
* to wait for for a request to complete. If the call is not competed
|
||||
* within the set time span, it is assumed to have failed. To wait
|
||||
* indefinitely for a request to complete set the timout to a negative
|
||||
* number.
|
||||
*
|
||||
* @param {number} timeout The length of time before calls are
|
||||
* interrupted.
|
||||
*/
|
||||
goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
|
||||
this.timeout_ = timeout;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current timeout value, in milliseconds.
|
||||
*
|
||||
* @return {number} The timeout value.
|
||||
*/
|
||||
goog.net.Jsonp.prototype.getRequestTimeout = function() {
|
||||
return this.timeout_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends the given payload to the URL specified at the construction
|
||||
* time. The reply is delivered to the given replyCallback. If the
|
||||
* errorCallback is specified and the reply does not arrive within the
|
||||
* timeout period set on this channel, the errorCallback is invoked
|
||||
* with the original payload.
|
||||
*
|
||||
* If no reply callback is specified, then the response is expected to
|
||||
* consist of calls to globally registered functions. No &callback=
|
||||
* URL parameter will be sent in the request, and the script element
|
||||
* will be cleaned up after the timeout.
|
||||
*
|
||||
* @param {Object=} opt_payload Name-value pairs. If given, these will be
|
||||
* added as parameters to the supplied URI as GET parameters to the
|
||||
* given server URI.
|
||||
*
|
||||
* @param {Function=} opt_replyCallback A function expecting one
|
||||
* argument, called when the reply arrives, with the response data.
|
||||
*
|
||||
* @param {Function=} opt_errorCallback A function expecting one
|
||||
* argument, called on timeout, with the payload (if given), otherwise
|
||||
* null.
|
||||
*
|
||||
* @param {string=} opt_callbackParamValue Value to be used as the
|
||||
* parameter value for the callback parameter (callbackParamName).
|
||||
* To be used when the value needs to be fixed by the client for a
|
||||
* particular request, to make use of the cached responses for the request.
|
||||
* NOTE: If multiple requests are made with the same
|
||||
* opt_callbackParamValue, only the last call will work whenever the
|
||||
* response comes back.
|
||||
*
|
||||
* @return {Object} A request descriptor that may be used to cancel this
|
||||
* transmission, or null, if the message may not be cancelled.
|
||||
*/
|
||||
goog.net.Jsonp.prototype.send = function(opt_payload,
|
||||
opt_replyCallback,
|
||||
opt_errorCallback,
|
||||
opt_callbackParamValue) {
|
||||
|
||||
var payload = opt_payload || null;
|
||||
|
||||
var id = opt_callbackParamValue ||
|
||||
'_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
|
||||
goog.now().toString(36);
|
||||
|
||||
if (!goog.global[goog.net.Jsonp.CALLBACKS]) {
|
||||
goog.global[goog.net.Jsonp.CALLBACKS] = {};
|
||||
}
|
||||
|
||||
// Create a new Uri object onto which this payload will be added
|
||||
var uri = this.uri_.clone();
|
||||
if (payload) {
|
||||
goog.net.Jsonp.addPayloadToUri_(payload, uri);
|
||||
}
|
||||
|
||||
if (opt_replyCallback) {
|
||||
var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback);
|
||||
goog.global[goog.net.Jsonp.CALLBACKS][id] = reply;
|
||||
|
||||
uri.setParameterValues(this.callbackParamName_,
|
||||
goog.net.Jsonp.CALLBACKS + '.' + id);
|
||||
}
|
||||
|
||||
var deferred = goog.net.jsloader.load(uri.toString(),
|
||||
{timeout: this.timeout_, cleanupWhenDone: true});
|
||||
var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback);
|
||||
deferred.addErrback(error);
|
||||
|
||||
return {id_: id, deferred_: deferred};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cancels a given request. The request must be exactly the object returned by
|
||||
* the send method.
|
||||
*
|
||||
* @param {Object} request The request object returned by the send method.
|
||||
*/
|
||||
goog.net.Jsonp.prototype.cancel = function(request) {
|
||||
if (request) {
|
||||
if (request.deferred_) {
|
||||
request.deferred_.cancel();
|
||||
}
|
||||
if (request.id_) {
|
||||
goog.net.Jsonp.cleanup_(request.id_, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a timeout callback that calls the given timeoutCallback with the
|
||||
* original payload.
|
||||
*
|
||||
* @param {string} id The id of the script node.
|
||||
* @param {Object} payload The payload that was sent to the server.
|
||||
* @param {Function=} opt_errorCallback The function called on timeout.
|
||||
* @return {!Function} A zero argument function that handles callback duties.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Jsonp.newErrorHandler_ = function(id,
|
||||
payload,
|
||||
opt_errorCallback) {
|
||||
/**
|
||||
* When we call across domains with a request, this function is the
|
||||
* timeout handler. Once it's done executing the user-specified
|
||||
* error-handler, it removes the script node and original function.
|
||||
*/
|
||||
return function() {
|
||||
goog.net.Jsonp.cleanup_(id, false);
|
||||
if (opt_errorCallback) {
|
||||
opt_errorCallback(payload);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a reply callback that calls the given replyCallback with data
|
||||
* returned by the server.
|
||||
*
|
||||
* @param {string} id The id of the script node.
|
||||
* @param {Function} replyCallback The function called on reply.
|
||||
* @return {Function} A reply callback function.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) {
|
||||
/**
|
||||
* This function is the handler for the all-is-well response. It
|
||||
* clears the error timeout handler, calls the user's handler, then
|
||||
* removes the script node and itself.
|
||||
*
|
||||
* @param {...Object} var_args The response data sent from the server.
|
||||
*/
|
||||
return function(var_args) {
|
||||
goog.net.Jsonp.cleanup_(id, true);
|
||||
replyCallback.apply(undefined, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the script node and reply handler with the given id.
|
||||
*
|
||||
* @param {string} id The id of the script node to be removed.
|
||||
* @param {boolean} deleteReplyHandler If true, delete the reply handler
|
||||
* instead of setting it to nullFunction (if we know the callback could
|
||||
* never be called again).
|
||||
* @private
|
||||
*/
|
||||
goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) {
|
||||
if (goog.global[goog.net.Jsonp.CALLBACKS][id]) {
|
||||
if (deleteReplyHandler) {
|
||||
delete goog.global[goog.net.Jsonp.CALLBACKS][id];
|
||||
} else {
|
||||
// Removing the script tag doesn't necessarily prevent the script
|
||||
// from firing, so we make the callback a noop.
|
||||
goog.global[goog.net.Jsonp.CALLBACKS][id] = goog.nullFunction;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns URL encoded payload. The payload should be a map of name-value
|
||||
* pairs, in the form {"foo": 1, "bar": true, ...}. If the map is empty,
|
||||
* the URI will be unchanged.
|
||||
*
|
||||
* <p>The method uses hasOwnProperty() to assure the properties are on the
|
||||
* object, not on its prototype.
|
||||
*
|
||||
* @param {!Object} payload A map of value name pairs to be encoded.
|
||||
* A value may be specified as an array, in which case a query parameter
|
||||
* will be created for each value, e.g.:
|
||||
* {"foo": [1,2]} will encode to "foo=1&foo=2".
|
||||
*
|
||||
* @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
|
||||
* will be encoded.
|
||||
*
|
||||
* @return {!goog.Uri} A reference to the Uri sent as a parameter.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
|
||||
for (var name in payload) {
|
||||
// NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
|
||||
// case, we iterate over all properties as a very lame workaround.
|
||||
if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
|
||||
uri.setParameterValues(name, payload[name]);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
//
|
||||
// This class allows us (Google) to send data from non-Google and thus
|
||||
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
|
||||
// anything sensitive, such as session or cookie specific data. Return
|
||||
// only data that you want parties external to Google to have. Also
|
||||
// NEVER use this method to send data from web pages to untrusted
|
||||
// servers, or redirects to unknown servers (www.google.com/cache,
|
||||
// /q=xx&btnl, /url, www.googlepages.com, etc.)
|
||||
//
|
||||
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
@@ -0,0 +1,319 @@
|
||||
// Copyright 2007 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 Mock of IframeIo for unit testing.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.MockIFrameIo');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.net.ErrorCode');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.net.IframeIo');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Mock implenetation of goog.net.IframeIo. This doesn't provide a mock
|
||||
* implementation for all cases, but it's not too hard to add them as needed.
|
||||
* @param {goog.testing.TestQueue} testQueue Test queue for inserting test
|
||||
* events.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.net.MockIFrameIo = function(testQueue) {
|
||||
goog.events.EventTarget.call(this);
|
||||
|
||||
/**
|
||||
* Queue of events write to
|
||||
* @type {goog.testing.TestQueue}
|
||||
* @private
|
||||
*/
|
||||
this.testQueue_ = testQueue;
|
||||
|
||||
};
|
||||
goog.inherits(goog.net.MockIFrameIo, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Whether MockIFrameIo is active.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.active_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Last content.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.lastContent_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* Last error code.
|
||||
* @type {goog.net.ErrorCode}
|
||||
* @private
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
|
||||
|
||||
|
||||
/**
|
||||
* Last error message.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.lastError_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* Last custom error.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.lastCustomError_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Last URI.
|
||||
* @type {goog.Uri}
|
||||
* @private
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.lastUri_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Simulates the iframe send.
|
||||
*
|
||||
* @param {goog.Uri|string} uri Uri of the request.
|
||||
* @param {string=} opt_method Default is GET, POST uses a form to submit the
|
||||
* request.
|
||||
* @param {boolean=} opt_noCache Append a timestamp to the request to avoid
|
||||
* caching.
|
||||
* @param {Object|goog.structs.Map=} opt_data Map of key-value pairs.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.send = function(uri, opt_method, opt_noCache,
|
||||
opt_data) {
|
||||
if (this.active_) {
|
||||
throw Error('[goog.net.IframeIo] Unable to send, already active.');
|
||||
}
|
||||
|
||||
this.testQueue_.enqueue(['s', uri, opt_method, opt_noCache, opt_data]);
|
||||
this.complete_ = false;
|
||||
this.active_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates the iframe send from a form.
|
||||
* @param {Element} form Form element used to send the request to the server.
|
||||
* @param {string=} opt_uri Uri to set for the destination of the request, by
|
||||
* default the uri will come from the form.
|
||||
* @param {boolean=} opt_noCache Append a timestamp to the request to avoid
|
||||
* caching.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.sendFromForm = function(form, opt_uri,
|
||||
opt_noCache) {
|
||||
if (this.active_) {
|
||||
throw Error('[goog.net.IframeIo] Unable to send, already active.');
|
||||
}
|
||||
|
||||
this.testQueue_.enqueue(['s', form, opt_uri, opt_noCache]);
|
||||
this.complete_ = false;
|
||||
this.active_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates aborting the current Iframe request.
|
||||
* @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use -
|
||||
* defaults to ABORT.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.abort = function(opt_failureCode) {
|
||||
if (this.active_) {
|
||||
this.testQueue_.enqueue(['a', opt_failureCode]);
|
||||
this.complete_ = false;
|
||||
this.active_ = false;
|
||||
this.success_ = false;
|
||||
this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT;
|
||||
this.dispatchEvent(goog.net.EventType.ABORT);
|
||||
this.simulateReady();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates receive of incremental data.
|
||||
* @param {Object} data Data.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.simulateIncrementalData = function(data) {
|
||||
this.dispatchEvent(new goog.net.IframeIo.IncrementalDataEvent(data));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates the iframe is done.
|
||||
* @param {goog.net.ErrorCode} errorCode The error code for any error that
|
||||
* should be simulated.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.simulateDone = function(errorCode) {
|
||||
if (errorCode) {
|
||||
this.success_ = false;
|
||||
this.lastErrorCode_ = goog.net.ErrorCode.HTTP_ERROR;
|
||||
this.lastError_ = this.getLastError();
|
||||
this.dispatchEvent(goog.net.EventType.ERROR);
|
||||
} else {
|
||||
this.success_ = true;
|
||||
this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR;
|
||||
this.dispatchEvent(goog.net.EventType.SUCCESS);
|
||||
}
|
||||
this.complete_ = true;
|
||||
this.dispatchEvent(goog.net.EventType.COMPLETE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Simulates the IFrame is ready for the next request.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.simulateReady = function() {
|
||||
this.dispatchEvent(goog.net.EventType.READY);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if transfer is complete.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.isComplete = function() {
|
||||
return this.complete_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if transfer was successful.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.isSuccess = function() {
|
||||
return this.success_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if a transfer is in progress.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.isActive = function() {
|
||||
return this.active_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last response text (i.e. the text content of the iframe).
|
||||
* Assumes plain text!
|
||||
* @return {string} Result from the server.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getResponseText = function() {
|
||||
return this.lastContent_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses the content as JSON. This is a safe parse and may throw an error
|
||||
* if the response is malformed.
|
||||
* @return {Object} The parsed content.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getResponseJson = function() {
|
||||
return goog.json.parse(this.lastContent_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the uri of the last request.
|
||||
* @return {goog.Uri} Uri of last request.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getLastUri = function() {
|
||||
return this.lastUri_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the last error code.
|
||||
* @return {goog.net.ErrorCode} Last error code.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getLastErrorCode = function() {
|
||||
return this.lastErrorCode_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the last error message.
|
||||
* @return {string} Last error message.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getLastError = function() {
|
||||
return goog.net.ErrorCode.getDebugMessage(this.lastErrorCode_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the last custom error.
|
||||
* @return {Object} Last custom error.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getLastCustomError = function() {
|
||||
return this.lastCustomError_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the callback function used to check if a loaded IFrame is in an error
|
||||
* state.
|
||||
* @param {Function} fn Callback that expects a document object as it's single
|
||||
* argument.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.setErrorChecker = function(fn) {
|
||||
this.errorChecker_ = fn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the callback function used to check if a loaded IFrame is in an error
|
||||
* state.
|
||||
* @return {Function} A callback that expects a document object as it's single
|
||||
* argument.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getErrorChecker = function() {
|
||||
return this.errorChecker_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of milliseconds after which an incomplete request will be
|
||||
* aborted, or 0 if no timeout is set.
|
||||
* @return {number} Timeout interval in milliseconds.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.getTimeoutInterval = function() {
|
||||
return this.timeoutInterval_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of milliseconds after which an incomplete request will be
|
||||
* aborted and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no
|
||||
* timeout is set.
|
||||
* @param {number} ms Timeout interval in milliseconds; 0 means none.
|
||||
*/
|
||||
goog.net.MockIFrameIo.prototype.setTimeoutInterval = function(ms) {
|
||||
// TODO (pupius) - never used - doesn't look like timeouts were implemented
|
||||
this.timeoutInterval_ = Math.max(0, ms);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2008 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 Class that can be used to determine when multiple iframes have
|
||||
* been loaded. Refactored from static APIs in IframeLoadMonitor.
|
||||
*/
|
||||
goog.provide('goog.net.MultiIframeLoadMonitor');
|
||||
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.net.IframeLoadMonitor');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a wrapper around IframeLoadMonitor, to allow the caller to wait for
|
||||
* multiple iframes to load.
|
||||
*
|
||||
* @param {Array.<HTMLIFrameElement>} iframes Array of iframe elements to
|
||||
* wait until they are loaded.
|
||||
* @param {function():void} callback The callback to invoke once the frames have
|
||||
* loaded.
|
||||
* @param {boolean=} opt_hasContent true if the monitor should wait until the
|
||||
* iframes have content (body.firstChild != null).
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.MultiIframeLoadMonitor = function(iframes, callback, opt_hasContent) {
|
||||
/**
|
||||
* Array of IframeLoadMonitors we use to track the loaded status of any
|
||||
* currently unloaded iframes.
|
||||
* @type {Array.<goog.net.IframeLoadMonitor>}
|
||||
* @private
|
||||
*/
|
||||
this.pendingIframeLoadMonitors_ = [];
|
||||
|
||||
/**
|
||||
* Callback which is invoked when all of the iframes are loaded.
|
||||
* @type {function():void}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = callback;
|
||||
|
||||
for (var i = 0; i < iframes.length; i++) {
|
||||
var iframeLoadMonitor = new goog.net.IframeLoadMonitor(
|
||||
iframes[i], opt_hasContent);
|
||||
if (iframeLoadMonitor.isLoaded()) {
|
||||
// Already loaded - don't need to wait
|
||||
iframeLoadMonitor.dispose();
|
||||
} else {
|
||||
// Iframe isn't loaded yet - register to be notified when it is
|
||||
// loaded, and track this monitor so we can dispose later as
|
||||
// required.
|
||||
this.pendingIframeLoadMonitors_.push(iframeLoadMonitor);
|
||||
goog.events.listen(
|
||||
iframeLoadMonitor, goog.net.IframeLoadMonitor.LOAD_EVENT, this);
|
||||
}
|
||||
}
|
||||
if (!this.pendingIframeLoadMonitors_.length) {
|
||||
// All frames were already loaded
|
||||
this.callback_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles a pending iframe load monitor load event.
|
||||
* @param {goog.events.Event} e The goog.net.IframeLoadMonitor.LOAD_EVENT event.
|
||||
*/
|
||||
goog.net.MultiIframeLoadMonitor.prototype.handleEvent = function(e) {
|
||||
var iframeLoadMonitor = e.target;
|
||||
// iframeLoadMonitor is now loaded, remove it from the array of
|
||||
// pending iframe load monitors.
|
||||
for (var i = 0; i < this.pendingIframeLoadMonitors_.length; i++) {
|
||||
if (this.pendingIframeLoadMonitors_[i] == iframeLoadMonitor) {
|
||||
this.pendingIframeLoadMonitors_.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Disposes of the iframe load monitor. We created this iframe load monitor
|
||||
// and installed the single listener on it, so it is safe to dispose it
|
||||
// in the middle of this event handler.
|
||||
iframeLoadMonitor.dispose();
|
||||
|
||||
// If there are no more pending iframe load monitors, all the iframes
|
||||
// have loaded, and so we invoke the callback.
|
||||
if (!this.pendingIframeLoadMonitors_.length) {
|
||||
this.callback_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops monitoring the iframes, cleaning up any associated resources. In
|
||||
* general, the object cleans up its own resources before invoking the
|
||||
* callback, so this API should only be used if the caller wants to stop the
|
||||
* monitoring before the iframes are loaded (for example, if the caller is
|
||||
* implementing a timeout).
|
||||
*/
|
||||
goog.net.MultiIframeLoadMonitor.prototype.stopMonitoring = function() {
|
||||
for (var i = 0; i < this.pendingIframeLoadMonitors_.length; i++) {
|
||||
this.pendingIframeLoadMonitors_[i].dispose();
|
||||
}
|
||||
this.pendingIframeLoadMonitors_.length = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2012 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 class for objects monitoring and exposing runtime
|
||||
* network status information.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.NetworkStatusMonitor');
|
||||
|
||||
goog.require('goog.events.Listenable');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class for network status information providers.
|
||||
* @interface
|
||||
* @extends {goog.events.Listenable}
|
||||
*/
|
||||
goog.net.NetworkStatusMonitor = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Enum for the events dispatched by the OnlineHandler.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.net.NetworkStatusMonitor.EventType = {
|
||||
ONLINE: 'online',
|
||||
OFFLINE: 'offline'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the system is online or otherwise.
|
||||
*/
|
||||
goog.net.NetworkStatusMonitor.prototype.isOnline;
|
||||
@@ -0,0 +1,383 @@
|
||||
// Copyright 2007 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 goog.net.NetworkTester.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.NetworkTester');
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.log');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of goog.net.NetworkTester which can be used to test
|
||||
* for internet connectivity by seeing if an image can be loaded from
|
||||
* google.com. It can also be tested with other URLs.
|
||||
* @param {Function} callback Callback that is called when the test completes.
|
||||
* The callback takes a single boolean parameter. True indicates the URL
|
||||
* was reachable, false indicates it wasn't.
|
||||
* @param {Object=} opt_handler Handler object for the callback.
|
||||
* @param {goog.Uri=} opt_uri URI to use for testing.
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.NetworkTester = function(callback, opt_handler, opt_uri) {
|
||||
/**
|
||||
* Callback that is called when the test completes.
|
||||
* The callback takes a single boolean parameter. True indicates the URL was
|
||||
* reachable, false indicates it wasn't.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = callback;
|
||||
|
||||
/**
|
||||
* Handler object for the callback.
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
if (!opt_uri) {
|
||||
// set the default URI to be based on the cleardot image at google.com
|
||||
// We need to add a 'rand' to make sure the response is not fulfilled
|
||||
// by browser cache. Use protocol-relative URLs to avoid insecure content
|
||||
// warnings in IE.
|
||||
opt_uri = new goog.Uri('//www.google.com/images/cleardot.gif');
|
||||
opt_uri.makeUnique();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uri to use for test. Defaults to using an image off of google.com
|
||||
* @type {goog.Uri}
|
||||
* @private
|
||||
*/
|
||||
this.uri_ = opt_uri;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default timeout
|
||||
* @type {number}
|
||||
*/
|
||||
goog.net.NetworkTester.DEFAULT_TIMEOUT_MS = 10000;
|
||||
|
||||
|
||||
/**
|
||||
* Logger object
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.logger_ =
|
||||
goog.log.getLogger('goog.net.NetworkTester');
|
||||
|
||||
|
||||
/**
|
||||
* Timeout for test
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.timeoutMs_ =
|
||||
goog.net.NetworkTester.DEFAULT_TIMEOUT_MS;
|
||||
|
||||
|
||||
/**
|
||||
* Whether we've already started running.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.running_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Number of retries to attempt
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.retries_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Attempt number we're on
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.attempt_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Pause between retries in milliseconds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.pauseBetweenRetriesMs_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for timeouts.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.timeoutTimer_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for pauses between retries.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.pauseTimer_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the timeout in milliseconds.
|
||||
* @return {number} Timeout in milliseconds.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.getTimeout = function() {
|
||||
return this.timeoutMs_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the timeout in milliseconds.
|
||||
* @param {number} timeoutMs Timeout in milliseconds.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.setTimeout = function(timeoutMs) {
|
||||
this.timeoutMs_ = timeoutMs;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the numer of retries to attempt.
|
||||
* @return {number} Number of retries to attempt.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.getNumRetries = function() {
|
||||
return this.retries_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the timeout in milliseconds.
|
||||
* @param {number} retries Number of retries to attempt.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.setNumRetries = function(retries) {
|
||||
this.retries_ = retries;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the pause between retries in milliseconds.
|
||||
* @return {number} Pause between retries in milliseconds.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.getPauseBetweenRetries = function() {
|
||||
return this.pauseBetweenRetriesMs_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the pause between retries in milliseconds.
|
||||
* @param {number} pauseMs Pause between retries in milliseconds.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.setPauseBetweenRetries = function(pauseMs) {
|
||||
this.pauseBetweenRetriesMs_ = pauseMs;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the uri to use for the test.
|
||||
* @return {goog.Uri} The uri for the test.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.getUri = function() {
|
||||
return this.uri_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the uri to use for the test.
|
||||
* @param {goog.Uri} uri The uri for the test.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.setUri = function(uri) {
|
||||
this.uri_ = uri;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the tester is currently running.
|
||||
* @return {boolean} True if it's running, false if it's not running.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.isRunning = function() {
|
||||
return this.running_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the process of testing the network.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.start = function() {
|
||||
if (this.running_) {
|
||||
throw Error('NetworkTester.start called when already running');
|
||||
}
|
||||
this.running_ = true;
|
||||
|
||||
goog.log.info(this.logger_, 'Starting');
|
||||
this.attempt_ = 0;
|
||||
this.startNextAttempt_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the testing of the network. This is a noop if not running.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.stop = function() {
|
||||
this.cleanupCallbacks_();
|
||||
this.running_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the next attempt to load an image.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.startNextAttempt_ = function() {
|
||||
this.attempt_++;
|
||||
|
||||
if (goog.net.NetworkTester.getNavigatorOffline_()) {
|
||||
goog.log.info(this.logger_, 'Browser is set to work offline.');
|
||||
// Call in a timeout to make async like the rest.
|
||||
goog.Timer.callOnce(goog.bind(this.onResult, this, false), 0);
|
||||
} else {
|
||||
goog.log.info(this.logger_, 'Loading image (attempt ' + this.attempt_ +
|
||||
') at ' + this.uri_);
|
||||
this.image_ = new Image();
|
||||
this.image_.onload = goog.bind(this.onImageLoad_, this);
|
||||
this.image_.onerror = goog.bind(this.onImageError_, this);
|
||||
this.image_.onabort = goog.bind(this.onImageAbort_, this);
|
||||
|
||||
this.timeoutTimer_ = goog.Timer.callOnce(this.onImageTimeout_,
|
||||
this.timeoutMs_, this);
|
||||
this.image_.src = String(this.uri_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether navigator.onLine returns false.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.getNavigatorOffline_ = function() {
|
||||
return 'onLine' in navigator && !navigator.onLine;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for the image successfully loading.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.onImageLoad_ = function() {
|
||||
goog.log.info(this.logger_, 'Image loaded');
|
||||
this.onResult(true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for the image failing to load.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.onImageError_ = function() {
|
||||
goog.log.info(this.logger_, 'Image load error');
|
||||
this.onResult(false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for the image load being aborted.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.onImageAbort_ = function() {
|
||||
goog.log.info(this.logger_, 'Image load aborted');
|
||||
this.onResult(false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for the image load timing out.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.onImageTimeout_ = function() {
|
||||
goog.log.info(this.logger_, 'Image load timed out');
|
||||
this.onResult(false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles a successful or failed result.
|
||||
* @param {boolean} succeeded Whether the image load succeeded.
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.onResult = function(succeeded) {
|
||||
this.cleanupCallbacks_();
|
||||
|
||||
if (succeeded) {
|
||||
this.running_ = false;
|
||||
this.callback_.call(this.handler_, true);
|
||||
} else {
|
||||
if (this.attempt_ <= this.retries_) {
|
||||
if (this.pauseBetweenRetriesMs_) {
|
||||
this.pauseTimer_ = goog.Timer.callOnce(this.onPauseFinished_,
|
||||
this.pauseBetweenRetriesMs_, this);
|
||||
} else {
|
||||
this.startNextAttempt_();
|
||||
}
|
||||
} else {
|
||||
this.running_ = false;
|
||||
this.callback_.call(this.handler_, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback for the pause between retry timer.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.onPauseFinished_ = function() {
|
||||
this.pauseTimer_ = null;
|
||||
this.startNextAttempt_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up the handlers and timer associated with the image.
|
||||
* @private
|
||||
*/
|
||||
goog.net.NetworkTester.prototype.cleanupCallbacks_ = function() {
|
||||
// clear handlers to avoid memory leaks
|
||||
// NOTE(user): Nullified individually to avoid compiler warnings
|
||||
// (BUG 658126)
|
||||
if (this.image_) {
|
||||
this.image_.onload = null;
|
||||
this.image_.onerror = null;
|
||||
this.image_.onabort = null;
|
||||
this.image_ = null;
|
||||
}
|
||||
if (this.timeoutTimer_) {
|
||||
goog.Timer.clear(this.timeoutTimer_);
|
||||
this.timeoutTimer_ = null;
|
||||
}
|
||||
if (this.pauseTimer_) {
|
||||
goog.Timer.clear(this.pauseTimer_);
|
||||
this.pauseTimer_ = null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2011 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.
|
||||
// All Rights Reserved
|
||||
|
||||
/**
|
||||
* @fileoverview Test #1 of jsloader.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.testdata.jsloader_test1');
|
||||
goog.setTestOnly('jsloader_test1');
|
||||
|
||||
window['test1'] = 'Test #1 loaded';
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2011 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.
|
||||
// All Rights Reserved
|
||||
|
||||
/**
|
||||
* @fileoverview Test #2 of jsloader.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.testdata.jsloader_test2');
|
||||
goog.setTestOnly('jsloader_test2');
|
||||
|
||||
window['closure_verification']['test2'] = 'Test #2 loaded';
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2011 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.
|
||||
// All Rights Reserved
|
||||
|
||||
/**
|
||||
* @fileoverview Test #3 of jsloader.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.testdata.jsloader_test3');
|
||||
goog.setTestOnly('jsloader_test3');
|
||||
|
||||
window['test3Callback']('Test #3 loaded');
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2011 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.
|
||||
// All Rights Reserved
|
||||
|
||||
/**
|
||||
* @fileoverview Test #4 of jsloader.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.testdata.jsloader_test4');
|
||||
goog.setTestOnly('jsloader_test4');
|
||||
|
||||
window['test4Callback']('Test #4 loaded');
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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 tmpnetwork.js contains some temporary networking functions
|
||||
* for browserchannel which will be moved at a later date.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Namespace for BrowserChannel
|
||||
*/
|
||||
goog.provide('goog.net.tmpnetwork');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.net.ChannelDebug');
|
||||
|
||||
|
||||
/**
|
||||
* Default timeout to allow for google.com pings.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.net.tmpnetwork.GOOGLECOM_TIMEOUT = 10000;
|
||||
|
||||
|
||||
/**
|
||||
* Pings the network to check if an error is a server error or user's network
|
||||
* error.
|
||||
*
|
||||
* @param {Function} callback The function to call back with results.
|
||||
* @param {goog.Uri?=} opt_imageUri The URI of an image to use for the network
|
||||
* test. You *must* provide an image URI; the default behavior is provided
|
||||
* for compatibility with existing code, but the search team does not want
|
||||
* people using images served off of google.com for this purpose. The
|
||||
* default will go away when all usages have been changed.
|
||||
*/
|
||||
goog.net.tmpnetwork.testGoogleCom = function(callback, opt_imageUri) {
|
||||
// We need to add a 'rand' to make sure the response is not fulfilled
|
||||
// by browser cache.
|
||||
var uri = opt_imageUri;
|
||||
if (!uri) {
|
||||
uri = new goog.Uri('//www.google.com/images/cleardot.gif');
|
||||
uri.makeUnique();
|
||||
}
|
||||
goog.net.tmpnetwork.testLoadImage(uri.toString(),
|
||||
goog.net.tmpnetwork.GOOGLECOM_TIMEOUT, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Test loading the given image, retrying if necessary.
|
||||
* @param {string} url URL to the iamge.
|
||||
* @param {number} timeout Milliseconds before giving up.
|
||||
* @param {Function} callback Function to call with results.
|
||||
* @param {number} retries The number of times to retry.
|
||||
* @param {number=} opt_pauseBetweenRetriesMS Optional number of milliseconds
|
||||
* between retries - defaults to 0.
|
||||
*/
|
||||
goog.net.tmpnetwork.testLoadImageWithRetries = function(url, timeout, callback,
|
||||
retries, opt_pauseBetweenRetriesMS) {
|
||||
var channelDebug = new goog.net.ChannelDebug();
|
||||
channelDebug.debug('TestLoadImageWithRetries: ' + opt_pauseBetweenRetriesMS);
|
||||
if (retries == 0) {
|
||||
// no more retries, give up
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var pauseBetweenRetries = opt_pauseBetweenRetriesMS || 0;
|
||||
retries--;
|
||||
goog.net.tmpnetwork.testLoadImage(url, timeout, function(succeeded) {
|
||||
if (succeeded) {
|
||||
callback(true);
|
||||
} else {
|
||||
// try again
|
||||
goog.global.setTimeout(function() {
|
||||
goog.net.tmpnetwork.testLoadImageWithRetries(url, timeout, callback,
|
||||
retries, pauseBetweenRetries);
|
||||
}, pauseBetweenRetries);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Test loading the given image.
|
||||
* @param {string} url URL to the iamge.
|
||||
* @param {number} timeout Milliseconds before giving up.
|
||||
* @param {Function} callback Function to call with results.
|
||||
*/
|
||||
goog.net.tmpnetwork.testLoadImage = function(url, timeout, callback) {
|
||||
var channelDebug = new goog.net.ChannelDebug();
|
||||
channelDebug.debug('TestLoadImage: loading ' + url);
|
||||
var img = new Image();
|
||||
img.onload = function() {
|
||||
try {
|
||||
channelDebug.debug('TestLoadImage: loaded');
|
||||
goog.net.tmpnetwork.clearImageCallbacks_(img);
|
||||
callback(true);
|
||||
} catch (e) {
|
||||
channelDebug.dumpException(e);
|
||||
}
|
||||
};
|
||||
img.onerror = function() {
|
||||
try {
|
||||
channelDebug.debug('TestLoadImage: error');
|
||||
goog.net.tmpnetwork.clearImageCallbacks_(img);
|
||||
callback(false);
|
||||
} catch (e) {
|
||||
channelDebug.dumpException(e);
|
||||
}
|
||||
};
|
||||
img.onabort = function() {
|
||||
try {
|
||||
channelDebug.debug('TestLoadImage: abort');
|
||||
goog.net.tmpnetwork.clearImageCallbacks_(img);
|
||||
callback(false);
|
||||
} catch (e) {
|
||||
channelDebug.dumpException(e);
|
||||
}
|
||||
};
|
||||
img.ontimeout = function() {
|
||||
try {
|
||||
channelDebug.debug('TestLoadImage: timeout');
|
||||
goog.net.tmpnetwork.clearImageCallbacks_(img);
|
||||
callback(false);
|
||||
} catch (e) {
|
||||
channelDebug.dumpException(e);
|
||||
}
|
||||
};
|
||||
|
||||
goog.global.setTimeout(function() {
|
||||
if (img.ontimeout) {
|
||||
img.ontimeout();
|
||||
}
|
||||
}, timeout);
|
||||
img.src = url;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clear handlers to avoid memory leaks.
|
||||
* @param {Image} img The image to clear handlers from.
|
||||
* @private
|
||||
*/
|
||||
goog.net.tmpnetwork.clearImageCallbacks_ = function(img) {
|
||||
// NOTE(user): Nullified individually to avoid compiler warnings
|
||||
// (BUG 658126)
|
||||
img.onload = null;
|
||||
img.onerror = null;
|
||||
img.onabort = null;
|
||||
img.ontimeout = null;
|
||||
};
|
||||
@@ -0,0 +1,505 @@
|
||||
// Copyright 2011 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 WebSocket class. A WebSocket provides a
|
||||
* bi-directional, full-duplex communications channel, over a single TCP socket.
|
||||
*
|
||||
* See http://dev.w3.org/html5/websockets/
|
||||
* for the full HTML5 WebSocket API.
|
||||
*
|
||||
* Typical usage will look like this:
|
||||
*
|
||||
* var ws = new goog.net.WebSocket();
|
||||
*
|
||||
* var handler = new goog.events.EventHandler();
|
||||
* handler.listen(ws, goog.net.WebSocket.EventType.OPENED, onOpen);
|
||||
* handler.listen(ws, goog.net.WebSocket.EventType.MESSAGE, onMessage);
|
||||
*
|
||||
* try {
|
||||
* ws.open('ws://127.0.0.1:4200');
|
||||
* } catch (e) {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.WebSocket');
|
||||
goog.provide('goog.net.WebSocket.ErrorEvent');
|
||||
goog.provide('goog.net.WebSocket.EventType');
|
||||
goog.provide('goog.net.WebSocket.MessageEvent');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.log');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class encapsulating the logic for using a WebSocket.
|
||||
*
|
||||
* @param {boolean=} opt_autoReconnect True if the web socket should
|
||||
* automatically reconnect or not. This is true by default.
|
||||
* @param {function(number):number=} opt_getNextReconnect A function for
|
||||
* obtaining the time until the next reconnect attempt. Given the reconnect
|
||||
* attempt count (which is a positive integer), the function should return a
|
||||
* positive integer representing the milliseconds to the next reconnect
|
||||
* attempt. The default function used is an exponential back-off. Note that
|
||||
* this function is never called if auto reconnect is disabled.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.net.WebSocket = function(opt_autoReconnect, opt_getNextReconnect) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* True if the web socket should automatically reconnect or not.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.autoReconnect_ = goog.isDef(opt_autoReconnect) ?
|
||||
opt_autoReconnect : true;
|
||||
|
||||
/**
|
||||
* A function for obtaining the time until the next reconnect attempt.
|
||||
* Given the reconnect attempt count (which is a positive integer), the
|
||||
* function should return a positive integer representing the milliseconds to
|
||||
* the next reconnect attempt.
|
||||
* @type {function(number):number}
|
||||
* @private
|
||||
*/
|
||||
this.getNextReconnect_ = opt_getNextReconnect ||
|
||||
goog.net.WebSocket.EXPONENTIAL_BACKOFF_;
|
||||
|
||||
/**
|
||||
* The time, in milliseconds, that must elapse before the next attempt to
|
||||
* reconnect.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
|
||||
};
|
||||
goog.inherits(goog.net.WebSocket, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The actual web socket that will be used to send/receive messages.
|
||||
* @type {WebSocket}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.webSocket_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The URL to which the web socket will connect.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.url_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The subprotocol name used when establishing the web socket connection.
|
||||
* @type {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.protocol_ = undefined;
|
||||
|
||||
|
||||
/**
|
||||
* True if a call to the close callback is expected or not.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.closeExpected_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Keeps track of the number of reconnect attempts made since the last
|
||||
* successful connection.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.reconnectAttempt_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The logger for this class.
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.logger_ = goog.log.getLogger(
|
||||
'goog.net.WebSocket');
|
||||
|
||||
|
||||
/**
|
||||
* The events fired by the web socket.
|
||||
* @enum {string} The event types for the web socket.
|
||||
*/
|
||||
goog.net.WebSocket.EventType = {
|
||||
|
||||
/**
|
||||
* Fired when an attempt to open the WebSocket fails or there is a connection
|
||||
* failure after a successful connection has been established.
|
||||
*/
|
||||
CLOSED: goog.events.getUniqueId('closed'),
|
||||
|
||||
/**
|
||||
* Fired when the WebSocket encounters an error.
|
||||
*/
|
||||
ERROR: goog.events.getUniqueId('error'),
|
||||
|
||||
/**
|
||||
* Fired when a new message arrives from the WebSocket.
|
||||
*/
|
||||
MESSAGE: goog.events.getUniqueId('message'),
|
||||
|
||||
/**
|
||||
* Fired when the WebSocket connection has been established.
|
||||
*/
|
||||
OPENED: goog.events.getUniqueId('opened')
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The various states of the web socket.
|
||||
* @enum {number} The states of the web socket.
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.ReadyState_ = {
|
||||
// This is the initial state during construction.
|
||||
CONNECTING: 0,
|
||||
// This is when the socket is actually open and ready for data.
|
||||
OPEN: 1,
|
||||
// This is when the socket is in the middle of a close handshake.
|
||||
// Note that this is a valid state even if the OPEN state was never achieved.
|
||||
CLOSING: 2,
|
||||
// This is when the socket is actually closed.
|
||||
CLOSED: 3
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The maximum amount of time between reconnect attempts for the exponential
|
||||
* back-off in milliseconds.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_ = 60 * 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Computes the next reconnect time given the number of reconnect attempts since
|
||||
* the last successful connection.
|
||||
*
|
||||
* @param {number} attempt The number of reconnect attempts since the last
|
||||
* connection.
|
||||
* @return {number} The time, in milliseconds, until the next reconnect attempt.
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.EXPONENTIAL_BACKOFF_ = function(attempt) {
|
||||
var time = Math.pow(2, attempt) * 1000;
|
||||
return Math.min(time, goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs exception protection for all entry points introduced by
|
||||
* goog.net.WebSocket instances which are not protected by
|
||||
* {@link goog.debug.ErrorHandler#protectWindowSetTimeout},
|
||||
* {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or
|
||||
* {@link goog.events.protectBrowserEventEntryPoint}.
|
||||
*
|
||||
* @param {!goog.debug.ErrorHandler} errorHandler Error handler with which to
|
||||
* protect the entry points.
|
||||
*/
|
||||
goog.net.WebSocket.protectEntryPoints = function(errorHandler) {
|
||||
goog.net.WebSocket.prototype.onOpen_ = errorHandler.protectEntryPoint(
|
||||
goog.net.WebSocket.prototype.onOpen_);
|
||||
goog.net.WebSocket.prototype.onClose_ = errorHandler.protectEntryPoint(
|
||||
goog.net.WebSocket.prototype.onClose_);
|
||||
goog.net.WebSocket.prototype.onMessage_ = errorHandler.protectEntryPoint(
|
||||
goog.net.WebSocket.prototype.onMessage_);
|
||||
goog.net.WebSocket.prototype.onError_ = errorHandler.protectEntryPoint(
|
||||
goog.net.WebSocket.prototype.onError_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates and opens the actual WebSocket. Only call this after attaching the
|
||||
* appropriate listeners to this object. If listeners aren't registered, then
|
||||
* the {@code goog.net.WebSocket.EventType.OPENED} event might be missed.
|
||||
*
|
||||
* @param {string} url The URL to which to connect.
|
||||
* @param {string=} opt_protocol The subprotocol to use. The connection will
|
||||
* only be established if the server reports that it has selected this
|
||||
* subprotocol. The subprotocol name must all be a non-empty ASCII string
|
||||
* with no control characters and no spaces in them (i.e. only characters
|
||||
* in the range U+0021 to U+007E).
|
||||
*/
|
||||
goog.net.WebSocket.prototype.open = function(url, opt_protocol) {
|
||||
// Sanity check. This works only in modern browsers.
|
||||
goog.asserts.assert(goog.global['WebSocket'],
|
||||
'This browser does not support WebSocket');
|
||||
|
||||
// Don't do anything if the web socket is already open.
|
||||
goog.asserts.assert(!this.isOpen(), 'The WebSocket is already open');
|
||||
|
||||
// Clear any pending attempts to reconnect.
|
||||
this.clearReconnectTimer_();
|
||||
|
||||
// Construct the web socket.
|
||||
this.url_ = url;
|
||||
this.protocol_ = opt_protocol;
|
||||
|
||||
// This check has to be made otherwise you get protocol mismatch exceptions
|
||||
// for passing undefined, null, '', or [].
|
||||
if (this.protocol_) {
|
||||
goog.log.info(this.logger_, 'Opening the WebSocket on ' + this.url_ +
|
||||
' with protocol ' + this.protocol_);
|
||||
this.webSocket_ = new WebSocket(this.url_, this.protocol_);
|
||||
} else {
|
||||
goog.log.info(this.logger_, 'Opening the WebSocket on ' + this.url_);
|
||||
this.webSocket_ = new WebSocket(this.url_);
|
||||
}
|
||||
|
||||
// Register the event handlers. Note that it is not possible for these
|
||||
// callbacks to be missed because it is registered after the web socket is
|
||||
// instantiated. Because of the synchronous nature of JavaScript, this code
|
||||
// will execute before the browser creates the resource and makes any calls
|
||||
// to these callbacks.
|
||||
this.webSocket_.onopen = goog.bind(this.onOpen_, this);
|
||||
this.webSocket_.onclose = goog.bind(this.onClose_, this);
|
||||
this.webSocket_.onmessage = goog.bind(this.onMessage_, this);
|
||||
this.webSocket_.onerror = goog.bind(this.onError_, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Closes the web socket connection.
|
||||
*/
|
||||
goog.net.WebSocket.prototype.close = function() {
|
||||
|
||||
// Clear any pending attempts to reconnect.
|
||||
this.clearReconnectTimer_();
|
||||
|
||||
// Attempt to close only if the web socket was created.
|
||||
if (this.webSocket_) {
|
||||
goog.log.info(this.logger_, 'Closing the WebSocket.');
|
||||
|
||||
// Close is expected here since it was a direct call. Close is considered
|
||||
// unexpected when opening the connection fails or there is some other form
|
||||
// of connection loss after being connected.
|
||||
this.closeExpected_ = true;
|
||||
this.webSocket_.close();
|
||||
this.webSocket_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends the message over the web socket.
|
||||
*
|
||||
* @param {string} message The message to send.
|
||||
*/
|
||||
goog.net.WebSocket.prototype.send = function(message) {
|
||||
// Make sure the socket is ready to go before sending a message.
|
||||
goog.asserts.assert(this.isOpen(), 'Cannot send without an open socket');
|
||||
|
||||
// Send the message and let onError_ be called if it fails thereafter.
|
||||
this.webSocket_.send(message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if the web socket is open or not.
|
||||
*
|
||||
* @return {boolean} True if the web socket is open, false otherwise.
|
||||
*/
|
||||
goog.net.WebSocket.prototype.isOpen = function() {
|
||||
return !!this.webSocket_ &&
|
||||
this.webSocket_.readyState == goog.net.WebSocket.ReadyState_.OPEN;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when the web socket has connected.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.onOpen_ = function() {
|
||||
goog.log.info(this.logger_, 'WebSocket opened on ' + this.url_);
|
||||
this.dispatchEvent(goog.net.WebSocket.EventType.OPENED);
|
||||
|
||||
// Set the next reconnect interval.
|
||||
this.reconnectAttempt_ = 0;
|
||||
this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when the web socket has closed.
|
||||
*
|
||||
* @param {!Event} event The close event.
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.onClose_ = function(event) {
|
||||
goog.log.info(this.logger_, 'The WebSocket on ' + this.url_ + ' closed.');
|
||||
|
||||
// Firing this event allows handlers to query the URL.
|
||||
this.dispatchEvent(goog.net.WebSocket.EventType.CLOSED);
|
||||
|
||||
// Always clear out the web socket on a close event.
|
||||
this.webSocket_ = null;
|
||||
|
||||
// See if this is an expected call to onClose_.
|
||||
if (this.closeExpected_) {
|
||||
goog.log.info(this.logger_, 'The WebSocket closed normally.');
|
||||
// Only clear out the URL if this is a normal close.
|
||||
this.url_ = null;
|
||||
this.protocol_ = undefined;
|
||||
} else {
|
||||
// Unexpected, so try to reconnect.
|
||||
goog.log.error(this.logger_, 'The WebSocket disconnected unexpectedly: ' +
|
||||
event.data);
|
||||
|
||||
// Only try to reconnect if it is enabled.
|
||||
if (this.autoReconnect_) {
|
||||
// Log the reconnect attempt.
|
||||
var seconds = Math.floor(this.nextReconnect_ / 1000);
|
||||
goog.log.info(this.logger_,
|
||||
'Seconds until next reconnect attempt: ' + seconds);
|
||||
|
||||
// Actually schedule the timer.
|
||||
this.reconnectTimer_ = goog.Timer.callOnce(
|
||||
goog.bind(this.open, this, this.url_, this.protocol_),
|
||||
this.nextReconnect_, this);
|
||||
|
||||
// Set the next reconnect interval.
|
||||
this.reconnectAttempt_++;
|
||||
this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
|
||||
}
|
||||
}
|
||||
this.closeExpected_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when a new message arrives from the server.
|
||||
*
|
||||
* @param {MessageEvent} event The web socket message event.
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.onMessage_ = function(event) {
|
||||
var message = /** @type {string} */ (event.data);
|
||||
this.dispatchEvent(new goog.net.WebSocket.MessageEvent(message));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when there is any error in communication.
|
||||
*
|
||||
* @param {Event} event The error event containing the error data.
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.onError_ = function(event) {
|
||||
var data = /** @type {string} */ (event.data);
|
||||
goog.log.error(this.logger_, 'An error occurred: ' + data);
|
||||
this.dispatchEvent(new goog.net.WebSocket.ErrorEvent(data));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the reconnect timer.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.net.WebSocket.prototype.clearReconnectTimer_ = function() {
|
||||
if (goog.isDefAndNotNull(this.reconnectTimer_)) {
|
||||
goog.Timer.clear(this.reconnectTimer_);
|
||||
}
|
||||
this.reconnectTimer_ = null;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.WebSocket.prototype.disposeInternal = function() {
|
||||
goog.base(this, 'disposeInternal');
|
||||
this.close();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Object representing a new incoming message event.
|
||||
*
|
||||
* @param {string} message The raw message coming from the web socket.
|
||||
* @extends {goog.events.Event}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.WebSocket.MessageEvent = function(message) {
|
||||
goog.base(this, goog.net.WebSocket.EventType.MESSAGE);
|
||||
|
||||
/**
|
||||
* The new message from the web socket.
|
||||
* @type {string}
|
||||
*/
|
||||
this.message = message;
|
||||
};
|
||||
goog.inherits(goog.net.WebSocket.MessageEvent, goog.events.Event);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Object representing an error event. This is fired whenever an error occurs
|
||||
* on the web socket.
|
||||
*
|
||||
* @param {string} data The error data.
|
||||
* @extends {goog.events.Event}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.WebSocket.ErrorEvent = function(data) {
|
||||
goog.base(this, goog.net.WebSocket.EventType.ERROR);
|
||||
|
||||
/**
|
||||
* The error data coming from the web socket.
|
||||
* @type {string}
|
||||
*/
|
||||
this.data = data;
|
||||
};
|
||||
goog.inherits(goog.net.WebSocket.ErrorEvent, goog.events.Event);
|
||||
|
||||
|
||||
// Register the WebSocket as an entry point, so that it can be monitored for
|
||||
// exception handling, etc.
|
||||
goog.debug.entryPointRegistry.register(
|
||||
/**
|
||||
* @param {function(!Function): !Function} transformer The transforming
|
||||
* function.
|
||||
*/
|
||||
function(transformer) {
|
||||
goog.net.WebSocket.prototype.onOpen_ =
|
||||
transformer(goog.net.WebSocket.prototype.onOpen_);
|
||||
goog.net.WebSocket.prototype.onClose_ =
|
||||
transformer(goog.net.WebSocket.prototype.onClose_);
|
||||
goog.net.WebSocket.prototype.onMessage_ =
|
||||
transformer(goog.net.WebSocket.prototype.onMessage_);
|
||||
goog.net.WebSocket.prototype.onError_ =
|
||||
transformer(goog.net.WebSocket.prototype.onError_);
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2010 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 XmlHttpFactory which allows construction from
|
||||
* simple factory methods.
|
||||
* @author dbk@google.com (David Barrett-Kahn)
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.WrapperXmlHttpFactory');
|
||||
|
||||
goog.require('goog.net.XmlHttpFactory');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An xhr factory subclass which can be constructed using two factory methods.
|
||||
* This exists partly to allow the preservation of goog.net.XmlHttp.setFactory()
|
||||
* with an unchanged signature.
|
||||
* @param {function() : !(XMLHttpRequest|GearsHttpRequest)} xhrFactory A
|
||||
* function which returns a new XHR object.
|
||||
* @param {function() : !Object} optionsFactory A function which returns the
|
||||
* options associated with xhr objects from this factory.
|
||||
* @extends {goog.net.XmlHttpFactory}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) {
|
||||
goog.net.XmlHttpFactory.call(this);
|
||||
|
||||
/**
|
||||
* XHR factory method.
|
||||
* @type {function() : !(XMLHttpRequest|GearsHttpRequest)}
|
||||
* @private
|
||||
*/
|
||||
this.xhrFactory_ = xhrFactory;
|
||||
|
||||
/**
|
||||
* Options factory method.
|
||||
* @type {function() : !Object}
|
||||
* @private
|
||||
*/
|
||||
this.optionsFactory_ = optionsFactory;
|
||||
};
|
||||
goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() {
|
||||
return this.xhrFactory_();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() {
|
||||
return this.optionsFactory_();
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
// 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 Creates a pool of XhrIo objects to use. This allows multiple
|
||||
* XhrIo objects to be grouped together and requests will use next available
|
||||
* XhrIo object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.XhrIoPool');
|
||||
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.PriorityPool');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A pool of XhrIo objects.
|
||||
* @param {goog.structs.Map=} opt_headers Map of default headers to add to every
|
||||
* request.
|
||||
* @param {number=} opt_minCount Minimum number of objects (Default: 1).
|
||||
* @param {number=} opt_maxCount Maximum number of objects (Default: 10).
|
||||
* @constructor
|
||||
* @extends {goog.structs.PriorityPool}
|
||||
*/
|
||||
goog.net.XhrIoPool = function(opt_headers, opt_minCount, opt_maxCount) {
|
||||
goog.structs.PriorityPool.call(this, opt_minCount, opt_maxCount);
|
||||
|
||||
/**
|
||||
* Map of default headers to add to every request.
|
||||
* @type {goog.structs.Map|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.headers_ = opt_headers;
|
||||
};
|
||||
goog.inherits(goog.net.XhrIoPool, goog.structs.PriorityPool);
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of an XhrIo object to use in the pool.
|
||||
* @return {goog.net.XhrIo} The created object.
|
||||
* @override
|
||||
*/
|
||||
goog.net.XhrIoPool.prototype.createObject = function() {
|
||||
var xhrIo = new goog.net.XhrIo();
|
||||
var headers = this.headers_;
|
||||
if (headers) {
|
||||
goog.structs.forEach(headers, function(value, key) {
|
||||
xhrIo.headers.set(key, value);
|
||||
});
|
||||
}
|
||||
return xhrIo;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine if an object has become unusable and should not be used.
|
||||
* @param {Object} obj The object to test.
|
||||
* @return {boolean} Whether the object can be reused, which is true if the
|
||||
* object is not disposed and not active.
|
||||
* @override
|
||||
*/
|
||||
goog.net.XhrIoPool.prototype.objectCanBeReused = function(obj) {
|
||||
// An active XhrIo object should never be used.
|
||||
var xhr = /** @type {goog.net.XhrIo} */ (obj);
|
||||
return !xhr.isDisposed() && !xhr.isActive();
|
||||
};
|
||||
@@ -0,0 +1,787 @@
|
||||
// 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 Manages a pool of XhrIo's. This handles all the details of
|
||||
* dealing with the XhrPool and provides a simple interface for sending requests
|
||||
* and managing events.
|
||||
*
|
||||
* This class supports queueing & prioritization of requests (XhrIoPool
|
||||
* handles this) and retrying of requests.
|
||||
*
|
||||
* The events fired by the XhrManager are an aggregation of the events of
|
||||
* each of its XhrIo objects (with some filtering, i.e., ERROR only called
|
||||
* when there are no more retries left). For this reason, all send requests have
|
||||
* to have an id, so that the user of this object can know which event is for
|
||||
* which request.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.XhrManager');
|
||||
goog.provide('goog.net.XhrManager.Event');
|
||||
goog.provide('goog.net.XhrManager.Request');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.net.ErrorCode');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.net.XhrIoPool');
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.Map');
|
||||
|
||||
// TODO(user): Add some time in between retries.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A manager of an XhrIoPool.
|
||||
* @param {number=} opt_maxRetries Max. number of retries (Default: 1).
|
||||
* @param {goog.structs.Map=} opt_headers Map of default headers to add to every
|
||||
* request.
|
||||
* @param {number=} opt_minCount Min. number of objects (Default: 1).
|
||||
* @param {number=} opt_maxCount Max. number of objects (Default: 10).
|
||||
* @param {number=} opt_timeoutInterval Timeout (in ms) before aborting an
|
||||
* attempt (Default: 0ms).
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.net.XhrManager = function(
|
||||
opt_maxRetries,
|
||||
opt_headers,
|
||||
opt_minCount,
|
||||
opt_maxCount,
|
||||
opt_timeoutInterval) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Maximum number of retries for a given request
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.maxRetries_ = goog.isDef(opt_maxRetries) ? opt_maxRetries : 1;
|
||||
|
||||
/**
|
||||
* Timeout interval for an attempt of a given request.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.timeoutInterval_ =
|
||||
goog.isDef(opt_timeoutInterval) ? Math.max(0, opt_timeoutInterval) : 0;
|
||||
|
||||
/**
|
||||
* The pool of XhrIo's to use.
|
||||
* @type {goog.net.XhrIoPool}
|
||||
* @private
|
||||
*/
|
||||
this.xhrPool_ = new goog.net.XhrIoPool(
|
||||
opt_headers, opt_minCount, opt_maxCount);
|
||||
|
||||
/**
|
||||
* Map of ID's to requests.
|
||||
* @type {goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
this.requests_ = new goog.structs.Map();
|
||||
|
||||
/**
|
||||
* The event handler.
|
||||
* @type {goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
};
|
||||
goog.inherits(goog.net.XhrManager, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Error to throw when a send is attempted with an ID that the manager already
|
||||
* has registered for another request.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.ERROR_ID_IN_USE_ = '[goog.net.XhrManager] ID in use';
|
||||
|
||||
|
||||
/**
|
||||
* The goog.net.EventType's to listen/unlisten for on the XhrIo object.
|
||||
* @type {Array.<goog.net.EventType>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.XHR_EVENT_TYPES_ = [
|
||||
goog.net.EventType.READY,
|
||||
goog.net.EventType.COMPLETE,
|
||||
goog.net.EventType.SUCCESS,
|
||||
goog.net.EventType.ERROR,
|
||||
goog.net.EventType.ABORT,
|
||||
goog.net.EventType.TIMEOUT];
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of milliseconds after which an incomplete request will be
|
||||
* aborted. Zero means no timeout is set.
|
||||
* @param {number} ms Timeout interval in milliseconds; 0 means none.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.setTimeoutInterval = function(ms) {
|
||||
this.timeoutInterval_ = Math.max(0, ms);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of requests either in flight, or waiting to be sent.
|
||||
* The count will include the current request if used within a COMPLETE event
|
||||
* handler or callback.
|
||||
* @return {number} The number of requests in flight or pending send.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.getOutstandingCount = function() {
|
||||
return this.requests_.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of request ids that are either in flight, or waiting to
|
||||
* be sent. The id of the current request will be included if used within a
|
||||
* COMPLETE event handler or callback.
|
||||
* @return {!Array.<string>} Request ids in flight or pending send.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.getOutstandingRequestIds = function() {
|
||||
return this.requests_.getKeys();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Registers the given request to be sent. Throws an error if a request
|
||||
* already exists with the given ID.
|
||||
* NOTE: It is not sent immediately. It is queued and will be sent when an
|
||||
* XhrIo object becomes available, taking into account the request's
|
||||
* priority.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {string} url Uri to make the request too.
|
||||
* @param {string=} opt_method Send method, default: GET.
|
||||
* @param {ArrayBuffer|Blob|Document|FormData|string=} opt_content Post data.
|
||||
* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
|
||||
* request.
|
||||
* @param {*=} opt_priority The priority of the request. A smaller value means a
|
||||
* higher priority.
|
||||
* @param {Function=} opt_callback Callback function for when request is
|
||||
* complete. The only param is the event object from the COMPLETE event.
|
||||
* @param {number=} opt_maxRetries The maximum number of times the request
|
||||
* should be retried.
|
||||
* @param {goog.net.XhrIo.ResponseType=} opt_responseType The response type of
|
||||
* this request; defaults to goog.net.XhrIo.ResponseType.DEFAULT.
|
||||
* @return {goog.net.XhrManager.Request} The queued request object.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.send = function(
|
||||
id,
|
||||
url,
|
||||
opt_method,
|
||||
opt_content,
|
||||
opt_headers,
|
||||
opt_priority,
|
||||
opt_callback,
|
||||
opt_maxRetries,
|
||||
opt_responseType) {
|
||||
var requests = this.requests_;
|
||||
// Check if there is already a request with the given id.
|
||||
if (requests.get(id)) {
|
||||
throw Error(goog.net.XhrManager.ERROR_ID_IN_USE_);
|
||||
}
|
||||
|
||||
// Make the Request object.
|
||||
var request = new goog.net.XhrManager.Request(
|
||||
url,
|
||||
goog.bind(this.handleEvent_, this, id),
|
||||
opt_method,
|
||||
opt_content,
|
||||
opt_headers,
|
||||
opt_callback,
|
||||
goog.isDef(opt_maxRetries) ? opt_maxRetries : this.maxRetries_,
|
||||
opt_responseType);
|
||||
this.requests_.set(id, request);
|
||||
|
||||
// Setup the callback for the pool.
|
||||
var callback = goog.bind(this.handleAvailableXhr_, this, id);
|
||||
this.xhrPool_.getObject(callback, opt_priority);
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Aborts the request associated with id.
|
||||
* @param {string} id The id of the request to abort.
|
||||
* @param {boolean=} opt_force If true, remove the id now so it can be reused.
|
||||
* No events are fired and the callback is not called when forced.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.abort = function(id, opt_force) {
|
||||
var request = this.requests_.get(id);
|
||||
if (request) {
|
||||
var xhrIo = request.xhrIo;
|
||||
request.setAborted(true);
|
||||
if (opt_force) {
|
||||
if (xhrIo) {
|
||||
// We remove listeners to make sure nothing gets called if a new request
|
||||
// with the same id is made.
|
||||
this.removeXhrListener_(xhrIo, request.getXhrEventCallback());
|
||||
goog.events.listenOnce(
|
||||
xhrIo,
|
||||
goog.net.EventType.READY,
|
||||
function() { this.xhrPool_.releaseObject(xhrIo); },
|
||||
false,
|
||||
this);
|
||||
}
|
||||
this.requests_.remove(id);
|
||||
}
|
||||
if (xhrIo) {
|
||||
xhrIo.abort();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles when an XhrIo object becomes available. Sets up the events, fires
|
||||
* the READY event, and starts the process to send the request.
|
||||
* @param {string} id The id of the request the XhrIo is for.
|
||||
* @param {goog.net.XhrIo} xhrIo The available XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleAvailableXhr_ = function(id, xhrIo) {
|
||||
var request = this.requests_.get(id);
|
||||
// Make sure the request doesn't already have an XhrIo attached. This can
|
||||
// happen if a forced abort occurs before an XhrIo is available, and a new
|
||||
// request with the same id is made.
|
||||
if (request && !request.xhrIo) {
|
||||
this.addXhrListener_(xhrIo, request.getXhrEventCallback());
|
||||
|
||||
// Set properties for the XhrIo.
|
||||
xhrIo.setTimeoutInterval(this.timeoutInterval_);
|
||||
xhrIo.setResponseType(request.getResponseType());
|
||||
|
||||
// Add a reference to the XhrIo object to the request.
|
||||
request.xhrIo = xhrIo;
|
||||
|
||||
// Notify the listeners.
|
||||
this.dispatchEvent(new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.READY, this, id, xhrIo));
|
||||
|
||||
// Send the request.
|
||||
this.retry_(id, xhrIo);
|
||||
|
||||
// If the request was aborted before it got an XhrIo object, abort it now.
|
||||
if (request.getAborted()) {
|
||||
xhrIo.abort();
|
||||
}
|
||||
} else {
|
||||
// If the request has an XhrIo object already, or no request exists, just
|
||||
// return the XhrIo back to the pool.
|
||||
this.xhrPool_.releaseObject(xhrIo);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles all events fired by the XhrIo object for a given request.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.events.Event} e The event.
|
||||
* @return {Object} The return value from the handler, if any.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleEvent_ = function(id, e) {
|
||||
var xhrIo = /** @type {goog.net.XhrIo} */(e.target);
|
||||
switch (e.type) {
|
||||
case goog.net.EventType.READY:
|
||||
this.retry_(id, xhrIo);
|
||||
break;
|
||||
|
||||
case goog.net.EventType.COMPLETE:
|
||||
return this.handleComplete_(id, xhrIo, e);
|
||||
|
||||
case goog.net.EventType.SUCCESS:
|
||||
this.handleSuccess_(id, xhrIo);
|
||||
break;
|
||||
|
||||
// A timeout is handled like an error.
|
||||
case goog.net.EventType.TIMEOUT:
|
||||
case goog.net.EventType.ERROR:
|
||||
this.handleError_(id, xhrIo);
|
||||
break;
|
||||
|
||||
case goog.net.EventType.ABORT:
|
||||
this.handleAbort_(id, xhrIo);
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to retry the given request. If the request has already attempted
|
||||
* the maximum number of retries, then it removes the request and releases
|
||||
* the XhrIo object back into the pool.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.retry_ = function(id, xhrIo) {
|
||||
var request = this.requests_.get(id);
|
||||
|
||||
// If the request has not completed and it is below its max. retries.
|
||||
if (request && !request.getCompleted() && !request.hasReachedMaxRetries()) {
|
||||
request.increaseAttemptCount();
|
||||
xhrIo.send(request.getUrl(), request.getMethod(), request.getContent(),
|
||||
request.getHeaders());
|
||||
} else {
|
||||
if (request) {
|
||||
// Remove the events on the XhrIo objects.
|
||||
this.removeXhrListener_(xhrIo, request.getXhrEventCallback());
|
||||
|
||||
// Remove the request.
|
||||
this.requests_.remove(id);
|
||||
}
|
||||
// Release the XhrIo object back into the pool.
|
||||
this.xhrPool_.releaseObject(xhrIo);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the complete of a request. Dispatches the COMPLETE event and sets the
|
||||
* the request as completed if the request has succeeded, or is done retrying.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @param {goog.events.Event} e The original event.
|
||||
* @return {Object} The return value from the callback, if any.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleComplete_ = function(id, xhrIo, e) {
|
||||
// Only if the request is done processing should a COMPLETE event be fired.
|
||||
var request = this.requests_.get(id);
|
||||
if (xhrIo.getLastErrorCode() == goog.net.ErrorCode.ABORT ||
|
||||
xhrIo.isSuccess() || request.hasReachedMaxRetries()) {
|
||||
this.dispatchEvent(new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.COMPLETE, this, id, xhrIo));
|
||||
|
||||
// If the request exists, we mark it as completed and call the callback
|
||||
if (request) {
|
||||
request.setCompleted(true);
|
||||
// Call the complete callback as if it was set as a COMPLETE event on the
|
||||
// XhrIo directly.
|
||||
if (request.getCompleteCallback()) {
|
||||
return request.getCompleteCallback().call(xhrIo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the abort of an underlying XhrIo object.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleAbort_ = function(id, xhrIo) {
|
||||
// Fire event.
|
||||
// NOTE: The complete event should always be fired before the abort event, so
|
||||
// the bulk of the work is done in handleComplete.
|
||||
this.dispatchEvent(new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.ABORT, this, id, xhrIo));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the success of a request. Dispatches the SUCCESS event and sets the
|
||||
* the request as completed.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleSuccess_ = function(id, xhrIo) {
|
||||
// Fire event.
|
||||
// NOTE: We don't release the XhrIo object from the pool here.
|
||||
// It is released in the retry method, when we know it is back in the
|
||||
// ready state.
|
||||
this.dispatchEvent(new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.SUCCESS, this, id, xhrIo));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the error of a request. If the request has not reach its maximum
|
||||
* number of retries, then it lets the request retry naturally (will let the
|
||||
* request hit the READY state). Else, it dispatches the ERROR event.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleError_ = function(id, xhrIo) {
|
||||
var request = this.requests_.get(id);
|
||||
|
||||
// If the maximum number of retries has been reached.
|
||||
if (request.hasReachedMaxRetries()) {
|
||||
// Fire event.
|
||||
// NOTE: We don't release the XhrIo object from the pool here.
|
||||
// It is released in the retry method, when we know it is back in the
|
||||
// ready state.
|
||||
this.dispatchEvent(new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.ERROR, this, id, xhrIo));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove listeners for XHR events on an XhrIo object.
|
||||
* @param {goog.net.XhrIo} xhrIo The object to stop listenening to events on.
|
||||
* @param {Function} func The callback to remove from event handling.
|
||||
* @param {string|Array.<string>=} opt_types Event types to remove listeners
|
||||
* for. Defaults to XHR_EVENT_TYPES_.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.removeXhrListener_ = function(xhrIo,
|
||||
func,
|
||||
opt_types) {
|
||||
var types = opt_types || goog.net.XhrManager.XHR_EVENT_TYPES_;
|
||||
this.eventHandler_.unlisten(xhrIo, types, func);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for XHR events on an XhrIo object.
|
||||
* @param {goog.net.XhrIo} xhrIo The object listen to events on.
|
||||
* @param {Function} func The callback when the event occurs.
|
||||
* @param {string|Array.<string>=} opt_types Event types to attach listeners to.
|
||||
* Defaults to XHR_EVENT_TYPES_.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.addXhrListener_ = function(xhrIo,
|
||||
func,
|
||||
opt_types) {
|
||||
var types = opt_types || goog.net.XhrManager.XHR_EVENT_TYPES_;
|
||||
this.eventHandler_.listen(xhrIo, types, func);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.XhrManager.prototype.disposeInternal = function() {
|
||||
goog.net.XhrManager.superClass_.disposeInternal.call(this);
|
||||
|
||||
this.xhrPool_.dispose();
|
||||
this.xhrPool_ = null;
|
||||
|
||||
this.eventHandler_.dispose();
|
||||
this.eventHandler_ = null;
|
||||
|
||||
// Call dispose on each request.
|
||||
var requests = this.requests_;
|
||||
goog.structs.forEach(requests, function(value, key) {
|
||||
value.dispose();
|
||||
});
|
||||
requests.clear();
|
||||
this.requests_ = null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An event dispatched by XhrManager.
|
||||
*
|
||||
* @param {goog.net.EventType} type Event Type.
|
||||
* @param {goog.net.XhrManager} target Reference to the object that is the
|
||||
* target of this event.
|
||||
* @param {string} id The id of the request this event is for.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object of the request.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
goog.net.XhrManager.Event = function(type, target, id, xhrIo) {
|
||||
goog.events.Event.call(this, type, target);
|
||||
|
||||
/**
|
||||
* The id of the request this event is for.
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* The XhrIo object of the request.
|
||||
* @type {goog.net.XhrIo}
|
||||
*/
|
||||
this.xhrIo = xhrIo;
|
||||
};
|
||||
goog.inherits(goog.net.XhrManager.Event, goog.events.Event);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An encapsulation of everything needed to make a Xhr request.
|
||||
* NOTE: This is used internal to the XhrManager.
|
||||
*
|
||||
* @param {string} url Uri to make the request too.
|
||||
* @param {Function} xhrEventCallback Callback attached to the events of the
|
||||
* XhrIo object of the request.
|
||||
* @param {string=} opt_method Send method, default: GET.
|
||||
* @param {ArrayBuffer|Blob|Document|FormData|string=} opt_content Post data.
|
||||
* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
|
||||
* request.
|
||||
* @param {Function=} opt_callback Callback function for when request is
|
||||
* complete. NOTE: Only 1 callback supported across all events.
|
||||
* @param {number=} opt_maxRetries The maximum number of times the request
|
||||
* should be retried (Default: 1).
|
||||
* @param {goog.net.XhrIo.ResponseType=} opt_responseType The response type of
|
||||
* this request; defaults to goog.net.XhrIo.ResponseType.DEFAULT.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.net.XhrManager.Request = function(url, xhrEventCallback, opt_method,
|
||||
opt_content, opt_headers, opt_callback, opt_maxRetries, opt_responseType) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Uri to make the request too.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.url_ = url;
|
||||
|
||||
/**
|
||||
* Send method.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.method_ = opt_method || 'GET';
|
||||
|
||||
/**
|
||||
* Post data.
|
||||
* @type {ArrayBuffer|Blob|Document|FormData|string|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.content_ = opt_content;
|
||||
|
||||
/**
|
||||
* Map of headers
|
||||
* @type {Object|goog.structs.Map|null}
|
||||
* @private
|
||||
*/
|
||||
this.headers_ = opt_headers || null;
|
||||
|
||||
/**
|
||||
* The maximum number of times the request should be retried.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.maxRetries_ = goog.isDef(opt_maxRetries) ? opt_maxRetries : 1;
|
||||
|
||||
/**
|
||||
* The number of attempts so far.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.attemptCount_ = 0;
|
||||
|
||||
/**
|
||||
* Whether the request has been completed.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.completed_ = false;
|
||||
|
||||
/**
|
||||
* Whether the request has been aborted.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.aborted_ = false;
|
||||
|
||||
/**
|
||||
* Callback attached to the events of the XhrIo object.
|
||||
* @type {Function|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.xhrEventCallback_ = xhrEventCallback;
|
||||
|
||||
/**
|
||||
* Callback function called when request is complete.
|
||||
* @type {Function|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.completeCallback_ = opt_callback;
|
||||
|
||||
/**
|
||||
* A response type to set on this.xhrIo when it's populated.
|
||||
* @type {!goog.net.XhrIo.ResponseType}
|
||||
* @private
|
||||
*/
|
||||
this.responseType_ = opt_responseType || goog.net.XhrIo.ResponseType.DEFAULT;
|
||||
|
||||
/**
|
||||
* The XhrIo instance handling this request. Set in handleAvailableXhr.
|
||||
* @type {goog.net.XhrIo}
|
||||
*/
|
||||
this.xhrIo = null;
|
||||
|
||||
};
|
||||
goog.inherits(goog.net.XhrManager.Request, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Gets the uri.
|
||||
* @return {string} The uri to make the request to.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getUrl = function() {
|
||||
return this.url_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the send method.
|
||||
* @return {string} The send method.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getMethod = function() {
|
||||
return this.method_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the post data.
|
||||
* @return {ArrayBuffer|Blob|Document|FormData|string|undefined}
|
||||
* The post data.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getContent = function() {
|
||||
return this.content_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the map of headers.
|
||||
* @return {Object|goog.structs.Map} The map of headers.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getHeaders = function() {
|
||||
return this.headers_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the maximum number of times the request should be retried.
|
||||
* @return {number} The maximum number of times the request should be retried.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getMaxRetries = function() {
|
||||
return this.maxRetries_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of attempts so far.
|
||||
* @return {number} The number of attempts so far.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getAttemptCount = function() {
|
||||
return this.attemptCount_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Increases the number of attempts so far.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.increaseAttemptCount = function() {
|
||||
this.attemptCount_++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the request has reached the maximum number of retries.
|
||||
* @return {boolean} Whether the request has reached the maximum number of
|
||||
* retries.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.hasReachedMaxRetries = function() {
|
||||
return this.attemptCount_ > this.maxRetries_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the completed status.
|
||||
* @param {boolean} complete The completed status.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.setCompleted = function(complete) {
|
||||
this.completed_ = complete;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the completed status.
|
||||
* @return {boolean} The completed status.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getCompleted = function() {
|
||||
return this.completed_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the aborted status.
|
||||
* @param {boolean} aborted True if the request was aborted, otherwise False.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.setAborted = function(aborted) {
|
||||
this.aborted_ = aborted;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the aborted status.
|
||||
* @return {boolean} True if request was aborted, otherwise False.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getAborted = function() {
|
||||
return this.aborted_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the callback attached to the events of the XhrIo object.
|
||||
* @return {Function|undefined} The callback attached to the events of the
|
||||
* XhrIo object.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getXhrEventCallback = function() {
|
||||
return this.xhrEventCallback_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the callback for when the request is complete.
|
||||
* @return {Function|undefined} The callback for when the request is complete.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getCompleteCallback = function() {
|
||||
return this.completeCallback_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the response type that will be set on this request's XhrIo when it's
|
||||
* available.
|
||||
* @return {!goog.net.XhrIo.ResponseType} The response type to be set
|
||||
* when an XhrIo becomes available to this request.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getResponseType = function() {
|
||||
return this.responseType_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.XhrManager.Request.prototype.disposeInternal = function() {
|
||||
goog.net.XhrManager.Request.superClass_.disposeInternal.call(this);
|
||||
delete this.xhrEventCallback_;
|
||||
delete this.completeCallback_;
|
||||
};
|
||||
@@ -0,0 +1,228 @@
|
||||
// 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 Low level handling of XMLHttpRequest.
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
* @author dbk@google.com (David Barrett-Kahn)
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.DefaultXmlHttpFactory');
|
||||
goog.provide('goog.net.XmlHttp');
|
||||
goog.provide('goog.net.XmlHttp.OptionType');
|
||||
goog.provide('goog.net.XmlHttp.ReadyState');
|
||||
|
||||
goog.require('goog.net.WrapperXmlHttpFactory');
|
||||
goog.require('goog.net.XmlHttpFactory');
|
||||
|
||||
|
||||
/**
|
||||
* Static class for creating XMLHttpRequest objects.
|
||||
* @return {!(XMLHttpRequest|GearsHttpRequest)} A new XMLHttpRequest object.
|
||||
*/
|
||||
goog.net.XmlHttp = function() {
|
||||
return goog.net.XmlHttp.factory_.createInstance();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
|
||||
* true strips the ActiveX probing code.
|
||||
*/
|
||||
goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false);
|
||||
|
||||
|
||||
/**
|
||||
* Gets the options to use with the XMLHttpRequest objects obtained using
|
||||
* the static methods.
|
||||
* @return {Object} The options.
|
||||
*/
|
||||
goog.net.XmlHttp.getOptions = function() {
|
||||
return goog.net.XmlHttp.factory_.getOptions();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Type of options that an XmlHttp object can have.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.XmlHttp.OptionType = {
|
||||
/**
|
||||
* Whether a goog.nullFunction should be used to clear the onreadystatechange
|
||||
* handler instead of null.
|
||||
*/
|
||||
USE_NULL_FUNCTION: 0,
|
||||
|
||||
/**
|
||||
* NOTE(user): In IE if send() errors on a *local* request the readystate
|
||||
* is still changed to COMPLETE. We need to ignore it and allow the
|
||||
* try/catch around send() to pick up the error.
|
||||
*/
|
||||
LOCAL_REQUEST_ERROR: 1
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Status constants for XMLHTTP, matches:
|
||||
* http://msdn.microsoft.com/library/default.asp?url=/library/
|
||||
* en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.XmlHttp.ReadyState = {
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is uninitialized
|
||||
*/
|
||||
UNINITIALIZED: 0,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is loading.
|
||||
*/
|
||||
LOADING: 1,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is loaded.
|
||||
*/
|
||||
LOADED: 2,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is in an interactive state.
|
||||
*/
|
||||
INTERACTIVE: 3,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is completed
|
||||
*/
|
||||
COMPLETE: 4
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The global factory instance for creating XMLHttpRequest objects.
|
||||
* @type {goog.net.XmlHttpFactory}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XmlHttp.factory_;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the factories for creating XMLHttpRequest objects and their options.
|
||||
* @param {Function} factory The factory for XMLHttpRequest objects.
|
||||
* @param {Function} optionsFactory The factory for options.
|
||||
* @deprecated Use setGlobalFactory instead.
|
||||
*/
|
||||
goog.net.XmlHttp.setFactory = function(factory, optionsFactory) {
|
||||
goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(
|
||||
/** @type {function() : !(XMLHttpRequest|GearsHttpRequest)} */ (factory),
|
||||
/** @type {function() : !Object}*/ (optionsFactory)));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the global factory object.
|
||||
* @param {!goog.net.XmlHttpFactory} factory New global factory object.
|
||||
*/
|
||||
goog.net.XmlHttp.setGlobalFactory = function(factory) {
|
||||
goog.net.XmlHttp.factory_ = factory;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default factory to use when creating xhr objects. You probably shouldn't be
|
||||
* instantiating this directly, but rather using it via goog.net.XmlHttp.
|
||||
* @extends {goog.net.XmlHttpFactory}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.DefaultXmlHttpFactory = function() {
|
||||
goog.net.XmlHttpFactory.call(this);
|
||||
};
|
||||
goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() {
|
||||
var progId = this.getProgId_();
|
||||
if (progId) {
|
||||
return new ActiveXObject(progId);
|
||||
} else {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() {
|
||||
var progId = this.getProgId_();
|
||||
var options = {};
|
||||
if (progId) {
|
||||
options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true;
|
||||
options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.
|
||||
* @type {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.net.DefaultXmlHttpFactory.prototype.ieProgId_;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the private state used by other functions.
|
||||
* @return {string} The ActiveX PROG ID string to use to create xhr's in IE.
|
||||
* @private
|
||||
*/
|
||||
goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() {
|
||||
if (goog.net.XmlHttp.ASSUME_NATIVE_XHR) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// The following blog post describes what PROG IDs to use to create the
|
||||
// XMLHTTP object in Internet Explorer:
|
||||
// http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
|
||||
// However we do not (yet) fully trust that this will be OK for old versions
|
||||
// of IE on Win9x so we therefore keep the last 2.
|
||||
if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' &&
|
||||
typeof ActiveXObject != 'undefined') {
|
||||
// Candidate Active X types.
|
||||
var ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0',
|
||||
'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];
|
||||
for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) {
|
||||
var candidate = ACTIVE_X_IDENTS[i];
|
||||
/** @preserveTry */
|
||||
try {
|
||||
new ActiveXObject(candidate);
|
||||
// NOTE(user): cannot assign progid and return candidate in one line
|
||||
// because JSCompiler complaings: BUG 658126
|
||||
this.ieProgId_ = candidate;
|
||||
return candidate;
|
||||
} catch (e) {
|
||||
// do nothing; try next choice
|
||||
}
|
||||
}
|
||||
|
||||
// couldn't find any matches
|
||||
throw Error('Could not create ActiveXObject. ActiveX might be disabled,' +
|
||||
' or MSXML might not be installed');
|
||||
}
|
||||
|
||||
return /** @type {string} */ (this.ieProgId_);
|
||||
};
|
||||
|
||||
|
||||
//Set the global factory to an instance of the default factory.
|
||||
goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory());
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2010 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 Interface for a factory for creating XMLHttpRequest objects
|
||||
* and metadata about them.
|
||||
* @author dbk@google.com (David Barrett-Kahn)
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.XmlHttpFactory');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Abstract base class for an XmlHttpRequest factory.
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.XmlHttpFactory = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cache of options - we only actually call internalGetOptions once.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.cachedOptions_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* @return {!(XMLHttpRequest|GearsHttpRequest)} A new XMLHttpRequest instance.
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Object} Options describing how xhr objects obtained from this
|
||||
* factory should be used.
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.getOptions = function() {
|
||||
return this.cachedOptions_ ||
|
||||
(this.cachedOptions_ = this.internalGetOptions());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Override this method in subclasses to preserve the caching offered by
|
||||
* getOptions().
|
||||
* @return {Object} Options describing how xhr objects obtained from this
|
||||
* factory should be used.
|
||||
* @protected
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod;
|
||||
@@ -0,0 +1,805 @@
|
||||
// Copyright 2007 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 the class CrossPageChannel, the main class in
|
||||
* goog.net.xpc.
|
||||
*
|
||||
* @see ../../demos/xpc/index.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.CrossPageChannel');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.async.Delay');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.messaging.AbstractChannel');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.FrameElementMethodTransport');
|
||||
goog.require('goog.net.xpc.IframePollingTransport');
|
||||
goog.require('goog.net.xpc.IframeRelayTransport');
|
||||
goog.require('goog.net.xpc.NativeMessagingTransport');
|
||||
goog.require('goog.net.xpc.NixTransport');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A communication channel between two documents from different domains.
|
||||
* Provides asynchronous messaging.
|
||||
*
|
||||
* @param {Object} cfg Channel configuration object.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The optional dom helper to
|
||||
* use for looking up elements in the dom.
|
||||
* @constructor
|
||||
* @extends {goog.messaging.AbstractChannel}
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel = function(cfg, opt_domHelper) {
|
||||
goog.base(this);
|
||||
|
||||
for (var i = 0, uriField; uriField = goog.net.xpc.UriCfgFields[i]; i++) {
|
||||
if (uriField in cfg && !/^https?:\/\//.test(cfg[uriField])) {
|
||||
throw Error('URI ' + cfg[uriField] + ' is invalid for field ' + uriField);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration for this channel.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.cfg_ = cfg;
|
||||
|
||||
/**
|
||||
* The name of the channel.
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = this.cfg_[goog.net.xpc.CfgFields.CHANNEL_NAME] ||
|
||||
goog.net.xpc.getRandomString(10);
|
||||
|
||||
/**
|
||||
* The dom helper to use for accessing the dom.
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
|
||||
|
||||
/**
|
||||
* Collects deferred function calls which will be made once the connection
|
||||
* has been fully set up.
|
||||
* @type {!Array.<function()>}
|
||||
* @private
|
||||
*/
|
||||
this.deferredDeliveries_ = [];
|
||||
|
||||
/**
|
||||
* An event handler used to listen for load events on peer iframes.
|
||||
* @type {!goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.peerLoadHandler_ = new goog.events.EventHandler(this);
|
||||
|
||||
// If LOCAL_POLL_URI or PEER_POLL_URI is not available, try using
|
||||
// robots.txt from that host.
|
||||
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =
|
||||
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] ||
|
||||
goog.uri.utils.getHost(this.domHelper_.getWindow().location.href) +
|
||||
'/robots.txt';
|
||||
// PEER_URI is sometimes undefined in tests.
|
||||
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
|
||||
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] ||
|
||||
goog.uri.utils.getHost(cfg[goog.net.xpc.CfgFields.PEER_URI] || '') +
|
||||
'/robots.txt';
|
||||
|
||||
goog.net.xpc.channels[this.name] = this;
|
||||
|
||||
goog.events.listen(window, 'unload',
|
||||
goog.net.xpc.CrossPageChannel.disposeAll_);
|
||||
|
||||
goog.log.info(goog.net.xpc.logger, 'CrossPageChannel created: ' + this.name);
|
||||
};
|
||||
goog.inherits(goog.net.xpc.CrossPageChannel, goog.messaging.AbstractChannel);
|
||||
|
||||
|
||||
/**
|
||||
* Regexp for escaping service names.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_ =
|
||||
new RegExp('^%*' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');
|
||||
|
||||
|
||||
/**
|
||||
* Regexp for unescaping service names.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_ =
|
||||
new RegExp('^%+' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');
|
||||
|
||||
|
||||
/**
|
||||
* A delay between the transport reporting as connected and the calling of the
|
||||
* connection callback. Sometimes used to paper over timing vulnerabilities.
|
||||
* @type {goog.async.Delay}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.connectionDelay_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A deferred which is set to non-null while a peer iframe is being created
|
||||
* but has not yet thrown its load event, and which fires when that load event
|
||||
* arrives.
|
||||
* @type {goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.peerWindowDeferred_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The transport.
|
||||
* @type {goog.net.xpc.Transport?}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.transport_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The channel state.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.state_ =
|
||||
goog.net.xpc.ChannelStates.NOT_CONNECTED;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {boolean} Whether the channel is connected.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.isConnected = function() {
|
||||
return this.state_ == goog.net.xpc.ChannelStates.CONNECTED;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reference to the window-object of the peer page.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.peerWindowObject_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to the iframe-element.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.iframeElement_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the configuration object for this channel.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {Object} The configuration object for this channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getConfig = function() {
|
||||
return this.cfg_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the iframe-element.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {Object} A reference to the iframe-element.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getIframeElement = function() {
|
||||
return this.iframeElement_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the window object the foreign document resides in.
|
||||
*
|
||||
* @param {Object} peerWindowObject The window object of the peer.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.setPeerWindowObject =
|
||||
function(peerWindowObject) {
|
||||
this.peerWindowObject_ = peerWindowObject;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the window object the foreign document resides in.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {Object} The window object of the peer.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getPeerWindowObject = function() {
|
||||
return this.peerWindowObject_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the peer window is available (e.g. not closed).
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {boolean} Whether the peer window is available.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.isPeerAvailable = function() {
|
||||
// NOTE(user): This check is not reliable in IE, where a document in an
|
||||
// iframe does not get unloaded when removing the iframe element from the DOM.
|
||||
// TODO(user): Find something that works in IE as well.
|
||||
// NOTE(user): "!this.peerWindowObject_.closed" evaluates to 'false' in IE9
|
||||
// sometimes even though typeof(this.peerWindowObject_.closed) is boolean and
|
||||
// this.peerWindowObject_.closed evaluates to 'false'. Casting it to a Boolean
|
||||
// results in sane evaluation. When this happens, it's in the inner iframe
|
||||
// when querying its parent's 'closed' status. Note that this is a different
|
||||
// case than mibuerge@'s note above.
|
||||
try {
|
||||
return !!this.peerWindowObject_ && !Boolean(this.peerWindowObject_.closed);
|
||||
} catch (e) {
|
||||
// If the window is closing, an error may be thrown.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine which transport type to use for this channel / useragent.
|
||||
* @return {goog.net.xpc.TransportTypes|undefined} The best transport type.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.determineTransportType_ = function() {
|
||||
var transportType;
|
||||
if (goog.isFunction(document.postMessage) ||
|
||||
goog.isFunction(window.postMessage) ||
|
||||
// IE8 supports window.postMessage, but
|
||||
// typeof window.postMessage returns "object"
|
||||
(goog.userAgent.IE && window.postMessage)) {
|
||||
transportType = goog.net.xpc.TransportTypes.NATIVE_MESSAGING;
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
transportType = goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD;
|
||||
} else if (goog.userAgent.IE &&
|
||||
this.cfg_[goog.net.xpc.CfgFields.PEER_RELAY_URI]) {
|
||||
transportType = goog.net.xpc.TransportTypes.IFRAME_RELAY;
|
||||
} else if (goog.userAgent.IE && goog.net.xpc.NixTransport.isNixSupported()) {
|
||||
transportType = goog.net.xpc.TransportTypes.NIX;
|
||||
} else {
|
||||
transportType = goog.net.xpc.TransportTypes.IFRAME_POLLING;
|
||||
}
|
||||
return transportType;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the transport for this channel. Chooses from the available
|
||||
* transport based on the user agent and the configuration.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.createTransport_ = function() {
|
||||
// return, if the transport has already been created
|
||||
if (this.transport_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.cfg_[goog.net.xpc.CfgFields.TRANSPORT]) {
|
||||
this.cfg_[goog.net.xpc.CfgFields.TRANSPORT] =
|
||||
this.determineTransportType_();
|
||||
}
|
||||
|
||||
switch (this.cfg_[goog.net.xpc.CfgFields.TRANSPORT]) {
|
||||
case goog.net.xpc.TransportTypes.NATIVE_MESSAGING:
|
||||
var protocolVersion = this.cfg_[
|
||||
goog.net.xpc.CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] || 2;
|
||||
this.transport_ = new goog.net.xpc.NativeMessagingTransport(
|
||||
this,
|
||||
this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME],
|
||||
this.domHelper_,
|
||||
!!this.cfg_[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE],
|
||||
protocolVersion);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.NIX:
|
||||
this.transport_ = new goog.net.xpc.NixTransport(this, this.domHelper_);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD:
|
||||
this.transport_ =
|
||||
new goog.net.xpc.FrameElementMethodTransport(this, this.domHelper_);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.IFRAME_RELAY:
|
||||
this.transport_ =
|
||||
new goog.net.xpc.IframeRelayTransport(this, this.domHelper_);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.IFRAME_POLLING:
|
||||
this.transport_ =
|
||||
new goog.net.xpc.IframePollingTransport(this, this.domHelper_);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.transport_) {
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'Transport created: ' + this.transport_.getName());
|
||||
} else {
|
||||
throw Error('CrossPageChannel: No suitable transport found!');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the transport type in use for this channel.
|
||||
* @return {number} Transport-type identifier.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getTransportType = function() {
|
||||
return this.transport_.getType();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the tranport name in use for this channel.
|
||||
* @return {string} The transport name.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getTransportName = function() {
|
||||
return this.transport_.getName();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Object} Configuration-object to be used by the peer to
|
||||
* initialize the channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getPeerConfiguration = function() {
|
||||
var peerCfg = {};
|
||||
peerCfg[goog.net.xpc.CfgFields.CHANNEL_NAME] = this.name;
|
||||
peerCfg[goog.net.xpc.CfgFields.TRANSPORT] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.TRANSPORT];
|
||||
peerCfg[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE];
|
||||
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI]) {
|
||||
peerCfg[goog.net.xpc.CfgFields.PEER_RELAY_URI] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI];
|
||||
}
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI]) {
|
||||
peerCfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI];
|
||||
}
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI]) {
|
||||
peerCfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI];
|
||||
}
|
||||
var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];
|
||||
if (role) {
|
||||
peerCfg[goog.net.xpc.CfgFields.ROLE] =
|
||||
role == goog.net.xpc.CrossPageChannelRole.INNER ?
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER :
|
||||
goog.net.xpc.CrossPageChannelRole.INNER;
|
||||
}
|
||||
|
||||
return peerCfg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the iframe containing the peer page in a specified parent element.
|
||||
* This method does not connect the channel, connect() still has to be called
|
||||
* separately.
|
||||
*
|
||||
* @param {!Element} parentElm The container element the iframe is appended to.
|
||||
* @param {Function=} opt_configureIframeCb If present, this function gets
|
||||
* called with the iframe element as parameter to allow setting properties
|
||||
* on it before it gets added to the DOM. If absent, the iframe's width and
|
||||
* height are set to '100%'.
|
||||
* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as
|
||||
* URL parameter (default: true).
|
||||
* @return {!HTMLIFrameElement} The iframe element.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.createPeerIframe = function(
|
||||
parentElm, opt_configureIframeCb, opt_addCfgParam) {
|
||||
goog.log.info(goog.net.xpc.logger, 'createPeerIframe()');
|
||||
|
||||
var iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID];
|
||||
if (!iframeId) {
|
||||
// Create a randomized ID for the iframe element to avoid
|
||||
// bfcache-related issues.
|
||||
iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID] =
|
||||
'xpcpeer' + goog.net.xpc.getRandomString(4);
|
||||
}
|
||||
|
||||
// TODO(user) Opera creates a history-entry when creating an iframe
|
||||
// programmatically as follows. Find a way which avoids this.
|
||||
|
||||
var iframeElm = goog.dom.getDomHelper(parentElm).createElement('IFRAME');
|
||||
iframeElm.id = iframeElm.name = iframeId;
|
||||
if (opt_configureIframeCb) {
|
||||
opt_configureIframeCb(iframeElm);
|
||||
} else {
|
||||
iframeElm.style.width = iframeElm.style.height = '100%';
|
||||
}
|
||||
|
||||
this.cleanUpIncompleteConnection_();
|
||||
this.peerWindowDeferred_ =
|
||||
new goog.async.Deferred(undefined, this);
|
||||
var peerUri = this.getPeerUri(opt_addCfgParam);
|
||||
this.peerLoadHandler_.listenOnce(iframeElm, 'load',
|
||||
this.peerWindowDeferred_.callback, false, this.peerWindowDeferred_);
|
||||
|
||||
if (goog.userAgent.GECKO || goog.userAgent.WEBKIT) {
|
||||
// Appending the iframe in a timeout to avoid a weird fastback issue, which
|
||||
// is present in Safari and Gecko.
|
||||
window.setTimeout(
|
||||
goog.bind(function() {
|
||||
parentElm.appendChild(iframeElm);
|
||||
iframeElm.src = peerUri.toString();
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'peer iframe created (' + iframeId + ')');
|
||||
}, this), 1);
|
||||
} else {
|
||||
iframeElm.src = peerUri.toString();
|
||||
parentElm.appendChild(iframeElm);
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'peer iframe created (' + iframeId + ')');
|
||||
}
|
||||
|
||||
return /** @type {!HTMLIFrameElement} */ (iframeElm);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clean up after any incomplete attempt to establish and connect to a peer
|
||||
* iframe.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.cleanUpIncompleteConnection_ =
|
||||
function() {
|
||||
if (this.peerWindowDeferred_) {
|
||||
this.peerWindowDeferred_.cancel();
|
||||
this.peerWindowDeferred_ = null;
|
||||
}
|
||||
this.deferredDeliveries_.length = 0;
|
||||
this.peerLoadHandler_.removeAll();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the peer URI, with an optional URL parameter for configuring the peer
|
||||
* window.
|
||||
*
|
||||
* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as
|
||||
* URL parameter (default: true).
|
||||
* @return {!goog.Uri} The peer URI.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getPeerUri = function(opt_addCfgParam) {
|
||||
var peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI];
|
||||
if (goog.isString(peerUri)) {
|
||||
peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI] =
|
||||
new goog.Uri(peerUri);
|
||||
}
|
||||
|
||||
// Add the channel configuration used by the peer as URL parameter.
|
||||
if (opt_addCfgParam !== false) {
|
||||
peerUri.setParameterValue('xpc',
|
||||
goog.json.serialize(
|
||||
this.getPeerConfiguration()));
|
||||
}
|
||||
|
||||
return peerUri;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initiates connecting the channel. When this method is called, all the
|
||||
* information needed to connect the channel has to be available.
|
||||
*
|
||||
* @override
|
||||
* @param {Function=} opt_connectCb The function to be called when the
|
||||
* channel has been connected and is ready to be used.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.connect = function(opt_connectCb) {
|
||||
this.connectCb_ = opt_connectCb || goog.nullFunction;
|
||||
|
||||
// If we know of a peer window whose creation has been requested but is not
|
||||
// complete, peerWindowDeferred_ will be non-null, and we should block on it.
|
||||
if (this.peerWindowDeferred_) {
|
||||
this.peerWindowDeferred_.addCallback(this.continueConnection_);
|
||||
} else {
|
||||
this.continueConnection_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Continues the connection process once we're as sure as we can be that the
|
||||
* peer iframe has been created.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.continueConnection_ = function() {
|
||||
goog.log.info(goog.net.xpc.logger, 'continueConnection_()');
|
||||
this.peerWindowDeferred_ = null;
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]) {
|
||||
this.iframeElement_ = this.domHelper_.getElement(
|
||||
this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]);
|
||||
}
|
||||
if (this.iframeElement_) {
|
||||
var winObj = this.iframeElement_.contentWindow;
|
||||
// accessing the window using contentWindow doesn't work in safari
|
||||
if (!winObj) {
|
||||
winObj = window.frames[this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]];
|
||||
}
|
||||
this.setPeerWindowObject(winObj);
|
||||
}
|
||||
|
||||
// if the peer window object has not been set at this point, we assume
|
||||
// being in an iframe and the channel is meant to be to the containing page
|
||||
if (!this.peerWindowObject_) {
|
||||
// throw an error if we are in the top window (== not in an iframe)
|
||||
if (window == window.top) {
|
||||
throw Error(
|
||||
"CrossPageChannel: Can't connect, peer window-object not set.");
|
||||
} else {
|
||||
this.setPeerWindowObject(window.parent);
|
||||
}
|
||||
}
|
||||
|
||||
this.createTransport_();
|
||||
|
||||
this.transport_.connect();
|
||||
|
||||
// Now we run any deferred deliveries collected while connection was deferred.
|
||||
while (this.deferredDeliveries_.length > 0) {
|
||||
this.deferredDeliveries_.shift()();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Closes the channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.close = function() {
|
||||
this.cleanUpIncompleteConnection_();
|
||||
this.state_ = goog.net.xpc.ChannelStates.CLOSED;
|
||||
goog.dispose(this.transport_);
|
||||
this.transport_ = null;
|
||||
this.connectCb_ = null;
|
||||
goog.dispose(this.connectionDelay_);
|
||||
this.connectionDelay_ = null;
|
||||
goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" closed');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Package-private.
|
||||
* Called by the transport when the channel is connected.
|
||||
* @param {number=} opt_delay Delay this number of milliseconds before calling
|
||||
* the connection callback. Usage is discouraged, but can be used to paper
|
||||
* over timing vulnerabilities when there is no alternative.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyConnected = function(opt_delay) {
|
||||
if (this.isConnected() ||
|
||||
(this.connectionDelay_ && this.connectionDelay_.isActive())) {
|
||||
return;
|
||||
}
|
||||
this.state_ = goog.net.xpc.ChannelStates.CONNECTED;
|
||||
goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" connected');
|
||||
goog.dispose(this.connectionDelay_);
|
||||
if (opt_delay) {
|
||||
this.connectionDelay_ =
|
||||
new goog.async.Delay(this.connectCb_, opt_delay);
|
||||
this.connectionDelay_.start();
|
||||
} else {
|
||||
this.connectionDelay_ = null;
|
||||
this.connectCb_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Alias for notifyConected, for backward compatibility reasons.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyConnected_ =
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyConnected;
|
||||
|
||||
|
||||
/**
|
||||
* Called by the transport in case of an unrecoverable failure.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyTransportError = function() {
|
||||
goog.log.info(goog.net.xpc.logger, 'Transport Error');
|
||||
this.close();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.CrossPageChannel.prototype.send = function(serviceName, payload) {
|
||||
if (!this.isConnected()) {
|
||||
goog.log.error(goog.net.xpc.logger, 'Can\'t send. Channel not connected.');
|
||||
return;
|
||||
}
|
||||
// Check if the peer is still around.
|
||||
if (!this.isPeerAvailable()) {
|
||||
goog.log.error(goog.net.xpc.logger, 'Peer has disappeared.');
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
if (goog.isObject(payload)) {
|
||||
payload = goog.json.serialize(payload);
|
||||
}
|
||||
|
||||
// Partially URL-encode the service name because some characters (: and |) are
|
||||
// used as delimiters for some transports, and we want to allow those
|
||||
// characters in service names.
|
||||
this.transport_.send(this.escapeServiceName_(serviceName), payload);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delivers messages to the appropriate service-handler. Named xpcDeliver to
|
||||
* avoid name conflict with {@code deliver} function in superclass
|
||||
* goog.messaging.AbstractChannel.
|
||||
*
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @param {string} serviceName The name of the port.
|
||||
* @param {string} payload The payload.
|
||||
* @param {string=} opt_origin An optional origin for the message, where the
|
||||
* underlying transport makes that available. If this is specified, and
|
||||
* the PEER_HOSTNAME parameter was provided, they must match or the message
|
||||
* will be rejected.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.xpcDeliver = function(
|
||||
serviceName, payload, opt_origin) {
|
||||
|
||||
// This check covers the very rare (but producable) case where the inner frame
|
||||
// becomes ready and sends its setup message while the outer frame is
|
||||
// deferring its connect method waiting for the inner frame to be ready. The
|
||||
// resulting deferral ensures the message will not be processed until the
|
||||
// channel is fully configured.
|
||||
if (this.peerWindowDeferred_) {
|
||||
this.deferredDeliveries_.push(
|
||||
goog.bind(this.xpcDeliver, this, serviceName, payload, opt_origin));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether the origin of the message is as expected.
|
||||
if (!this.isMessageOriginAcceptable_(opt_origin)) {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'Message received from unapproved origin "' +
|
||||
opt_origin + '" - rejected.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isDisposed()) {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'CrossPageChannel::xpcDeliver(): Disposed.');
|
||||
} else if (!serviceName ||
|
||||
serviceName == goog.net.xpc.TRANSPORT_SERVICE_) {
|
||||
this.transport_.transportServiceHandler(payload);
|
||||
} else {
|
||||
// only deliver messages if connected
|
||||
if (this.isConnected()) {
|
||||
this.deliver(this.unescapeServiceName_(serviceName), payload);
|
||||
} else {
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'CrossPageChannel::xpcDeliver(): Not connected.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Escape the user-provided service name for sending across the channel. This
|
||||
* URL-encodes certain special characters so they don't conflict with delimiters
|
||||
* used by some of the transports, and adds a special prefix if the name
|
||||
* conflicts with the reserved transport service name.
|
||||
*
|
||||
* This is the opposite of {@link #unescapeServiceName_}.
|
||||
*
|
||||
* @param {string} name The name of the service to escape.
|
||||
* @return {string} The escaped service name.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.escapeServiceName_ = function(name) {
|
||||
if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_.test(name)) {
|
||||
name = '%' + name;
|
||||
}
|
||||
return name.replace(/[%:|]/g, encodeURIComponent);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unescape the escaped service name that was sent across the channel. This is
|
||||
* the opposite of {@link #escapeServiceName_}.
|
||||
*
|
||||
* @param {string} name The name of the service to unescape.
|
||||
* @return {string} The unescaped service name.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.unescapeServiceName_ = function(name) {
|
||||
name = name.replace(/%[0-9a-f]{2}/gi, decodeURIComponent);
|
||||
if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_.test(name)) {
|
||||
return name.substring(1);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the role of this channel (either inner or outer).
|
||||
* @return {number} The role of this channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getRole = function() {
|
||||
var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];
|
||||
if (role) {
|
||||
return role;
|
||||
} else {
|
||||
return window.parent == this.peerWindowObject_ ?
|
||||
goog.net.xpc.CrossPageChannelRole.INNER :
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether an incoming message with the given origin is acceptable.
|
||||
* If an incoming request comes with a specified (non-empty) origin, and the
|
||||
* PEER_HOSTNAME config parameter has also been provided, the two must match,
|
||||
* or the message is unacceptable.
|
||||
* @param {string=} opt_origin The origin associated with the incoming message.
|
||||
* @return {boolean} Whether the message is acceptable.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.isMessageOriginAcceptable_ = function(
|
||||
opt_origin) {
|
||||
var peerHostname = this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];
|
||||
return goog.string.isEmptySafe(opt_origin) ||
|
||||
goog.string.isEmptySafe(peerHostname) ||
|
||||
opt_origin == this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.CrossPageChannel.prototype.disposeInternal = function() {
|
||||
this.close();
|
||||
|
||||
this.peerWindowObject_ = null;
|
||||
this.iframeElement_ = null;
|
||||
delete goog.net.xpc.channels[this.name];
|
||||
goog.dispose(this.peerLoadHandler_);
|
||||
delete this.peerLoadHandler_;
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes all channels.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.disposeAll_ = function() {
|
||||
for (var name in goog.net.xpc.channels) {
|
||||
goog.dispose(goog.net.xpc.channels[name]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2007 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 the enum for the role of the CrossPageChannel.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.CrossPageChannelRole');
|
||||
|
||||
|
||||
/**
|
||||
* The role of the peer.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannelRole = {
|
||||
OUTER: 0,
|
||||
INNER: 1
|
||||
};
|
||||
@@ -0,0 +1,254 @@
|
||||
// Copyright 2007 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 Contains the frame element method transport for cross-domain
|
||||
* communication. It exploits the fact that FF lets a page in an
|
||||
* iframe call a method on the iframe-element it is contained in, even if the
|
||||
* containing page is from a different domain.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.FrameElementMethodTransport');
|
||||
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Frame-element method transport.
|
||||
*
|
||||
* Firefox allows a document within an iframe to call methods on the
|
||||
* iframe-element added by the containing document.
|
||||
* NOTE(user): Tested in all FF versions starting from 1.0
|
||||
*
|
||||
* @param {goog.net.xpc.CrossPageChannel} channel The channel this transport
|
||||
* belongs to.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
|
||||
* the correct window.
|
||||
* @constructor
|
||||
* @extends {goog.net.xpc.Transport}
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport = function(channel, opt_domHelper) {
|
||||
goog.base(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
// To transfer messages, this transport basically uses normal function calls,
|
||||
// which are synchronous. To avoid endless recursion, the delivery has to
|
||||
// be artificially made asynchronous.
|
||||
|
||||
/**
|
||||
* Array for queued messages.
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.queue_ = [];
|
||||
|
||||
/**
|
||||
* Callback function which wraps deliverQueued_.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.deliverQueuedCb_ = goog.bind(this.deliverQueued_, this);
|
||||
};
|
||||
goog.inherits(goog.net.xpc.FrameElementMethodTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD;
|
||||
|
||||
|
||||
/**
|
||||
* Flag used to enforce asynchronous messaging semantics.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.recursive_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Timer used to enforce asynchronous message delivery.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.timer_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Holds the function to send messages to the peer
|
||||
* (once it becomes available).
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.outgoing_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Connect this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.connect = function() {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) {
|
||||
// get shortcut to iframe-element
|
||||
this.iframeElm_ = this.channel_.getIframeElement();
|
||||
|
||||
// add the gateway function to the iframe-element
|
||||
// (to be called by the peer)
|
||||
this.iframeElm_['XPC_toOuter'] = goog.bind(this.incoming_, this);
|
||||
|
||||
// at this point we just have to wait for a notification from the peer...
|
||||
|
||||
} else {
|
||||
this.attemptSetup_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Only used from within an iframe. Attempts to attach the method
|
||||
* to be used for sending messages by the containing document. Has to
|
||||
* wait until the containing document has finished. Therefore calls
|
||||
* itself in a timeout if not successful.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.attemptSetup_ = function() {
|
||||
var retry = true;
|
||||
/** @preserveTry */
|
||||
try {
|
||||
if (!this.iframeElm_) {
|
||||
// throws security exception when called too early
|
||||
this.iframeElm_ = this.getWindow().frameElement;
|
||||
}
|
||||
// check if iframe-element and the gateway-function to the
|
||||
// outer-frame are present
|
||||
// TODO(user) Make sure the following code doesn't throw any exceptions
|
||||
if (this.iframeElm_ && this.iframeElm_['XPC_toOuter']) {
|
||||
// get a reference to the gateway function
|
||||
this.outgoing_ = this.iframeElm_['XPC_toOuter'];
|
||||
// attach the gateway function the other document will use
|
||||
this.iframeElm_['XPC_toOuter']['XPC_toInner'] =
|
||||
goog.bind(this.incoming_, this);
|
||||
// stop retrying
|
||||
retry = false;
|
||||
// notify outer frame
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
|
||||
// notify channel that the transport is ready
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting setup: ' + e);
|
||||
}
|
||||
// retry necessary?
|
||||
if (retry) {
|
||||
if (!this.attemptSetupCb_) {
|
||||
this.attemptSetupCb_ = goog.bind(this.attemptSetup_, this);
|
||||
}
|
||||
this.getWindow().setTimeout(this.attemptSetupCb_, 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.transportServiceHandler =
|
||||
function(payload) {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER &&
|
||||
!this.channel_.isConnected() && payload == goog.net.xpc.SETUP_ACK_) {
|
||||
// get a reference to the gateway function
|
||||
this.outgoing_ = this.iframeElm_['XPC_toOuter']['XPC_toInner'];
|
||||
// notify the channel we're ready
|
||||
this.channel_.notifyConnected();
|
||||
} else {
|
||||
throw Error('Got unexpected transport message.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Process incoming message.
|
||||
* @param {string} serviceName The name of the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message to process.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.incoming_ =
|
||||
function(serviceName, payload) {
|
||||
if (!this.recursive_ && this.queue_.length == 0) {
|
||||
this.channel_.xpcDeliver(serviceName, payload);
|
||||
}
|
||||
else {
|
||||
this.queue_.push({serviceName: serviceName, payload: payload});
|
||||
if (this.queue_.length == 1) {
|
||||
this.timer_ = this.getWindow().setTimeout(this.deliverQueuedCb_, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delivers queued messages.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.deliverQueued_ =
|
||||
function() {
|
||||
while (this.queue_.length) {
|
||||
var msg = this.queue_.shift();
|
||||
this.channel_.xpcDeliver(msg.serviceName, msg.payload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send a message
|
||||
* @param {string} service The name off the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.send =
|
||||
function(service, payload) {
|
||||
this.recursive_ = true;
|
||||
this.outgoing_(service, payload);
|
||||
this.recursive_ = false;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.disposeInternal =
|
||||
function() {
|
||||
goog.net.xpc.FrameElementMethodTransport.superClass_.disposeInternal.call(
|
||||
this);
|
||||
this.outgoing_ = null;
|
||||
this.iframeElm_ = null;
|
||||
};
|
||||
@@ -0,0 +1,921 @@
|
||||
// Copyright 2007 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 Contains the iframe polling transport.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.IframePollingTransport');
|
||||
goog.provide('goog.net.xpc.IframePollingTransport.Receiver');
|
||||
goog.provide('goog.net.xpc.IframePollingTransport.Sender');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Iframe polling transport. Uses hidden iframes to transfer data
|
||||
* in the fragment identifier of the URL. The peer polls the iframe's location
|
||||
* for changes.
|
||||
* Unfortunately, in Safari this screws up the history, because Safari doesn't
|
||||
* allow to call location.replace() on a window containing a document from a
|
||||
* different domain (last version tested: 2.0.4).
|
||||
*
|
||||
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
|
||||
* transport belongs to.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
|
||||
* the correct window.
|
||||
* @constructor
|
||||
* @extends {goog.net.xpc.Transport}
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport = function(channel, opt_domHelper) {
|
||||
goog.base(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* The URI used to send messages.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.sendUri_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_POLL_URI];
|
||||
|
||||
/**
|
||||
* The URI which is polled for incoming messages.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.rcvUri_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.LOCAL_POLL_URI];
|
||||
|
||||
/**
|
||||
* The queue to hold messages which can't be sent immediately.
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.sendQueue_ = [];
|
||||
};
|
||||
goog.inherits(goog.net.xpc.IframePollingTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
/**
|
||||
* The number of times the inner frame will check for evidence of the outer
|
||||
* frame before it tries its reconnection sequence. These occur at 100ms
|
||||
* intervals, making this an effective max waiting period of 500ms.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.pollsBeforeReconnect_ = 5;
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.IFRAME_POLLING;
|
||||
|
||||
|
||||
/**
|
||||
* Sequence counter.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.sequence_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating whether we are waiting for an acknoledgement.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.waitForAck_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating if channel has been initialized.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.initialized_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Reconnection iframe created by inner peer.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.reconnectFrame_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The string used to prefix all iframe names and IDs.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.IFRAME_PREFIX = 'googlexpc';
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name/ID of the message frame.
|
||||
* @return {string} Name of message frame.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.getMsgFrameName_ = function() {
|
||||
return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +
|
||||
this.channel_.name + '_msg';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name/ID of the ack frame.
|
||||
* @return {string} Name of ack frame.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.getAckFrameName_ = function() {
|
||||
return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +
|
||||
this.channel_.name + '_ack';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the channel is still available. The channel is
|
||||
* unavailable if the transport was disposed or the peer is no longer
|
||||
* available.
|
||||
* @return {boolean} Whether the channel is available.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.isChannelAvailable = function() {
|
||||
return !this.isDisposed() && this.channel_.isPeerAvailable();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely retrieves the frames from the peer window. If an error is thrown
|
||||
* (e.g. the window is closing) an empty frame object is returned.
|
||||
* @return {!Object.<!Window>} The frames from the peer window.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.getPeerFrames_ = function() {
|
||||
try {
|
||||
if (this.isChannelAvailable()) {
|
||||
return this.channel_.getPeerWindowObject().frames || {};
|
||||
}
|
||||
} catch (e) {
|
||||
// An error may be thrown if the window is closing.
|
||||
goog.log.fine(goog.net.xpc.logger, 'error retrieving peer frames');
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely retrieves the peer frame with the specified name.
|
||||
* @param {string} frameName The name of the peer frame to retrieve.
|
||||
* @return {Window} The peer frame with the specified name.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.getPeerFrame_ = function(
|
||||
frameName) {
|
||||
return this.getPeerFrames_()[frameName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Connects this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.connect = function() {
|
||||
if (!this.isChannelAvailable()) {
|
||||
// When the channel is unavailable there is no peer to poll so stop trying
|
||||
// to connect.
|
||||
return;
|
||||
}
|
||||
|
||||
goog.log.fine(goog.net.xpc.logger, 'transport connect called');
|
||||
if (!this.initialized_) {
|
||||
goog.log.fine(goog.net.xpc.logger, 'initializing...');
|
||||
this.constructSenderFrames_();
|
||||
this.initialized_ = true;
|
||||
}
|
||||
this.checkForeignFramesReady_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the iframes which are used to send messages (and acknowledgements)
|
||||
* to the peer. Sender iframes contain a document from a different origin and
|
||||
* therefore their content can't be accessed.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.constructSenderFrames_ =
|
||||
function() {
|
||||
var name = this.getMsgFrameName_();
|
||||
this.msgIframeElm_ = this.constructSenderFrame_(name);
|
||||
this.msgWinObj_ = this.getWindow().frames[name];
|
||||
|
||||
name = this.getAckFrameName_();
|
||||
this.ackIframeElm_ = this.constructSenderFrame_(name);
|
||||
this.ackWinObj_ = this.getWindow().frames[name];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a sending frame the the given id.
|
||||
* @param {string} id The id.
|
||||
* @return {Element} The constructed frame.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.constructSenderFrame_ =
|
||||
function(id) {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'constructing sender frame: ' + id);
|
||||
var ifr = goog.dom.createElement('iframe');
|
||||
var s = ifr.style;
|
||||
s.position = 'absolute';
|
||||
s.top = '-10px'; s.left = '10px'; s.width = '1px'; s.height = '1px';
|
||||
ifr.id = ifr.name = id;
|
||||
ifr.src = this.sendUri_ + '#INITIAL';
|
||||
this.getWindow().document.body.appendChild(ifr);
|
||||
return ifr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The protocol for reconnecting is for the inner frame to change channel
|
||||
* names, and then communicate the new channel name to the outer peer.
|
||||
* The outer peer looks in a predefined location for the channel name
|
||||
* upate. It is important to use a completely new channel name, as this
|
||||
* will ensure that all messaging iframes are not in the bfcache.
|
||||
* Otherwise, Safari may pollute the history when modifying the location
|
||||
* of bfcached iframes.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.maybeInnerPeerReconnect_ =
|
||||
function() {
|
||||
// Reconnection has been found to not function on some browsers (eg IE7), so
|
||||
// it's important that the mechanism only be triggered as a last resort. As
|
||||
// such, we poll a number of times to find the outer iframe before triggering
|
||||
// it.
|
||||
if (this.reconnectFrame_ || this.pollsBeforeReconnect_-- > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'Inner peer reconnect triggered.');
|
||||
this.channel_.name = goog.net.xpc.getRandomString(10);
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'switching channels: ' + this.channel_.name);
|
||||
this.deconstructSenderFrames_();
|
||||
this.initialized_ = false;
|
||||
// Communicate new channel name to outer peer.
|
||||
this.reconnectFrame_ = this.constructSenderFrame_(
|
||||
goog.net.xpc.IframePollingTransport.IFRAME_PREFIX +
|
||||
'_reconnect_' + this.channel_.name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Scans inner peer for a reconnect message, which will be used to update
|
||||
* the outer peer's channel name. If a reconnect message is found, the
|
||||
* sender frames will be cleaned up to make way for the new sender frames.
|
||||
* Only called by the outer peer.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.outerPeerReconnect_ = function() {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'outerPeerReconnect called');
|
||||
var frames = this.getPeerFrames_();
|
||||
var length = frames.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var frameName;
|
||||
try {
|
||||
if (frames[i] && frames[i].name) {
|
||||
frameName = frames[i].name;
|
||||
}
|
||||
} catch (e) {
|
||||
// Do nothing.
|
||||
}
|
||||
if (!frameName) {
|
||||
continue;
|
||||
}
|
||||
var message = frameName.split('_');
|
||||
if (message.length == 3 &&
|
||||
message[0] == goog.net.xpc.IframePollingTransport.IFRAME_PREFIX &&
|
||||
message[1] == 'reconnect') {
|
||||
// This is a legitimate reconnect message from the peer. Start using
|
||||
// the peer provided channel name, and start a connection over from
|
||||
// scratch.
|
||||
this.channel_.name = message[2];
|
||||
this.deconstructSenderFrames_();
|
||||
this.initialized_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up the existing sender frames owned by this peer. Only called by
|
||||
* the outer peer.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.deconstructSenderFrames_ =
|
||||
function() {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'deconstructSenderFrames called');
|
||||
if (this.msgIframeElm_) {
|
||||
this.msgIframeElm_.parentNode.removeChild(this.msgIframeElm_);
|
||||
this.msgIframeElm_ = null;
|
||||
this.msgWinObj_ = null;
|
||||
}
|
||||
if (this.ackIframeElm_) {
|
||||
this.ackIframeElm_.parentNode.removeChild(this.ackIframeElm_);
|
||||
this.ackIframeElm_ = null;
|
||||
this.ackWinObj_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the frames in the peer's page are ready. These contain a
|
||||
* document from the own domain and are the ones messages are received through.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.checkForeignFramesReady_ =
|
||||
function() {
|
||||
// check if the connected iframe ready
|
||||
if (!(this.isRcvFrameReady_(this.getMsgFrameName_()) &&
|
||||
this.isRcvFrameReady_(this.getAckFrameName_()))) {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'foreign frames not (yet) present');
|
||||
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {
|
||||
// The outer peer might need a short time to get its frames ready, as
|
||||
// CrossPageChannel prevents them from getting created until the inner
|
||||
// peer's frame has thrown its loaded event. This method is a noop for
|
||||
// the first few times it's called, and then allows the reconnection
|
||||
// sequence to begin.
|
||||
this.maybeInnerPeerReconnect_();
|
||||
} else if (this.channel_.getRole() ==
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER) {
|
||||
// The inner peer is either not loaded yet, or the receiving
|
||||
// frames are simply missing. Since we cannot discern the two cases, we
|
||||
// should scan for a reconnect message from the inner peer.
|
||||
this.outerPeerReconnect_();
|
||||
}
|
||||
|
||||
// start a timer to check again
|
||||
this.getWindow().setTimeout(goog.bind(this.connect, this), 100);
|
||||
} else {
|
||||
goog.log.fine(goog.net.xpc.logger, 'foreign frames present');
|
||||
|
||||
// Create receivers.
|
||||
this.msgReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(
|
||||
this,
|
||||
this.getPeerFrame_(this.getMsgFrameName_()),
|
||||
goog.bind(this.processIncomingMsg, this));
|
||||
this.ackReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(
|
||||
this,
|
||||
this.getPeerFrame_(this.getAckFrameName_()),
|
||||
goog.bind(this.processIncomingAck, this));
|
||||
|
||||
this.checkLocalFramesPresent_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the receiving frame is ready.
|
||||
* @param {string} frameName Which receiving frame to check.
|
||||
* @return {boolean} Whether the receiving frame is ready.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.isRcvFrameReady_ =
|
||||
function(frameName) {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'checking for receive frame: ' + frameName);
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var winObj = this.getPeerFrame_(frameName);
|
||||
if (!winObj || winObj.location.href.indexOf(this.rcvUri_) != 0) {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the iframes created in the own document are ready.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresent_ =
|
||||
function() {
|
||||
|
||||
// Are the sender frames ready?
|
||||
// These contain a document from the peer's domain, therefore we can only
|
||||
// check if the frame itself is present.
|
||||
var frames = this.getPeerFrames_();
|
||||
if (!(frames[this.getAckFrameName_()] &&
|
||||
frames[this.getMsgFrameName_()])) {
|
||||
// start a timer to check again
|
||||
if (!this.checkLocalFramesPresentCb_) {
|
||||
this.checkLocalFramesPresentCb_ = goog.bind(
|
||||
this.checkLocalFramesPresent_, this);
|
||||
}
|
||||
this.getWindow().setTimeout(this.checkLocalFramesPresentCb_, 100);
|
||||
goog.log.fine(goog.net.xpc.logger, 'local frames not (yet) present');
|
||||
} else {
|
||||
// Create senders.
|
||||
this.msgSender_ = new goog.net.xpc.IframePollingTransport.Sender(
|
||||
this.sendUri_, this.msgWinObj_);
|
||||
this.ackSender_ = new goog.net.xpc.IframePollingTransport.Sender(
|
||||
this.sendUri_, this.ackWinObj_);
|
||||
|
||||
goog.log.fine(goog.net.xpc.logger, 'local frames ready');
|
||||
|
||||
this.getWindow().setTimeout(goog.bind(function() {
|
||||
this.msgSender_.send(goog.net.xpc.SETUP);
|
||||
this.sentConnectionSetup_ = true;
|
||||
this.waitForAck_ = true;
|
||||
goog.log.fine(goog.net.xpc.logger, 'SETUP sent');
|
||||
}, this), 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if connection is ready.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.checkIfConnected_ = function() {
|
||||
if (this.sentConnectionSetupAck_ && this.rcvdConnectionSetupAck_) {
|
||||
this.channel_.notifyConnected();
|
||||
|
||||
if (this.deliveryQueue_) {
|
||||
goog.log.fine(goog.net.xpc.logger, 'delivering queued messages ' +
|
||||
'(' + this.deliveryQueue_.length + ')');
|
||||
|
||||
for (var i = 0, m; i < this.deliveryQueue_.length; i++) {
|
||||
m = this.deliveryQueue_[i];
|
||||
this.channel_.xpcDeliver(m.service, m.payload);
|
||||
}
|
||||
delete this.deliveryQueue_;
|
||||
}
|
||||
} else {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'checking if connected: ' +
|
||||
'ack sent:' + this.sentConnectionSetupAck_ +
|
||||
', ack rcvd: ' + this.rcvdConnectionSetupAck_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Processes an incoming message.
|
||||
* @param {string} raw The complete received string.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.processIncomingMsg =
|
||||
function(raw) {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'msg received: ' + raw);
|
||||
|
||||
if (raw == goog.net.xpc.SETUP) {
|
||||
if (!this.ackSender_) {
|
||||
// Got SETUP msg, but we can't send an ack.
|
||||
return;
|
||||
}
|
||||
|
||||
this.ackSender_.send(goog.net.xpc.SETUP_ACK_);
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'SETUP_ACK sent');
|
||||
|
||||
this.sentConnectionSetupAck_ = true;
|
||||
this.checkIfConnected_();
|
||||
|
||||
} else if (this.channel_.isConnected() || this.sentConnectionSetupAck_) {
|
||||
|
||||
var pos = raw.indexOf('|');
|
||||
var head = raw.substring(0, pos);
|
||||
var frame = raw.substring(pos + 1);
|
||||
|
||||
// check if it is a framed message
|
||||
pos = head.indexOf(',');
|
||||
if (pos == -1) {
|
||||
var seq = head;
|
||||
// send acknowledgement
|
||||
this.ackSender_.send('ACK:' + seq);
|
||||
this.deliverPayload_(frame);
|
||||
} else {
|
||||
var seq = head.substring(0, pos);
|
||||
// send acknowledgement
|
||||
this.ackSender_.send('ACK:' + seq);
|
||||
|
||||
var partInfo = head.substring(pos + 1).split('/');
|
||||
var part0 = parseInt(partInfo[0], 10);
|
||||
var part1 = parseInt(partInfo[1], 10);
|
||||
// create an array to accumulate the parts if this is the
|
||||
// first frame of a message
|
||||
if (part0 == 1) {
|
||||
this.parts_ = [];
|
||||
}
|
||||
this.parts_.push(frame);
|
||||
// deliver the message if this was the last frame of a message
|
||||
if (part0 == part1) {
|
||||
this.deliverPayload_(this.parts_.join(''));
|
||||
delete this.parts_;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'received msg, but channel is not connected');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming acknowdedgement.
|
||||
* @param {string} msgStr The incoming ack string to process.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.processIncomingAck =
|
||||
function(msgStr) {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'ack received: ' + msgStr);
|
||||
|
||||
if (msgStr == goog.net.xpc.SETUP_ACK_) {
|
||||
this.waitForAck_ = false;
|
||||
this.rcvdConnectionSetupAck_ = true;
|
||||
// send the next frame
|
||||
this.checkIfConnected_();
|
||||
|
||||
} else if (this.channel_.isConnected()) {
|
||||
if (!this.waitForAck_) {
|
||||
goog.log.warning(goog.net.xpc.logger, 'got unexpected ack');
|
||||
return;
|
||||
}
|
||||
|
||||
var seq = parseInt(msgStr.split(':')[1], 10);
|
||||
if (seq == this.sequence_) {
|
||||
this.waitForAck_ = false;
|
||||
this.sendNextFrame_();
|
||||
} else {
|
||||
goog.log.warning(goog.net.xpc.logger, 'got ack with wrong sequence');
|
||||
}
|
||||
} else {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'received ack, but channel not connected');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a frame (message part).
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.sendNextFrame_ = function() {
|
||||
// do nothing if we are waiting for an acknowledgement or the
|
||||
// queue is emtpy
|
||||
if (this.waitForAck_ || !this.sendQueue_.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var s = this.sendQueue_.shift();
|
||||
++this.sequence_;
|
||||
this.msgSender_.send(this.sequence_ + s);
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'msg sent: ' + this.sequence_ + s);
|
||||
|
||||
|
||||
this.waitForAck_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delivers a message.
|
||||
* @param {string} s The complete message string ("<service_name>:<payload>").
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.deliverPayload_ = function(s) {
|
||||
// determine the service name and the payload
|
||||
var pos = s.indexOf(':');
|
||||
var service = s.substr(0, pos);
|
||||
var payload = s.substring(pos + 1);
|
||||
|
||||
// deliver the message
|
||||
if (!this.channel_.isConnected()) {
|
||||
// as valid messages can come in before a SETUP_ACK has
|
||||
// been received (because subchannels for msgs and acks are independent),
|
||||
// delay delivery of early messages until after 'connect'-event
|
||||
(this.deliveryQueue_ || (this.deliveryQueue_ = [])).
|
||||
push({service: service, payload: payload});
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'queued delivery');
|
||||
} else {
|
||||
this.channel_.xpcDeliver(service, payload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ---- send message ----
|
||||
|
||||
|
||||
/**
|
||||
* Maximal frame length.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.MAX_FRAME_LENGTH_ = 3800;
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message. Splits it in multiple frames if too long (exceeds IE's
|
||||
* URL-length maximum.
|
||||
* Wireformat: <seq>[,<frame_no>/<#frames>]|<frame_content>
|
||||
*
|
||||
* @param {string} service Name of service this the message has to be delivered.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.send =
|
||||
function(service, payload) {
|
||||
var frame = service + ':' + payload;
|
||||
// put in queue
|
||||
if (!goog.userAgent.IE || payload.length <= this.MAX_FRAME_LENGTH_) {
|
||||
this.sendQueue_.push('|' + frame);
|
||||
}
|
||||
else {
|
||||
var l = payload.length;
|
||||
var num = Math.ceil(l / this.MAX_FRAME_LENGTH_); // number of frames
|
||||
var pos = 0;
|
||||
var i = 1;
|
||||
while (pos < l) {
|
||||
this.sendQueue_.push(',' + i + '/' + num + '|' +
|
||||
frame.substr(pos, this.MAX_FRAME_LENGTH_));
|
||||
i++;
|
||||
pos += this.MAX_FRAME_LENGTH_;
|
||||
}
|
||||
}
|
||||
this.sendNextFrame_();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.IframePollingTransport.prototype.disposeInternal = function() {
|
||||
goog.base(this, 'disposeInternal');
|
||||
|
||||
var receivers = goog.net.xpc.IframePollingTransport.receivers_;
|
||||
goog.array.remove(receivers, this.msgReceiver_);
|
||||
goog.array.remove(receivers, this.ackReceiver_);
|
||||
this.msgReceiver_ = this.ackReceiver_ = null;
|
||||
|
||||
goog.dom.removeNode(this.msgIframeElm_);
|
||||
goog.dom.removeNode(this.ackIframeElm_);
|
||||
this.msgIframeElm_ = this.ackIframeElm_ = null;
|
||||
this.msgWinObj_ = this.ackWinObj_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Array holding all Receiver-instances.
|
||||
* @type {Array.<goog.net.xpc.IframePollingTransport.Receiver>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.receivers_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Short polling interval.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ = 10;
|
||||
|
||||
|
||||
/**
|
||||
* Long polling interval.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_ = 100;
|
||||
|
||||
|
||||
/**
|
||||
* Period how long to use TIME_POLL_SHORT_ before raising polling-interval
|
||||
* to TIME_POLL_LONG_ after an activity.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ =
|
||||
1000;
|
||||
|
||||
|
||||
/**
|
||||
* Polls all receivers.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.receive_ = function() {
|
||||
var receivers = goog.net.xpc.IframePollingTransport.receivers_;
|
||||
var receiver;
|
||||
var rcvd = false;
|
||||
|
||||
/** @preserveTry */
|
||||
try {
|
||||
for (var i = 0; receiver = receivers[i]; i++) {
|
||||
rcvd = rcvd || receiver.receive();
|
||||
}
|
||||
} catch (e) {
|
||||
goog.log.info(goog.net.xpc.logger, 'receive_() failed: ' + e);
|
||||
|
||||
// Notify the channel that the transport had an error.
|
||||
receiver.transport_.channel_.notifyTransportError();
|
||||
|
||||
// notifyTransportError() closes the channel and disposes the transport.
|
||||
// If there are no other channels present, this.receivers_ will now be empty
|
||||
// and there is no need to keep polling.
|
||||
if (!receivers.length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var now = goog.now();
|
||||
if (rcvd) {
|
||||
goog.net.xpc.IframePollingTransport.lastActivity_ = now;
|
||||
}
|
||||
|
||||
// Schedule next check.
|
||||
var t = now - goog.net.xpc.IframePollingTransport.lastActivity_ <
|
||||
goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ ?
|
||||
goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ :
|
||||
goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_;
|
||||
goog.net.xpc.IframePollingTransport.rcvTimer_ = window.setTimeout(
|
||||
goog.net.xpc.IframePollingTransport.receiveCb_, t);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that wraps receive_ to be used in timers.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.receiveCb_ = goog.bind(
|
||||
goog.net.xpc.IframePollingTransport.receive_,
|
||||
goog.net.xpc.IframePollingTransport);
|
||||
|
||||
|
||||
/**
|
||||
* Starts the polling loop.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.startRcvTimer_ = function() {
|
||||
goog.log.fine(goog.net.xpc.logger, 'starting receive-timer');
|
||||
goog.net.xpc.IframePollingTransport.lastActivity_ = goog.now();
|
||||
if (goog.net.xpc.IframePollingTransport.rcvTimer_) {
|
||||
window.clearTimeout(goog.net.xpc.IframePollingTransport.rcvTimer_);
|
||||
}
|
||||
goog.net.xpc.IframePollingTransport.rcvTimer_ = window.setTimeout(
|
||||
goog.net.xpc.IframePollingTransport.receiveCb_,
|
||||
goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* goog.net.xpc.IframePollingTransport.Sender
|
||||
*
|
||||
* Utility class to send message-parts to a document from a different origin.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} url The url the other document will use for polling.
|
||||
* @param {Object} windowObj The frame used for sending information to.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.Sender = function(url, windowObj) {
|
||||
/**
|
||||
* The URI used to sending messages.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.sendUri_ = url;
|
||||
|
||||
/**
|
||||
* The window object of the iframe used to send messages.
|
||||
* The script instantiating the Sender won't have access to
|
||||
* the content of sendFrame_.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.sendFrame_ = windowObj;
|
||||
|
||||
/**
|
||||
* Cycle counter (used to make sure that sending two identical messages sent
|
||||
* in direct succession can be recognized as such by the receiver).
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.cycle_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message-part (frame) to the peer.
|
||||
* The message-part is encoded and put in the fragment identifier
|
||||
* of the URL used for sending (and belongs to the origin/domain of the peer).
|
||||
* @param {string} payload The message to send.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.Sender.prototype.send = function(payload) {
|
||||
this.cycle_ = ++this.cycle_ % 2;
|
||||
|
||||
var url = this.sendUri_ + '#' + this.cycle_ + encodeURIComponent(payload);
|
||||
|
||||
// TODO(user) Find out if try/catch is still needed
|
||||
/** @preserveTry */
|
||||
try {
|
||||
// safari doesn't allow to call location.replace()
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
this.sendFrame_.location.href = url;
|
||||
} else {
|
||||
this.sendFrame_.location.replace(url);
|
||||
}
|
||||
} catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger, 'sending failed', e);
|
||||
}
|
||||
|
||||
// Restart receiver timer on short polling interval, to support use-cases
|
||||
// where we need to capture responses quickly.
|
||||
goog.net.xpc.IframePollingTransport.startRcvTimer_();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* goog.net.xpc.IframePollingTransport.Receiver
|
||||
*
|
||||
* @constructor
|
||||
* @param {goog.net.xpc.IframePollingTransport} transport The transport to
|
||||
* receive from.
|
||||
* @param {Object} windowObj The window-object to poll for location-changes.
|
||||
* @param {Function} callback The callback-function to be called when
|
||||
* location has changed.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.Receiver = function(transport,
|
||||
windowObj,
|
||||
callback) {
|
||||
/**
|
||||
* The transport to receive from.
|
||||
* @type {goog.net.xpc.IframePollingTransport}
|
||||
* @private
|
||||
*/
|
||||
this.transport_ = transport;
|
||||
this.rcvFrame_ = windowObj;
|
||||
|
||||
this.cb_ = callback;
|
||||
this.currentLoc_ = this.rcvFrame_.location.href.split('#')[0] + '#INITIAL';
|
||||
|
||||
goog.net.xpc.IframePollingTransport.receivers_.push(this);
|
||||
goog.net.xpc.IframePollingTransport.startRcvTimer_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Polls the location of the receiver-frame for changes.
|
||||
* @return {boolean} Whether a change has been detected.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.Receiver.prototype.receive = function() {
|
||||
var loc = this.rcvFrame_.location.href;
|
||||
|
||||
if (loc != this.currentLoc_) {
|
||||
this.currentLoc_ = loc;
|
||||
var payload = loc.split('#')[1];
|
||||
if (payload) {
|
||||
payload = payload.substr(1); // discard first character (cycle)
|
||||
this.cb_(decodeURIComponent(payload));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,394 @@
|
||||
// Copyright 2007 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 Contains the iframe relay tranport.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.IframeRelayTransport');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Iframe relay transport. Creates hidden iframes containing a document
|
||||
* from the peer's origin. Data is transferred in the fragment identifier.
|
||||
* Therefore the document loaded in the iframes can be served from the
|
||||
* browser's cache.
|
||||
*
|
||||
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
|
||||
* transport belongs to.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
|
||||
* the correct window.
|
||||
* @constructor
|
||||
* @extends {goog.net.xpc.Transport}
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport = function(channel, opt_domHelper) {
|
||||
goog.base(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* The URI used to relay data to the peer.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.peerRelayUri_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_RELAY_URI];
|
||||
|
||||
/**
|
||||
* The id of the iframe the peer page lives in.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.peerIframeId_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.IFRAME_ID];
|
||||
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
goog.net.xpc.IframeRelayTransport.startCleanupTimer_();
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.net.xpc.IframeRelayTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
/**
|
||||
* Array to keep references to the relay-iframes. Used only if
|
||||
* there is no way to detect when the iframes are loaded. In that
|
||||
* case the relay-iframes are removed after a timeout.
|
||||
* @type {Array.<Object>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.iframeRefs_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Interval at which iframes are destroyed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_ = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Time after which a relay-iframe is destroyed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_ = 3000;
|
||||
|
||||
|
||||
/**
|
||||
* The cleanup timer id.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Starts the cleanup timer.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.startCleanupTimer_ = function() {
|
||||
if (!goog.net.xpc.IframeRelayTransport.cleanupTimer_) {
|
||||
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = window.setTimeout(
|
||||
function() { goog.net.xpc.IframeRelayTransport.cleanup_(); },
|
||||
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove all relay-iframes which are older than the maximal age.
|
||||
* @param {number=} opt_maxAge The maximal age in milliseconds.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.cleanup_ = function(opt_maxAge) {
|
||||
var now = goog.now();
|
||||
var maxAge =
|
||||
opt_maxAge || goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_;
|
||||
|
||||
while (goog.net.xpc.IframeRelayTransport.iframeRefs_.length &&
|
||||
now - goog.net.xpc.IframeRelayTransport.iframeRefs_[0].timestamp >=
|
||||
maxAge) {
|
||||
var ifr = goog.net.xpc.IframeRelayTransport.iframeRefs_.
|
||||
shift().iframeElement;
|
||||
goog.dom.removeNode(ifr);
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'iframe removed');
|
||||
}
|
||||
|
||||
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = window.setTimeout(
|
||||
goog.net.xpc.IframeRelayTransport.cleanupCb_,
|
||||
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function which wraps cleanup_().
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.cleanupCb_ = function() {
|
||||
goog.net.xpc.IframeRelayTransport.cleanup_();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maximum sendable size of a payload via a single iframe in IE.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_ = 1800;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{fragments: !Array.<string>, received: number, expected: number}}
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.FragmentInfo;
|
||||
|
||||
|
||||
/**
|
||||
* Used to track incoming payload fragments. The implementation can process
|
||||
* incoming fragments from several channels at a time, even if data is
|
||||
* out-of-order or interleaved.
|
||||
*
|
||||
* @type {!Object.<string, !goog.net.xpc.IframeRelayTransport.FragmentInfo>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.fragmentMap_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.IFRAME_RELAY;
|
||||
|
||||
|
||||
/**
|
||||
* Connects this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.connect = function() {
|
||||
if (!this.getWindow()['xpcRelay']) {
|
||||
this.getWindow()['xpcRelay'] =
|
||||
goog.net.xpc.IframeRelayTransport.receiveMessage_;
|
||||
}
|
||||
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Processes an incoming message.
|
||||
*
|
||||
* @param {string} channelName The name of the channel.
|
||||
* @param {string} frame The raw frame content.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.receiveMessage_ =
|
||||
function(channelName, frame) {
|
||||
var pos = frame.indexOf(':');
|
||||
var header = frame.substr(0, pos);
|
||||
var payload = frame.substr(pos + 1);
|
||||
|
||||
if (!goog.userAgent.IE || (pos = header.indexOf('|')) == -1) {
|
||||
// First, the easy case.
|
||||
var service = header;
|
||||
} else {
|
||||
// There was a fragment id in the header, so this is a message
|
||||
// fragment, not a whole message.
|
||||
var service = header.substr(0, pos);
|
||||
var fragmentIdStr = header.substr(pos + 1);
|
||||
|
||||
// Separate the message id string and the fragment number. Note that
|
||||
// there may be a single leading + in the argument to parseInt, but
|
||||
// this is harmless.
|
||||
pos = fragmentIdStr.indexOf('+');
|
||||
var messageIdStr = fragmentIdStr.substr(0, pos);
|
||||
var fragmentNum = parseInt(fragmentIdStr.substr(pos + 1), 10);
|
||||
var fragmentInfo =
|
||||
goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
|
||||
if (!fragmentInfo) {
|
||||
fragmentInfo =
|
||||
goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr] =
|
||||
{fragments: [], received: 0, expected: 0};
|
||||
}
|
||||
|
||||
if (goog.string.contains(fragmentIdStr, '++')) {
|
||||
fragmentInfo.expected = fragmentNum + 1;
|
||||
}
|
||||
fragmentInfo.fragments[fragmentNum] = payload;
|
||||
fragmentInfo.received++;
|
||||
|
||||
if (fragmentInfo.received != fragmentInfo.expected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We've received all outstanding fragments; combine what we've received
|
||||
// into payload and fall out to the call to xpcDeliver.
|
||||
payload = fragmentInfo.fragments.join('');
|
||||
delete goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
|
||||
}
|
||||
|
||||
goog.net.xpc.channels[channelName].
|
||||
xpcDeliver(service, decodeURIComponent(payload));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages (internal signalling).
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.transportServiceHandler =
|
||||
function(payload) {
|
||||
if (payload == goog.net.xpc.SETUP) {
|
||||
// TODO(user) Safari swallows the SETUP_ACK from the iframe to the
|
||||
// container after hitting reload.
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
else if (payload == goog.net.xpc.SETUP_ACK_) {
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param {string} service Name of service this the message has to be delivered.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.send = function(service, payload) {
|
||||
// If we're on IE and the post-encoding payload is large, split it
|
||||
// into multiple payloads and send each one separately. Otherwise,
|
||||
// just send the whole thing.
|
||||
var encodedPayload = encodeURIComponent(payload);
|
||||
var encodedLen = encodedPayload.length;
|
||||
var maxSize = goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_;
|
||||
|
||||
if (goog.userAgent.IE && encodedLen > maxSize) {
|
||||
// A probabilistically-unique string used to link together all fragments
|
||||
// in this message.
|
||||
var messageIdStr = goog.string.getRandomString();
|
||||
|
||||
for (var startIndex = 0, fragmentNum = 0; startIndex < encodedLen;
|
||||
fragmentNum++) {
|
||||
var payloadFragment = encodedPayload.substr(startIndex, maxSize);
|
||||
startIndex += maxSize;
|
||||
var fragmentIdStr =
|
||||
messageIdStr + (startIndex >= encodedLen ? '++' : '+') + fragmentNum;
|
||||
this.send_(service, payloadFragment, fragmentIdStr);
|
||||
}
|
||||
} else {
|
||||
this.send_(service, encodedPayload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends an encoded message or message fragment.
|
||||
* @param {string} service Name of service this the message has to be delivered.
|
||||
* @param {string} encodedPayload The message content, URI encoded.
|
||||
* @param {string=} opt_fragmentIdStr If sending a fragment, a string that
|
||||
* identifies the fragment.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.send_ =
|
||||
function(service, encodedPayload, opt_fragmentIdStr) {
|
||||
// IE requires that we create the onload attribute inline, otherwise the
|
||||
// handler is not triggered
|
||||
if (goog.userAgent.IE) {
|
||||
var div = this.getWindow().document.createElement('div');
|
||||
div.innerHTML = '<iframe onload="this.xpcOnload()"></iframe>';
|
||||
var ifr = div.childNodes[0];
|
||||
div = null;
|
||||
ifr['xpcOnload'] = goog.net.xpc.IframeRelayTransport.iframeLoadHandler_;
|
||||
} else {
|
||||
var ifr = this.getWindow().document.createElement('iframe');
|
||||
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
// safari doesn't fire load-events on iframes.
|
||||
// keep a reference and remove after a timeout.
|
||||
goog.net.xpc.IframeRelayTransport.iframeRefs_.push({
|
||||
timestamp: goog.now(),
|
||||
iframeElement: ifr
|
||||
});
|
||||
} else {
|
||||
goog.events.listen(ifr, 'load',
|
||||
goog.net.xpc.IframeRelayTransport.iframeLoadHandler_);
|
||||
}
|
||||
}
|
||||
|
||||
var style = ifr.style;
|
||||
style.visibility = 'hidden';
|
||||
style.width = ifr.style.height = '0px';
|
||||
style.position = 'absolute';
|
||||
|
||||
var url = this.peerRelayUri_;
|
||||
url += '#' + this.channel_.name;
|
||||
if (this.peerIframeId_) {
|
||||
url += ',' + this.peerIframeId_;
|
||||
}
|
||||
url += '|' + service;
|
||||
if (opt_fragmentIdStr) {
|
||||
url += '|' + opt_fragmentIdStr;
|
||||
}
|
||||
url += ':' + encodedPayload;
|
||||
|
||||
ifr.src = url;
|
||||
|
||||
this.getWindow().document.body.appendChild(ifr);
|
||||
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'msg sent: ' + url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The iframe load handler. Gets called as method on the iframe element.
|
||||
* @private
|
||||
* @this Element
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.iframeLoadHandler_ = function() {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe-load');
|
||||
goog.dom.removeNode(this);
|
||||
this.xpcOnload = null;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.IframeRelayTransport.prototype.disposeInternal = function() {
|
||||
goog.base(this, 'disposeInternal');
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
goog.net.xpc.IframeRelayTransport.cleanup_(0);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,649 @@
|
||||
// Copyright 2007 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 Contains the class which uses native messaging
|
||||
* facilities for cross domain communication.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.NativeMessagingTransport');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The native messaging transport
|
||||
*
|
||||
* Uses document.postMessage() to send messages to other documents.
|
||||
* Receiving is done by listening on 'message'-events on the document.
|
||||
*
|
||||
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
|
||||
* transport belongs to.
|
||||
* @param {string} peerHostname The hostname (protocol, domain, and port) of the
|
||||
* peer.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
|
||||
* finding the correct window/document.
|
||||
* @param {boolean=} opt_oneSidedHandshake If this is true, only the outer
|
||||
* transport sends a SETUP message and expects a SETUP_ACK. The inner
|
||||
* transport goes connected when it receives the SETUP.
|
||||
* @param {number=} opt_protocolVersion Which version of its setup protocol the
|
||||
* transport should use. The default is '2'.
|
||||
* @constructor
|
||||
* @extends {goog.net.xpc.Transport}
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport = function(channel, peerHostname,
|
||||
opt_domHelper, opt_oneSidedHandshake, opt_protocolVersion) {
|
||||
goog.base(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* Which version of the transport's protocol should be used.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.protocolVersion_ = opt_protocolVersion || 2;
|
||||
goog.asserts.assert(this.protocolVersion_ >= 1);
|
||||
goog.asserts.assert(this.protocolVersion_ <= 2);
|
||||
|
||||
/**
|
||||
* The hostname of the peer. This parameterizes all calls to postMessage, and
|
||||
* should contain the precise protocol, domain, and port of the peer window.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.peerHostname_ = peerHostname || '*';
|
||||
|
||||
/**
|
||||
* The event handler.
|
||||
* @type {!goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
|
||||
/**
|
||||
* Timer for connection reattempts.
|
||||
* @type {!goog.Timer}
|
||||
* @private
|
||||
*/
|
||||
this.maybeAttemptToConnectTimer_ = new goog.Timer(100, this.getWindow());
|
||||
|
||||
/**
|
||||
* Whether one-sided handshakes are enabled.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.oneSidedHandshake_ = !!opt_oneSidedHandshake;
|
||||
|
||||
/**
|
||||
* Fires once we've received our SETUP_ACK message.
|
||||
* @type {!goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
this.setupAckReceived_ = new goog.async.Deferred();
|
||||
|
||||
/**
|
||||
* Fires once we've sent our SETUP_ACK message.
|
||||
* @type {!goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
this.setupAckSent_ = new goog.async.Deferred();
|
||||
|
||||
/**
|
||||
* Fires once we're marked connected.
|
||||
* @type {!goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
this.connected_ = new goog.async.Deferred();
|
||||
|
||||
/**
|
||||
* The unique ID of this side of the connection. Used to determine when a peer
|
||||
* is reloaded.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.endpointId_ = goog.net.xpc.getRandomString(10);
|
||||
|
||||
/**
|
||||
* The unique ID of the peer. If we get a message from a peer with an ID we
|
||||
* don't expect, we reset the connection.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.peerEndpointId_ = null;
|
||||
|
||||
// We don't want to mark ourselves connected until we have sent whatever
|
||||
// message will cause our counterpart in the other frame to also declare
|
||||
// itself connected, if there is such a message. Otherwise we risk a user
|
||||
// message being sent in advance of that message, and it being discarded.
|
||||
if (this.oneSidedHandshake_) {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {
|
||||
// One sided handshake, inner frame:
|
||||
// SETUP_ACK must be received.
|
||||
this.connected_.awaitDeferred(this.setupAckReceived_);
|
||||
} else {
|
||||
// One sided handshake, outer frame:
|
||||
// SETUP_ACK must be sent.
|
||||
this.connected_.awaitDeferred(this.setupAckSent_);
|
||||
}
|
||||
} else {
|
||||
// Two sided handshake:
|
||||
// SETUP_ACK has to have been received, and sent.
|
||||
this.connected_.awaitDeferred(this.setupAckReceived_);
|
||||
if (this.protocolVersion_ == 2) {
|
||||
this.connected_.awaitDeferred(this.setupAckSent_);
|
||||
}
|
||||
}
|
||||
this.connected_.addCallback(this.notifyConnected_, this);
|
||||
this.connected_.callback(true);
|
||||
|
||||
this.eventHandler_.
|
||||
listen(this.maybeAttemptToConnectTimer_, goog.Timer.TICK,
|
||||
this.maybeAttemptToConnect_);
|
||||
|
||||
goog.log.info(goog.net.xpc.logger, 'NativeMessagingTransport created. ' +
|
||||
'protocolVersion=' + this.protocolVersion_ + ', oneSidedHandshake=' +
|
||||
this.oneSidedHandshake_ + ', role=' + this.channel_.getRole());
|
||||
};
|
||||
goog.inherits(goog.net.xpc.NativeMessagingTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
/**
|
||||
* Length of the delay in milliseconds between the channel being connected and
|
||||
* the connection callback being called, in cases where coverage of timing flaws
|
||||
* is required.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ = 200;
|
||||
|
||||
|
||||
/**
|
||||
* Current determination of peer's protocol version, or null for unknown.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.peerProtocolVersion_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating if this instance of the transport has been initialized.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.initialized_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.NATIVE_MESSAGING;
|
||||
|
||||
|
||||
/**
|
||||
* The delimiter used for transport service messages.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_ = ',';
|
||||
|
||||
|
||||
/**
|
||||
* Tracks the number of NativeMessagingTransport channels that have been
|
||||
* initialized but not disposed yet in a map keyed by the UID of the window
|
||||
* object. This allows for multiple windows to be initiallized and listening
|
||||
* for messages.
|
||||
* @type {Object.<number>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.activeCount_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Id of a timer user during postMessage sends.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.sendTimerId_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the peer transport protocol version could be as indicated.
|
||||
* @param {number} version The version to check for.
|
||||
* @return {boolean} Whether the peer transport protocol version is as
|
||||
* indicated, or null.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.couldPeerVersionBe_ =
|
||||
function(version) {
|
||||
return this.peerProtocolVersion_ == null ||
|
||||
this.peerProtocolVersion_ == version;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes this transport. Registers a listener for 'message'-events
|
||||
* on the document.
|
||||
* @param {Window} listenWindow The window to listen to events on.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.initialize_ = function(listenWindow) {
|
||||
var uid = goog.getUid(listenWindow);
|
||||
var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
|
||||
if (!goog.isNumber(value)) {
|
||||
value = 0;
|
||||
}
|
||||
if (value == 0) {
|
||||
// Listen for message-events. These are fired on window in FF3 and on
|
||||
// document in Opera.
|
||||
goog.events.listen(
|
||||
listenWindow.postMessage ? listenWindow : listenWindow.document,
|
||||
'message',
|
||||
goog.net.xpc.NativeMessagingTransport.messageReceived_,
|
||||
false,
|
||||
goog.net.xpc.NativeMessagingTransport);
|
||||
}
|
||||
goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Processes an incoming message-event.
|
||||
* @param {goog.events.BrowserEvent} msgEvt The message event.
|
||||
* @return {boolean} True if message was successfully delivered to a channel.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.messageReceived_ = function(msgEvt) {
|
||||
var data = msgEvt.getBrowserEvent().data;
|
||||
|
||||
if (!goog.isString(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var headDelim = data.indexOf('|');
|
||||
var serviceDelim = data.indexOf(':');
|
||||
|
||||
// make sure we got something reasonable
|
||||
if (headDelim == -1 || serviceDelim == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var channelName = data.substring(0, headDelim);
|
||||
var service = data.substring(headDelim + 1, serviceDelim);
|
||||
var payload = data.substring(serviceDelim + 1);
|
||||
|
||||
goog.log.fine(goog.net.xpc.logger,
|
||||
'messageReceived: channel=' + channelName +
|
||||
', service=' + service + ', payload=' + payload);
|
||||
|
||||
// Attempt to deliver message to the channel. Keep in mind that it may not
|
||||
// exist for several reasons, including but not limited to:
|
||||
// - a malformed message
|
||||
// - the channel simply has not been created
|
||||
// - channel was created in a different namespace
|
||||
// - message was sent to the wrong window
|
||||
// - channel has become stale (e.g. caching iframes and back clicks)
|
||||
var channel = goog.net.xpc.channels[channelName];
|
||||
if (channel) {
|
||||
channel.xpcDeliver(service, payload, msgEvt.getBrowserEvent().origin);
|
||||
return true;
|
||||
}
|
||||
|
||||
var transportMessageType =
|
||||
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload)[0];
|
||||
|
||||
// Check if there are any stale channel names that can be updated.
|
||||
for (var staleChannelName in goog.net.xpc.channels) {
|
||||
var staleChannel = goog.net.xpc.channels[staleChannelName];
|
||||
if (staleChannel.getRole() == goog.net.xpc.CrossPageChannelRole.INNER &&
|
||||
!staleChannel.isConnected() &&
|
||||
service == goog.net.xpc.TRANSPORT_SERVICE_ &&
|
||||
(transportMessageType == goog.net.xpc.SETUP ||
|
||||
transportMessageType == goog.net.xpc.SETUP_NTPV2)) {
|
||||
// Inner peer received SETUP message but channel names did not match.
|
||||
// Start using the channel name sent from outer peer. The channel name
|
||||
// of the inner peer can easily become out of date, as iframe's and their
|
||||
// JS state get cached in many browsers upon page reload or history
|
||||
// navigation (particularly Firefox 1.5+). We can trust the outer peer,
|
||||
// since we only accept postMessage messages from the same hostname that
|
||||
// originally setup the channel.
|
||||
goog.log.fine(goog.net.xpc.logger,
|
||||
'changing channel name to ' + channelName);
|
||||
staleChannel.name = channelName;
|
||||
// Remove old stale pointer to channel.
|
||||
delete goog.net.xpc.channels[staleChannelName];
|
||||
// Create fresh pointer to channel.
|
||||
goog.net.xpc.channels[channelName] = staleChannel;
|
||||
staleChannel.xpcDeliver(service, payload);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to find a channel to deliver this message to, so simply ignore it.
|
||||
goog.log.info(goog.net.xpc.logger, 'channel name mismatch; message ignored"');
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.transportServiceHandler =
|
||||
function(payload) {
|
||||
var transportParts =
|
||||
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload);
|
||||
var transportMessageType = transportParts[0];
|
||||
var peerEndpointId = transportParts[1];
|
||||
switch (transportMessageType) {
|
||||
case goog.net.xpc.SETUP_ACK_:
|
||||
this.setPeerProtocolVersion_(1);
|
||||
if (!this.setupAckReceived_.hasFired()) {
|
||||
this.setupAckReceived_.callback(true);
|
||||
}
|
||||
break;
|
||||
case goog.net.xpc.SETUP_ACK_NTPV2:
|
||||
if (this.protocolVersion_ == 2) {
|
||||
this.setPeerProtocolVersion_(2);
|
||||
if (!this.setupAckReceived_.hasFired()) {
|
||||
this.setupAckReceived_.callback(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case goog.net.xpc.SETUP:
|
||||
this.setPeerProtocolVersion_(1);
|
||||
this.sendSetupAckMessage_(1);
|
||||
break;
|
||||
case goog.net.xpc.SETUP_NTPV2:
|
||||
if (this.protocolVersion_ == 2) {
|
||||
var prevPeerProtocolVersion = this.peerProtocolVersion_;
|
||||
this.setPeerProtocolVersion_(2);
|
||||
this.sendSetupAckMessage_(2);
|
||||
if ((prevPeerProtocolVersion == 1 || this.peerEndpointId_ != null) &&
|
||||
this.peerEndpointId_ != peerEndpointId) {
|
||||
// Send a new SETUP message since the peer has been replaced.
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'Sending SETUP and changing peer ID to: ' + peerEndpointId);
|
||||
this.sendSetupMessage_();
|
||||
}
|
||||
this.peerEndpointId_ = peerEndpointId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SETUP transport service message of the correct protocol number for
|
||||
* our current situation.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.sendSetupMessage_ =
|
||||
function() {
|
||||
// 'real' (legacy) v1 transports don't know about there being v2 ones out
|
||||
// there, and we shouldn't either.
|
||||
goog.asserts.assert(!(this.protocolVersion_ == 1 &&
|
||||
this.peerProtocolVersion_ == 2));
|
||||
|
||||
if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2)) {
|
||||
var payload = goog.net.xpc.SETUP_NTPV2;
|
||||
payload += goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_;
|
||||
payload += this.endpointId_;
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);
|
||||
}
|
||||
|
||||
// For backward compatibility reasons, the V1 SETUP message can be sent by
|
||||
// both V1 and V2 transports. Once a V2 transport has 'heard' another V2
|
||||
// transport it starts ignoring V1 messages, so the V2 message must be sent
|
||||
// first.
|
||||
if (this.couldPeerVersionBe_(1)) {
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SETUP_ACK transport service message of the correct protocol number
|
||||
* for our current situation.
|
||||
* @param {number} protocolVersion The protocol version of the SETUP message
|
||||
* which gave rise to this ack message.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.sendSetupAckMessage_ =
|
||||
function(protocolVersion) {
|
||||
goog.asserts.assert(this.protocolVersion_ != 1 || protocolVersion != 2,
|
||||
'Shouldn\'t try to send a v2 setup ack in v1 mode.');
|
||||
if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2) &&
|
||||
protocolVersion == 2) {
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_NTPV2);
|
||||
} else if (this.couldPeerVersionBe_(1) && protocolVersion == 1) {
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.setupAckSent_.hasFired()) {
|
||||
this.setupAckSent_.callback(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to set the peer protocol number. Downgrades from 2 to 1 are not
|
||||
* permitted.
|
||||
* @param {number} version The new protocol number.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.setPeerProtocolVersion_ =
|
||||
function(version) {
|
||||
if (version > this.peerProtocolVersion_) {
|
||||
this.peerProtocolVersion_ = version;
|
||||
}
|
||||
if (this.peerProtocolVersion_ == 1) {
|
||||
if (!this.setupAckSent_.hasFired() && !this.oneSidedHandshake_) {
|
||||
this.setupAckSent_.callback(true);
|
||||
}
|
||||
this.peerEndpointId_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Connects this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.connect = function() {
|
||||
goog.net.xpc.NativeMessagingTransport.initialize_(this.getWindow());
|
||||
this.initialized_ = true;
|
||||
this.maybeAttemptToConnect_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Connects to other peer. In the case of the outer peer, the setup messages are
|
||||
* likely sent before the inner peer is ready to receive them. Therefore, this
|
||||
* function will continue trying to send the SETUP message until the inner peer
|
||||
* responds. In the case of the inner peer, it will occasionally have its
|
||||
* channel name fall out of sync with the outer peer, particularly during
|
||||
* soft-reloads and history navigations.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.maybeAttemptToConnect_ =
|
||||
function() {
|
||||
// In a one-sided handshake, the outer frame does not send a SETUP message,
|
||||
// but the inner frame does.
|
||||
var outerFrame = this.channel_.getRole() ==
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER;
|
||||
if ((this.oneSidedHandshake_ && outerFrame) ||
|
||||
this.channel_.isConnected() ||
|
||||
this.isDisposed()) {
|
||||
this.maybeAttemptToConnectTimer_.stop();
|
||||
return;
|
||||
}
|
||||
this.maybeAttemptToConnectTimer_.start();
|
||||
this.sendSetupMessage_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
* @param {string} service The name off the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.send = function(service,
|
||||
payload) {
|
||||
var win = this.channel_.getPeerWindowObject();
|
||||
if (!win) {
|
||||
goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
this.send = function(service, payload) {
|
||||
// In IE8 (and perhaps elsewhere), it seems like postMessage is sometimes
|
||||
// implemented as a synchronous call. That is, calling it synchronously
|
||||
// calls whatever listeners it has, and control is not returned to the
|
||||
// calling thread until those listeners are run. This produces different
|
||||
// ordering to all other browsers, and breaks this protocol. This timer
|
||||
// callback is introduced to produce standard behavior across all browsers.
|
||||
var transport = this;
|
||||
var channelName = this.channel_.name;
|
||||
var sendFunctor = function() {
|
||||
transport.sendTimerId_ = 0;
|
||||
|
||||
try {
|
||||
// postMessage is a method of the window object, except in some
|
||||
// versions of Opera, where it is a method of the document object. It
|
||||
// also seems that the appearance of postMessage on the peer window
|
||||
// object can sometimes be delayed.
|
||||
var obj = win.postMessage ? win : win.document;
|
||||
if (!obj.postMessage) {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'Peer window had no postMessage function.');
|
||||
return;
|
||||
}
|
||||
|
||||
obj.postMessage(channelName + '|' + service + ':' + payload,
|
||||
transport.peerHostname_);
|
||||
goog.log.fine(goog.net.xpc.logger, 'send(): service=' + service +
|
||||
' payload=' + payload + ' to hostname=' + transport.peerHostname_);
|
||||
} catch (error) {
|
||||
// There is some evidence (not totally convincing) that postMessage can
|
||||
// be missing or throw errors during a narrow timing window during
|
||||
// startup. This protects against that.
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'Error performing postMessage, ignoring.', error);
|
||||
}
|
||||
};
|
||||
this.sendTimerId_ = goog.Timer.callOnce(sendFunctor, 0);
|
||||
};
|
||||
this.send(service, payload);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Notify the channel that this transport is connected. If either transport is
|
||||
* protocol v1, a short delay is required to paper over timing vulnerabilities
|
||||
* in that protocol version.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.notifyConnected_ =
|
||||
function() {
|
||||
var delay = (this.protocolVersion_ == 1 || this.peerProtocolVersion_ == 1) ?
|
||||
goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ : undefined;
|
||||
this.channel_.notifyConnected(delay);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.disposeInternal = function() {
|
||||
if (this.initialized_) {
|
||||
var listenWindow = this.getWindow();
|
||||
var uid = goog.getUid(listenWindow);
|
||||
var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
|
||||
goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value - 1;
|
||||
if (value == 1) {
|
||||
goog.events.unlisten(
|
||||
listenWindow.postMessage ? listenWindow : listenWindow.document,
|
||||
'message',
|
||||
goog.net.xpc.NativeMessagingTransport.messageReceived_,
|
||||
false,
|
||||
goog.net.xpc.NativeMessagingTransport);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sendTimerId_) {
|
||||
goog.Timer.clear(this.sendTimerId_);
|
||||
this.sendTimerId_ = 0;
|
||||
}
|
||||
|
||||
goog.dispose(this.eventHandler_);
|
||||
delete this.eventHandler_;
|
||||
|
||||
goog.dispose(this.maybeAttemptToConnectTimer_);
|
||||
delete this.maybeAttemptToConnectTimer_;
|
||||
|
||||
this.setupAckReceived_.cancel();
|
||||
delete this.setupAckReceived_;
|
||||
this.setupAckSent_.cancel();
|
||||
delete this.setupAckSent_;
|
||||
this.connected_.cancel();
|
||||
delete this.connected_;
|
||||
|
||||
// Cleaning up this.send as it is an instance method, created in
|
||||
// goog.net.xpc.NativeMessagingTransport.prototype.send and has a closure over
|
||||
// this.channel_.peerWindowObject_.
|
||||
delete this.send;
|
||||
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse a transport service payload message. For v1, it is simply expected to
|
||||
* be 'SETUP' or 'SETUP_ACK'. For v2, an example setup message is
|
||||
* 'SETUP_NTPV2,abc123', where the second part is the endpoint id. The v2 setup
|
||||
* ack message is simply 'SETUP_ACK_NTPV2'.
|
||||
* @param {string} payload The payload.
|
||||
* @return {!Array.<?string>} An array with the message type as the first member
|
||||
* and the endpoint id as the second, if one was sent, or null otherwise.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_ =
|
||||
function(payload) {
|
||||
var transportParts = /** @type {!Array.<?string>} */ (payload.split(
|
||||
goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_));
|
||||
transportParts[1] = transportParts[1] || null;
|
||||
return transportParts;
|
||||
};
|
||||
@@ -0,0 +1,479 @@
|
||||
// Copyright 2008 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 Contains the NIX (Native IE XDC) method transport for
|
||||
* cross-domain communication. It exploits the fact that Internet Explorer
|
||||
* allows a window that is the parent of an iframe to set said iframe window's
|
||||
* opener property to an object. This object can be a function that in turn
|
||||
* can be used to send a message despite same-origin constraints. Note that
|
||||
* this function, if a pure JavaScript object, opens up the possibilitiy of
|
||||
* gaining a hold of the context of the other window and in turn, attacking
|
||||
* it. This implementation therefore wraps the JavaScript objects used inside
|
||||
* a VBScript class. Since VBScript objects are passed in JavaScript as a COM
|
||||
* wrapper (like DOM objects), they are thus opaque to JavaScript
|
||||
* (except for the interface they expose). This therefore provides a safe
|
||||
* method of transport.
|
||||
*
|
||||
*
|
||||
* Initially based on FrameElementTransport which shares some similarities
|
||||
* to this method.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.NixTransport');
|
||||
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.reflect');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* NIX method transport.
|
||||
*
|
||||
* NOTE(user): NIX method tested in all IE versions starting from 6.0.
|
||||
*
|
||||
* @param {goog.net.xpc.CrossPageChannel} channel The channel this transport
|
||||
* belongs to.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
|
||||
* the correct window.
|
||||
* @constructor
|
||||
* @extends {goog.net.xpc.Transport}
|
||||
*/
|
||||
goog.net.xpc.NixTransport = function(channel, opt_domHelper) {
|
||||
goog.base(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* The authorization token, if any, used by this transport.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.authToken_ = channel[goog.net.xpc.CfgFields.AUTH_TOKEN] || '';
|
||||
|
||||
/**
|
||||
* The authorization token, if any, that must be sent by the other party
|
||||
* for setup to occur.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.remoteAuthToken_ =
|
||||
channel[goog.net.xpc.CfgFields.REMOTE_AUTH_TOKEN] || '';
|
||||
|
||||
// Conduct the setup work for NIX in general, if need be.
|
||||
goog.net.xpc.NixTransport.conductGlobalSetup_(this.getWindow());
|
||||
|
||||
// Setup aliases so that VBScript can call these methods
|
||||
// on the transport class, even if they are renamed during
|
||||
// compression.
|
||||
this[goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE] = this.handleMessage_;
|
||||
this[goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL] = this.createChannel_;
|
||||
};
|
||||
goog.inherits(goog.net.xpc.NixTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
// Consts for NIX. VBScript doesn't allow items to start with _ for some
|
||||
// reason, so we need to make these names quite unique, as they will go into
|
||||
// the global namespace.
|
||||
|
||||
|
||||
/**
|
||||
* Global name of the Wrapper VBScript class.
|
||||
* Note that this class will be stored in the *global*
|
||||
* namespace (i.e. window in browsers).
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_WRAPPER = 'GCXPC____NIXVBS_wrapper';
|
||||
|
||||
|
||||
/**
|
||||
* Global name of the GetWrapper VBScript function. This
|
||||
* constant is used by JavaScript to call this function.
|
||||
* Note that this function will be stored in the *global*
|
||||
* namespace (i.e. window in browsers).
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_GET_WRAPPER = 'GCXPC____NIXVBS_get_wrapper';
|
||||
|
||||
|
||||
/**
|
||||
* The name of the handle message method used by the wrapper class
|
||||
* when calling the transport.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE = 'GCXPC____NIXJS_handle_message';
|
||||
|
||||
|
||||
/**
|
||||
* The name of the create channel method used by the wrapper class
|
||||
* when calling the transport.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL = 'GCXPC____NIXJS_create_channel';
|
||||
|
||||
|
||||
/**
|
||||
* A "unique" identifier that is stored in the wrapper
|
||||
* class so that the wrapper can be distinguished from
|
||||
* other objects easily.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_ID_FIELD = 'GCXPC____NIXVBS_container';
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the installed version of IE supports accessing window.opener
|
||||
* after it has been set to a non-Window/null value. NIX relies on this being
|
||||
* possible.
|
||||
* @return {boolean} Whether window.opener behavior is compatible with NIX.
|
||||
*/
|
||||
goog.net.xpc.NixTransport.isNixSupported = function() {
|
||||
var isSupported = false;
|
||||
try {
|
||||
var oldOpener = window.opener;
|
||||
// The compiler complains (as it should!) if we set window.opener to
|
||||
// something other than a window or null.
|
||||
window.opener = /** @type {Window} */ ({});
|
||||
isSupported = goog.reflect.canAccessProperty(window, 'opener');
|
||||
window.opener = oldOpener;
|
||||
} catch (e) { }
|
||||
return isSupported;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Conducts the global setup work for the NIX transport method.
|
||||
* This function creates and then injects into the page the
|
||||
* VBScript code necessary to create the NIX wrapper class.
|
||||
* Note that this method can be called multiple times, as
|
||||
* it internally checks whether the work is necessary before
|
||||
* proceeding.
|
||||
* @param {Window} listenWindow The window containing the affected page.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.conductGlobalSetup_ = function(listenWindow) {
|
||||
if (listenWindow['nix_setup_complete']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the VBScript code needed.
|
||||
var vbscript =
|
||||
// We create a class to act as a wrapper for
|
||||
// a Javascript call, to prevent a break in of
|
||||
// the context.
|
||||
'Class ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n ' +
|
||||
|
||||
// An internal member for keeping track of the
|
||||
// transport for which this wrapper exists.
|
||||
'Private m_Transport\n' +
|
||||
|
||||
// An internal member for keeping track of the
|
||||
// auth token associated with the context that
|
||||
// created this wrapper. Used for validation
|
||||
// purposes.
|
||||
'Private m_Auth\n' +
|
||||
|
||||
// Method for internally setting the value
|
||||
// of the m_Transport property. We have the
|
||||
// isEmpty check to prevent the transport
|
||||
// from being overridden with an illicit
|
||||
// object by a malicious party.
|
||||
'Public Sub SetTransport(transport)\n' +
|
||||
'If isEmpty(m_Transport) Then\n' +
|
||||
'Set m_Transport = transport\n' +
|
||||
'End If\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// Method for internally setting the value
|
||||
// of the m_Auth property. We have the
|
||||
// isEmpty check to prevent the transport
|
||||
// from being overridden with an illicit
|
||||
// object by a malicious party.
|
||||
'Public Sub SetAuth(auth)\n' +
|
||||
'If isEmpty(m_Auth) Then\n' +
|
||||
'm_Auth = auth\n' +
|
||||
'End If\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// Returns the auth token to the gadget, so it can
|
||||
// confirm a match before initiating the connection
|
||||
'Public Function GetAuthToken()\n ' +
|
||||
'GetAuthToken = m_Auth\n' +
|
||||
'End Function\n' +
|
||||
|
||||
// A wrapper method which causes a
|
||||
// message to be sent to the other context.
|
||||
'Public Sub SendMessage(service, payload)\n ' +
|
||||
'Call m_Transport.' +
|
||||
goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE + '(service, payload)\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// Method for setting up the inner->outer
|
||||
// channel.
|
||||
'Public Sub CreateChannel(channel)\n ' +
|
||||
'Call m_Transport.' +
|
||||
goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL + '(channel)\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// An empty field with a unique identifier to
|
||||
// prevent the code from confusing this wrapper
|
||||
// with a run-of-the-mill value found in window.opener.
|
||||
'Public Sub ' + goog.net.xpc.NixTransport.NIX_ID_FIELD + '()\n ' +
|
||||
'End Sub\n' +
|
||||
'End Class\n ' +
|
||||
|
||||
// Function to get a reference to the wrapper.
|
||||
'Function ' +
|
||||
goog.net.xpc.NixTransport.NIX_GET_WRAPPER + '(transport, auth)\n' +
|
||||
'Dim wrap\n' +
|
||||
'Set wrap = New ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n' +
|
||||
'wrap.SetTransport transport\n' +
|
||||
'wrap.SetAuth auth\n' +
|
||||
'Set ' + goog.net.xpc.NixTransport.NIX_GET_WRAPPER + ' = wrap\n' +
|
||||
'End Function';
|
||||
|
||||
try {
|
||||
listenWindow.execScript(vbscript, 'vbscript');
|
||||
listenWindow['nix_setup_complete'] = true;
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting global setup: ' + e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.NIX;
|
||||
|
||||
|
||||
/**
|
||||
* Keeps track of whether the local setup has completed (i.e.
|
||||
* the initial work towards setting the channel up has been
|
||||
* completed for this end).
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.localSetupCompleted_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The NIX channel used to talk to the other page. This
|
||||
* object is in fact a reference to a VBScript class
|
||||
* (see above) and as such, is in fact a COM wrapper.
|
||||
* When using this object, make sure to not access methods
|
||||
* without calling them, otherwise a COM error will be thrown.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.nixChannel_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Connect this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.connect = function() {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) {
|
||||
this.attemptOuterSetup_();
|
||||
} else {
|
||||
this.attemptInnerSetup_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to setup the channel from the perspective
|
||||
* of the outer (read: container) page. This method
|
||||
* will attempt to create a NIX wrapper for this transport
|
||||
* and place it into the "opener" property of the inner
|
||||
* page's window object. If it fails, it will continue
|
||||
* to loop until it does so.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.attemptOuterSetup_ = function() {
|
||||
if (this.localSetupCompleted_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get shortcut to iframe-element that contains the inner
|
||||
// page.
|
||||
var innerFrame = this.channel_.getIframeElement();
|
||||
|
||||
try {
|
||||
// Attempt to place the NIX wrapper object into the inner
|
||||
// frame's opener property.
|
||||
var theWindow = this.getWindow();
|
||||
var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER];
|
||||
innerFrame.contentWindow.opener = getWrapper(this, this.authToken_);
|
||||
this.localSetupCompleted_ = true;
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting setup: ' + e);
|
||||
}
|
||||
|
||||
// If the retry is necessary, reattempt this setup.
|
||||
if (!this.localSetupCompleted_) {
|
||||
this.getWindow().setTimeout(goog.bind(this.attemptOuterSetup_, this), 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to setup the channel from the perspective
|
||||
* of the inner (read: iframe) page. This method
|
||||
* will attempt to *read* the opener object from the
|
||||
* page's opener property. If it succeeds, this object
|
||||
* is saved into nixChannel_ and the channel is confirmed
|
||||
* with the container by calling CreateChannel with an instance
|
||||
* of a wrapper for *this* page. Note that if this method
|
||||
* fails, it will continue to loop until it succeeds.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.attemptInnerSetup_ = function() {
|
||||
if (this.localSetupCompleted_) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var opener = this.getWindow().opener;
|
||||
|
||||
// Ensure that the object contained inside the opener
|
||||
// property is in fact a NIX wrapper.
|
||||
if (opener && goog.net.xpc.NixTransport.NIX_ID_FIELD in opener) {
|
||||
this.nixChannel_ = opener;
|
||||
|
||||
// Ensure that the NIX channel given to use is valid.
|
||||
var remoteAuthToken = this.nixChannel_['GetAuthToken']();
|
||||
|
||||
if (remoteAuthToken != this.remoteAuthToken_) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'Invalid auth token from other party');
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the construction of the channel by sending our own
|
||||
// wrapper to the container via the channel they gave us.
|
||||
var theWindow = this.getWindow();
|
||||
var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER];
|
||||
this.nixChannel_['CreateChannel'](getWrapper(this, this.authToken_));
|
||||
|
||||
this.localSetupCompleted_ = true;
|
||||
|
||||
// Notify channel that the transport is ready.
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting setup: ' + e);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the retry is necessary, reattempt this setup.
|
||||
if (!this.localSetupCompleted_) {
|
||||
this.getWindow().setTimeout(goog.bind(this.attemptInnerSetup_, this), 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal method called by the inner page, via the
|
||||
* NIX wrapper, to complete the setup of the channel.
|
||||
*
|
||||
* @param {Object} channel The NIX wrapper of the
|
||||
* inner page.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.createChannel_ = function(channel) {
|
||||
// Verify that the channel is in fact a NIX wrapper.
|
||||
if (typeof channel != 'unknown' ||
|
||||
!(goog.net.xpc.NixTransport.NIX_ID_FIELD in channel)) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'Invalid NIX channel given to createChannel_');
|
||||
}
|
||||
|
||||
this.nixChannel_ = channel;
|
||||
|
||||
// Ensure that the NIX channel given to use is valid.
|
||||
var remoteAuthToken = this.nixChannel_['GetAuthToken']();
|
||||
|
||||
if (remoteAuthToken != this.remoteAuthToken_) {
|
||||
goog.log.error(goog.net.xpc.logger, 'Invalid auth token from other party');
|
||||
return;
|
||||
}
|
||||
|
||||
// Indicate to the CrossPageChannel that the channel is setup
|
||||
// and ready to use.
|
||||
this.channel_.notifyConnected();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal method called by the other page, via the NIX wrapper,
|
||||
* to deliver a message.
|
||||
* @param {string} serviceName The name of the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message to process.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.handleMessage_ =
|
||||
function(serviceName, payload) {
|
||||
/** @this {goog.net.xpc.NixTransport} */
|
||||
var deliveryHandler = function() {
|
||||
this.channel_.xpcDeliver(serviceName, payload);
|
||||
};
|
||||
this.getWindow().setTimeout(goog.bind(deliveryHandler, this), 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
* @param {string} service The name of the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.send = function(service, payload) {
|
||||
// Verify that the NIX channel we have is valid.
|
||||
if (typeof(this.nixChannel_) !== 'unknown') {
|
||||
goog.log.error(goog.net.xpc.logger, 'NIX channel not connected');
|
||||
}
|
||||
|
||||
// Send the message via the NIX wrapper object.
|
||||
this.nixChannel_['SendMessage'](service, payload);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.NixTransport.prototype.disposeInternal = function() {
|
||||
goog.base(this, 'disposeInternal');
|
||||
this.nixChannel_ = null;
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2007 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 Standalone script to be included in the relay-document
|
||||
* used by goog.net.xpc.IframeRelayTransport. This script will decode the
|
||||
* fragment identifier, determine the target window object and deliver
|
||||
* the data to it.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.relay');
|
||||
|
||||
(function() {
|
||||
// Decode the fragement identifier.
|
||||
// location.href is expected to be structured as follows:
|
||||
// <url>#<channel_name>[,<iframe_id>]|<data>
|
||||
|
||||
// Get the fragment identifier.
|
||||
var raw = window.location.hash;
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
if (raw.charAt(0) == '#') {
|
||||
raw = raw.substring(1);
|
||||
}
|
||||
var pos = raw.indexOf('|');
|
||||
var head = raw.substring(0, pos).split(',');
|
||||
var channelName = head[0];
|
||||
var iframeId = head.length == 2 ? head[1] : null;
|
||||
var frame = raw.substring(pos + 1);
|
||||
|
||||
// Find the window object of the peer.
|
||||
//
|
||||
// The general structure of the frames looks like this:
|
||||
// - peer1
|
||||
// - relay2
|
||||
// - peer2
|
||||
// - relay1
|
||||
//
|
||||
// We are either relay1 or relay2.
|
||||
|
||||
var win;
|
||||
if (iframeId) {
|
||||
// We are relay2 and need to deliver the data to peer2.
|
||||
win = window.parent.frames[iframeId];
|
||||
} else {
|
||||
// We are relay1 and need to deliver the data to peer1.
|
||||
win = window.parent.parent;
|
||||
}
|
||||
|
||||
// Deliver the data.
|
||||
try {
|
||||
win['xpcRelay'](channelName, frame);
|
||||
} catch (e) {
|
||||
// Nothing useful can be done here.
|
||||
// It would be great to inform the sender the delivery of this message
|
||||
// failed, but this is not possible because we are already in the receiver's
|
||||
// domain at this point.
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2007 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 Contains the base class for transports.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.Transport');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.net.xpc');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The base class for transports.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
|
||||
* finding the window objects.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable};
|
||||
*/
|
||||
goog.net.xpc.Transport = function(opt_domHelper) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The dom helper to use for finding the window objects to reference.
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
|
||||
};
|
||||
goog.inherits(goog.net.xpc.Transport, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.transportType = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The transport type identifier.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.getType = function() {
|
||||
return this.transportType;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the window associated with this transport instance.
|
||||
* @return {Window} The window to use.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.getWindow = function() {
|
||||
return this.domHelper_.getWindow();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the transport name.
|
||||
* @return {string} the transport name.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.getName = function() {
|
||||
return goog.net.xpc.TransportNames[this.transportType] || '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages (internal signalling).
|
||||
* @param {string} payload The message content.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.transportServiceHandler = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Connects this transport.
|
||||
* The transport implementation is expected to call
|
||||
* CrossPageChannel.prototype.notifyConnected when the channel is ready
|
||||
* to be used.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.connect = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
* @param {string} service The name off the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.send = goog.abstractMethod;
|
||||
@@ -0,0 +1,293 @@
|
||||
// Copyright 2007 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 the namesspace for client-side communication
|
||||
* between pages originating from different domains (it works also
|
||||
* with pages from the same domain, but doing that is kinda
|
||||
* pointless).
|
||||
*
|
||||
* The only publicly visible class is goog.net.xpc.CrossPageChannel.
|
||||
*
|
||||
* Note: The preferred name for the main class would have been
|
||||
* CrossDomainChannel. But as there already is a class named like
|
||||
* that (which serves a different purpose) in the maps codebase,
|
||||
* CrossPageChannel was chosen to avoid confusion.
|
||||
*
|
||||
* CrossPageChannel abstracts the underlying transport mechanism to
|
||||
* provide a common interface in all browsers.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO(user)
|
||||
- resolve fastback issues in Safari (IframeRelayTransport)
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Namespace for CrossPageChannel
|
||||
*/
|
||||
goog.provide('goog.net.xpc');
|
||||
goog.provide('goog.net.xpc.CfgFields');
|
||||
goog.provide('goog.net.xpc.ChannelStates');
|
||||
goog.provide('goog.net.xpc.TransportNames');
|
||||
goog.provide('goog.net.xpc.TransportTypes');
|
||||
goog.provide('goog.net.xpc.UriCfgFields');
|
||||
|
||||
goog.require('goog.log');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enum used to identify transport types.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.xpc.TransportTypes = {
|
||||
NATIVE_MESSAGING: 1,
|
||||
FRAME_ELEMENT_METHOD: 2,
|
||||
IFRAME_RELAY: 3,
|
||||
IFRAME_POLLING: 4,
|
||||
FLASH: 5,
|
||||
NIX: 6
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enum containing transport names. These need to correspond to the
|
||||
* transport class names for createTransport_() to work.
|
||||
* @type {Object}
|
||||
*/
|
||||
goog.net.xpc.TransportNames = {
|
||||
'1': 'NativeMessagingTransport',
|
||||
'2': 'FrameElementMethodTransport',
|
||||
'3': 'IframeRelayTransport',
|
||||
'4': 'IframePollingTransport',
|
||||
'5': 'FlashTransport',
|
||||
'6': 'NixTransport'
|
||||
};
|
||||
|
||||
|
||||
// TODO(user): Add auth token support to other methods.
|
||||
|
||||
|
||||
/**
|
||||
* Field names used on configuration object.
|
||||
* @type {Object}
|
||||
*/
|
||||
goog.net.xpc.CfgFields = {
|
||||
/**
|
||||
* Channel name identifier.
|
||||
* Both peers have to be initialized with
|
||||
* the same channel name. If not present, a channel name is
|
||||
* generated (which then has to transferred to the peer somehow).
|
||||
*/
|
||||
CHANNEL_NAME: 'cn',
|
||||
/**
|
||||
* Authorization token. If set, NIX will use this authorization token
|
||||
* to validate the setup.
|
||||
*/
|
||||
AUTH_TOKEN: 'at',
|
||||
/**
|
||||
* Remote party's authorization token. If set, NIX will validate this
|
||||
* authorization token against that sent by the other party.
|
||||
*/
|
||||
REMOTE_AUTH_TOKEN: 'rat',
|
||||
/**
|
||||
* The URI of the peer page.
|
||||
*/
|
||||
PEER_URI: 'pu',
|
||||
/**
|
||||
* Ifame-ID identifier.
|
||||
* The id of the iframe element the peer-document lives in.
|
||||
*/
|
||||
IFRAME_ID: 'ifrid',
|
||||
/**
|
||||
* Transport type identifier.
|
||||
* The transport type to use. Possible values are entries from
|
||||
* goog.net.xpc.TransportTypes. If not present, the transport is
|
||||
* determined automatically based on the useragent's capabilities.
|
||||
*/
|
||||
TRANSPORT: 'tp',
|
||||
/**
|
||||
* Local relay URI identifier (IframeRelayTransport-specific).
|
||||
* The URI (can't contain a fragment identifier) used by the peer to
|
||||
* relay data through.
|
||||
*/
|
||||
LOCAL_RELAY_URI: 'lru',
|
||||
/**
|
||||
* Peer relay URI identifier (IframeRelayTransport-specific).
|
||||
* The URI (can't contain a fragment identifier) used to relay data
|
||||
* to the peer.
|
||||
*/
|
||||
PEER_RELAY_URI: 'pru',
|
||||
/**
|
||||
* Local poll URI identifier (IframePollingTransport-specific).
|
||||
* The URI (can't contain a fragment identifier)which is polled
|
||||
* to receive data from the peer.
|
||||
*/
|
||||
LOCAL_POLL_URI: 'lpu',
|
||||
/**
|
||||
* Local poll URI identifier (IframePollingTransport-specific).
|
||||
* The URI (can't contain a fragment identifier) used to send data
|
||||
* to the peer.
|
||||
*/
|
||||
PEER_POLL_URI: 'ppu',
|
||||
/**
|
||||
* The hostname of the peer window, including protocol, domain, and port
|
||||
* (if specified). Used for security sensitive applications that make
|
||||
* use of NativeMessagingTransport (i.e. most applications).
|
||||
*/
|
||||
PEER_HOSTNAME: 'ph',
|
||||
/**
|
||||
* Usually both frames using a connection initially send a SETUP message to
|
||||
* each other, and each responds with a SETUP_ACK. A frame marks itself
|
||||
* connected when it receives that SETUP_ACK. If this parameter is true
|
||||
* however, the channel it is passed to will not send a SETUP, but rather will
|
||||
* wait for one from its peer and mark itself connected when that arrives.
|
||||
* Peer iframes created using such a channel will send SETUP however, and will
|
||||
* wait for SETUP_ACK before marking themselves connected. The goal is to
|
||||
* cope with a situation where the availability of the URL for the peer frame
|
||||
* cannot be relied on, eg when the application is offline. Without this
|
||||
* setting, the primary frame will attempt to send its SETUP message every
|
||||
* 100ms, forever. This floods the javascript console with uncatchable
|
||||
* security warnings, and fruitlessly burns CPU. There is one scenario this
|
||||
* mode will not support, and that is reconnection by the outer frame, ie the
|
||||
* creation of a new channel object to connect to a peer iframe which was
|
||||
* already communicating with a previous channel object of the same name. If
|
||||
* that behavior is needed, this mode should not be used. Reconnection by
|
||||
* inner frames is supported in this mode however.
|
||||
*/
|
||||
ONE_SIDED_HANDSHAKE: 'osh',
|
||||
/**
|
||||
* The frame role (inner or outer). Used to explicitly indicate the role for
|
||||
* each peer whenever the role cannot be reliably determined (e.g. the two
|
||||
* peer windows are not parent/child frames). If unspecified, the role will
|
||||
* be dynamically determined, assuming a parent/child frame setup.
|
||||
*/
|
||||
ROLE: 'role',
|
||||
/**
|
||||
* Which version of the native transport startup protocol should be used, the
|
||||
* default being '2'. Version 1 had various timing vulnerabilities, which
|
||||
* had to be compensated for by introducing delays, and is deprecated. V1
|
||||
* and V2 are broadly compatible, although the more robust timing and lack
|
||||
* of delays is not gained unless both sides are using V2. The only
|
||||
* unsupported case of cross-protocol interoperation is where a connection
|
||||
* starts out with V2 at both ends, and one of the ends reconnects as a V1.
|
||||
* All other initial startup and reconnection scenarios are supported.
|
||||
*/
|
||||
NATIVE_TRANSPORT_PROTOCOL_VERSION: 'nativeProtocolVersion'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Config properties that need to be URL sanitized.
|
||||
* @type {Array}.<string>
|
||||
*/
|
||||
goog.net.xpc.UriCfgFields = [
|
||||
goog.net.xpc.CfgFields.PEER_URI,
|
||||
goog.net.xpc.CfgFields.LOCAL_RELAY_URI,
|
||||
goog.net.xpc.CfgFields.PEER_RELAY_URI,
|
||||
goog.net.xpc.CfgFields.LOCAL_POLL_URI,
|
||||
goog.net.xpc.CfgFields.PEER_POLL_URI
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.xpc.ChannelStates = {
|
||||
NOT_CONNECTED: 1,
|
||||
CONNECTED: 2,
|
||||
CLOSED: 3
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The name of the transport service (used for internal signalling).
|
||||
* @type {string}
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.net.xpc.TRANSPORT_SERVICE_ = 'tp';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.SETUP = 'SETUP';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup for native transport protocol v2.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.SETUP_NTPV2 = 'SETUP_NTPV2';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup acknowledgement.
|
||||
* @type {string}
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.net.xpc.SETUP_ACK_ = 'SETUP_ACK';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup acknowledgement.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.SETUP_ACK_NTPV2 = 'SETUP_ACK_NTPV2';
|
||||
|
||||
|
||||
/**
|
||||
* Object holding active channels.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @type {Object.<string, goog.net.xpc.CrossPageChannel>}
|
||||
*/
|
||||
goog.net.xpc.channels = {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a random string.
|
||||
* @param {number} length How many characters the string shall contain.
|
||||
* @param {string=} opt_characters The characters used.
|
||||
* @return {string} The random string.
|
||||
*/
|
||||
goog.net.xpc.getRandomString = function(length, opt_characters) {
|
||||
var chars = opt_characters || goog.net.xpc.randomStringCharacters_;
|
||||
var charsLength = chars.length;
|
||||
var s = '';
|
||||
while (length-- > 0) {
|
||||
s += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The default characters used for random string generation.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.randomStringCharacters_ =
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
* @type {goog.log.Logger}
|
||||
*/
|
||||
goog.net.xpc.logger = goog.log.getLogger('goog.net.xpc');
|
||||
Reference in New Issue
Block a user