1082 lines
33 KiB
JavaScript
1082 lines
33 KiB
JavaScript
// Copyright 2009 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.
|
|
|
|
goog.provide('goog.net.xpc.CrossPageChannelTest');
|
|
goog.setTestOnly('goog.net.xpc.CrossPageChannelTest');
|
|
|
|
goog.require('goog.Disposable');
|
|
goog.require('goog.Uri');
|
|
goog.require('goog.async.Deferred');
|
|
goog.require('goog.dom');
|
|
goog.require('goog.labs.userAgent.browser');
|
|
goog.require('goog.log');
|
|
goog.require('goog.log.Level');
|
|
goog.require('goog.net.xpc');
|
|
goog.require('goog.net.xpc.CfgFields');
|
|
goog.require('goog.net.xpc.CrossPageChannel');
|
|
goog.require('goog.net.xpc.CrossPageChannelRole');
|
|
goog.require('goog.net.xpc.TransportTypes');
|
|
goog.require('goog.object');
|
|
goog.require('goog.testing.AsyncTestCase');
|
|
goog.require('goog.testing.PropertyReplacer');
|
|
goog.require('goog.testing.jsunit');
|
|
|
|
// Set this to false when working on this test. It needs to be true for
|
|
// automated testing, as some browsers (eg IE8) choke on the large numbers of
|
|
// iframes this test would otherwise leave active.
|
|
var CLEAN_UP_IFRAMES = true;
|
|
|
|
var IFRAME_LOAD_WAIT_MS = 1000;
|
|
var stubs = new goog.testing.PropertyReplacer();
|
|
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(
|
|
document.title);
|
|
var uniqueId = 0;
|
|
var driver;
|
|
var canAccessSameDomainIframe = true;
|
|
var accessCheckIframes = [];
|
|
|
|
function setUpPage() {
|
|
// This test is insanely slow on IE8 for some reason.
|
|
asyncTestCase.stepTimeout = 20 * 1000;
|
|
|
|
// Show debug log
|
|
var debugDiv = goog.dom.getElement('debugDiv');
|
|
var logger = goog.log.getLogger('goog.net.xpc');
|
|
logger.setLevel(goog.log.Level.ALL);
|
|
goog.log.addHandler(logger, function(logRecord) {
|
|
var msgElm = goog.dom.createDom('div');
|
|
msgElm.innerHTML = logRecord.getMessage();
|
|
goog.dom.appendChild(debugDiv, msgElm);
|
|
});
|
|
asyncTestCase.waitForAsync('Checking if we can access same domain iframes');
|
|
checkSameDomainIframeAccess();
|
|
}
|
|
|
|
|
|
function setUp() {
|
|
driver = new Driver();
|
|
}
|
|
|
|
|
|
function tearDown() {
|
|
stubs.reset();
|
|
driver.dispose();
|
|
}
|
|
|
|
|
|
function checkSameDomainIframeAccess() {
|
|
accessCheckIframes.push(
|
|
create1x1Iframe('nonexistant', 'testdata/i_am_non_existant.html'));
|
|
window.setTimeout(function() {
|
|
accessCheckIframes.push(
|
|
create1x1Iframe('existant', 'testdata/access_checker.html'));
|
|
}, 10);
|
|
}
|
|
|
|
|
|
function create1x1Iframe(iframeId, src) {
|
|
var iframeAccessChecker = goog.dom.createElement('IFRAME');
|
|
iframeAccessChecker.id = iframeAccessChecker.name = iframeId;
|
|
iframeAccessChecker.style.width = iframeAccessChecker.style.height = '1px';
|
|
iframeAccessChecker.src = src;
|
|
document.body.insertBefore(iframeAccessChecker, document.body.firstChild);
|
|
return iframeAccessChecker;
|
|
}
|
|
|
|
|
|
function sameDomainIframeAccessComplete(canAccess) {
|
|
canAccessSameDomainIframe = canAccess;
|
|
for (var i = 0; i < accessCheckIframes.length; i++) {
|
|
document.body.removeChild(accessCheckIframes[i]);
|
|
}
|
|
asyncTestCase.continueTesting();
|
|
}
|
|
|
|
|
|
function testCreateIframeSpecifyId() {
|
|
driver.createPeerIframe('new_iframe');
|
|
|
|
asyncTestCase.waitForAsync('iframe load');
|
|
window.setTimeout(function() {
|
|
driver.checkPeerIframe();
|
|
asyncTestCase.continueTesting();
|
|
}, IFRAME_LOAD_WAIT_MS);
|
|
}
|
|
|
|
|
|
function testCreateIframeRandomId() {
|
|
driver.createPeerIframe();
|
|
|
|
asyncTestCase.waitForAsync('iframe load');
|
|
window.setTimeout(function() {
|
|
driver.checkPeerIframe();
|
|
asyncTestCase.continueTesting();
|
|
}, IFRAME_LOAD_WAIT_MS);
|
|
}
|
|
|
|
|
|
function testGetRole() {
|
|
var cfg = {};
|
|
cfg[goog.net.xpc.CfgFields.ROLE] = goog.net.xpc.CrossPageChannelRole.OUTER;
|
|
var channel = new goog.net.xpc.CrossPageChannel(cfg);
|
|
// If the configured role is ignored, this will cause the dynamicly
|
|
// determined role to become INNER.
|
|
channel.peerWindowObject_ = window.parent;
|
|
assertEquals('Channel should use role from the config.',
|
|
goog.net.xpc.CrossPageChannelRole.OUTER, channel.getRole());
|
|
channel.dispose();
|
|
}
|
|
|
|
|
|
// The following batch of tests:
|
|
// * Establishes a peer iframe
|
|
// * Connects an XPC channel between the frames
|
|
// * From the connection callback in each frame, sends an 'echo' request, and
|
|
// expects a 'response' response.
|
|
// * Reconnects the inner frame, sends an 'echo', expects a 'response'.
|
|
// * Optionally, reconnects the outer frame, sends an 'echo', expects a
|
|
// 'response'.
|
|
// * Optionally, reconnects the inner frame, but first reconfigures it to the
|
|
// alternate protocol version, simulating an inner frame navigation that
|
|
// picks up a new/old version.
|
|
//
|
|
// Every valid combination of protocol versions is tested, with both single and
|
|
// double ended handshakes. Two timing scenarios are tested per combination,
|
|
// which is what the 'reverse' parameter distinguishes.
|
|
//
|
|
// Where single sided handshake is in use, reconnection by the outer frame is
|
|
// not supported, and therefore is not tested.
|
|
//
|
|
// The only known issue migrating to V2 is that once two V2 peers have
|
|
// connected, replacing either peer with a V1 peer will not work. Upgrading V1
|
|
// peers to v2 is supported, as is replacing the only v2 peer in a connection
|
|
// with a v1.
|
|
|
|
|
|
function testLifeCycle_v1_v1() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v1_v1_rev() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v1_v1_onesided() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v1_v1_onesided_rev() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v1_v2() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v1_v2_rev() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v1_v2_onesided() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v1_v2_onesided_rev() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
1 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v1() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v1_rev() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v1_onesided() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v1_onesided_rev() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
true /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v2() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
false /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v2_rev() {
|
|
checkLifeCycle(
|
|
false /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
true /* outerFrameReconnectSupported */,
|
|
false /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v2_onesided() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
false /* innerFrameMigrationSupported */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testLifeCycle_v2_v2_onesided_rev() {
|
|
checkLifeCycle(
|
|
true /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
false /* outerFrameReconnectSupported */,
|
|
false /* innerFrameMigrationSupported */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function checkLifeCycle(oneSidedHandshake, innerProtocolVersion,
|
|
outerProtocolVersion, outerFrameReconnectSupported,
|
|
innerFrameMigrationSupported, reverse) {
|
|
driver.createPeerIframe('new_iframe', oneSidedHandshake,
|
|
innerProtocolVersion, outerProtocolVersion);
|
|
driver.connect(true /* fullLifeCycleTest */, outerFrameReconnectSupported,
|
|
innerFrameMigrationSupported, reverse);
|
|
}
|
|
|
|
// testConnectMismatchedNames have been flaky on IEs.
|
|
// Flakiness is tracked in http://b/18595666
|
|
// For now, not running these tests on IE.
|
|
|
|
function testConnectMismatchedNames_v1_v1() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
1 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testConnectMismatchedNames_v1_v1_rev() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
1 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testConnectMismatchedNames_v1_v2() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
1 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testConnectMismatchedNames_v1_v2_rev() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
1 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testConnectMismatchedNames_v2_v1() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
2 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testConnectMismatchedNames_v2_v1_rev() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
2 /* innerProtocolVersion */,
|
|
1 /* outerProtocolVersion */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function testConnectMismatchedNames_v2_v2() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
2 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
false /* reverse */);
|
|
}
|
|
|
|
|
|
function testConnectMismatchedNames_v2_v2_rev() {
|
|
if (goog.labs.userAgent.browser.isIE()) {
|
|
return;
|
|
}
|
|
|
|
checkConnectMismatchedNames(
|
|
2 /* innerProtocolVersion */,
|
|
2 /* outerProtocolVersion */,
|
|
true /* reverse */);
|
|
}
|
|
|
|
|
|
function checkConnectMismatchedNames(innerProtocolVersion,
|
|
outerProtocolVersion, reverse) {
|
|
driver.createPeerIframe('new_iframe', false /* oneSidedHandshake */,
|
|
innerProtocolVersion,
|
|
outerProtocolVersion, true /* opt_randomChannelNames */);
|
|
driver.connect(false /* fullLifeCycleTest */,
|
|
false /* outerFrameReconnectSupported */,
|
|
false /* innerFrameMigrationSupported */,
|
|
reverse /* reverse */);
|
|
}
|
|
|
|
|
|
function testEscapeServiceName() {
|
|
var escape = goog.net.xpc.CrossPageChannel.prototype.escapeServiceName_;
|
|
assertEquals('Shouldn\'t escape alphanumeric name',
|
|
'fooBar123', escape('fooBar123'));
|
|
assertEquals('Shouldn\'t escape most non-alphanumeric characters',
|
|
'`~!@#$^&*()_-=+ []{}\'";,<.>/?\\',
|
|
escape('`~!@#$^&*()_-=+ []{}\'";,<.>/?\\'));
|
|
assertEquals('Should escape %, |, and :',
|
|
'foo%3ABar%7C123%25', escape('foo:Bar|123%'));
|
|
assertEquals('Should escape tp', '%25tp', escape('tp'));
|
|
assertEquals('Should escape %tp', '%25%25tp', escape('%tp'));
|
|
assertEquals('Should not escape stp', 'stp', escape('stp'));
|
|
assertEquals('Should not escape s%tp', 's%25tp', escape('s%tp'));
|
|
}
|
|
|
|
|
|
function testSameDomainCheck_noMessageOrigin() {
|
|
var channel = new goog.net.xpc.CrossPageChannel(goog.object.create(
|
|
goog.net.xpc.CfgFields.PEER_HOSTNAME, 'http://foo.com'));
|
|
assertTrue(channel.isMessageOriginAcceptable_(undefined));
|
|
}
|
|
|
|
|
|
function testSameDomainCheck_noPeerHostname() {
|
|
var channel = new goog.net.xpc.CrossPageChannel({});
|
|
assertTrue(channel.isMessageOriginAcceptable_('http://foo.com'));
|
|
}
|
|
|
|
|
|
function testSameDomainCheck_unconfigured() {
|
|
var channel = new goog.net.xpc.CrossPageChannel({});
|
|
assertTrue(channel.isMessageOriginAcceptable_(undefined));
|
|
}
|
|
|
|
|
|
function testSameDomainCheck_originsMatch() {
|
|
var channel = new goog.net.xpc.CrossPageChannel(goog.object.create(
|
|
goog.net.xpc.CfgFields.PEER_HOSTNAME, 'http://foo.com'));
|
|
assertTrue(channel.isMessageOriginAcceptable_('http://foo.com'));
|
|
}
|
|
|
|
|
|
function testSameDomainCheck_originsMismatch() {
|
|
var channel = new goog.net.xpc.CrossPageChannel(goog.object.create(
|
|
goog.net.xpc.CfgFields.PEER_HOSTNAME, 'http://foo.com'));
|
|
assertFalse(channel.isMessageOriginAcceptable_('http://nasty.com'));
|
|
}
|
|
|
|
|
|
function testUnescapeServiceName() {
|
|
var unescape = goog.net.xpc.CrossPageChannel.prototype.unescapeServiceName_;
|
|
assertEquals('Shouldn\'t modify alphanumeric name',
|
|
'fooBar123', unescape('fooBar123'));
|
|
assertEquals('Shouldn\'t modify most non-alphanumeric characters',
|
|
'`~!@#$^&*()_-=+ []{}\'";,<.>/?\\',
|
|
unescape('`~!@#$^&*()_-=+ []{}\'";,<.>/?\\'));
|
|
assertEquals('Should unescape URL-escapes',
|
|
'foo:Bar|123%', unescape('foo%3ABar%7C123%25'));
|
|
assertEquals('Should unescape tp', 'tp', unescape('%25tp'));
|
|
assertEquals('Should unescape %tp', '%tp', unescape('%25%25tp'));
|
|
assertEquals('Should not escape stp', 'stp', unescape('stp'));
|
|
assertEquals('Should not escape s%tp', 's%tp', unescape('s%25tp'));
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests the case where the channel is disposed before it is fully connected.
|
|
*/
|
|
function testDisposeBeforeConnect() {
|
|
asyncTestCase.waitForAsync('Checking disposal before connection.');
|
|
driver.createPeerIframe('new_iframe', false /* oneSidedHandshake */,
|
|
2 /* innerProtocolVersion */, 2 /* outerProtocolVersion */,
|
|
true /* opt_randomChannelNames */);
|
|
driver.connectOuterAndDispose();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Driver for the tests for CrossPageChannel.
|
|
*
|
|
* @constructor
|
|
* @extends {goog.Disposable}
|
|
*/
|
|
Driver = function() {
|
|
goog.Disposable.call(this);
|
|
|
|
/**
|
|
* The peer iframe.
|
|
* @type {!Element}
|
|
* @private
|
|
*/
|
|
this.iframe_ = null;
|
|
|
|
/**
|
|
* The channel to use.
|
|
* @type {goog.net.xpc.CrossPageChannel}
|
|
* @private
|
|
*/
|
|
this.channel_ = null;
|
|
|
|
/**
|
|
* Outer frame configuration object.
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this.outerFrameCfg_ = null;
|
|
|
|
/**
|
|
* The initial name of the outer channel.
|
|
* @type {?string}
|
|
* @private
|
|
*/
|
|
this.initialOuterChannelName_ = null;
|
|
|
|
/**
|
|
* Inner frame configuration object.
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this.innerFrameCfg_ = null;
|
|
|
|
/**
|
|
* The contents of the payload of the 'echo' request sent by the inner frame.
|
|
* @type {?string}
|
|
* @private
|
|
*/
|
|
this.innerFrameEchoPayload_ = null;
|
|
|
|
/**
|
|
* The contents of the payload of the 'echo' request sent by the outer frame.
|
|
* @type {?string}
|
|
* @private
|
|
*/
|
|
this.outerFrameEchoPayload_ = null;
|
|
|
|
/**
|
|
* A deferred which fires when the inner frame receives its echo response.
|
|
* @type {goog.async.Deferred}
|
|
* @private
|
|
*/
|
|
this.innerFrameResponseReceived_ = new goog.async.Deferred();
|
|
|
|
/**
|
|
* A deferred which fires when the outer frame receives its echo response.
|
|
* @type {goog.async.Deferred}
|
|
* @private
|
|
*/
|
|
this.outerFrameResponseReceived_ = new goog.async.Deferred();
|
|
|
|
};
|
|
goog.inherits(Driver, goog.Disposable);
|
|
|
|
|
|
/** @override */
|
|
Driver.prototype.disposeInternal = function() {
|
|
// Required to make this test perform acceptably (and pass) on slow browsers,
|
|
// esp IE8.
|
|
if (CLEAN_UP_IFRAMES) {
|
|
goog.dom.removeNode(this.iframe_);
|
|
delete this.iframe_;
|
|
}
|
|
goog.dispose(this.channel_);
|
|
this.innerFrameResponseReceived_.cancel();
|
|
this.innerFrameResponseReceived_ = null;
|
|
this.outerFrameResponseReceived_.cancel();
|
|
this.outerFrameResponseReceived_ = null;
|
|
Driver.base(this, 'disposeInternal');
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns the child peer's window object.
|
|
* @return {Window} Child peer's window.
|
|
* @private
|
|
*/
|
|
Driver.prototype.getInnerPeer_ = function() {
|
|
return this.iframe_.contentWindow;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets up the configuration objects for the inner and outer frames.
|
|
* @param {string=} opt_iframeId If present, the ID of the iframe to use,
|
|
* otherwise, tells the channel to generate an iframe ID.
|
|
* @param {boolean=} opt_oneSidedHandshake Whether the one sided handshake
|
|
* config option should be set.
|
|
* @param {string=} opt_channelName The name of the channel to use, or null
|
|
* to generate one.
|
|
* @param {number=} opt_innerProtocolVersion The native transport protocol
|
|
* version used in the inner iframe.
|
|
* @param {number=} opt_outerProtocolVersion The native transport protocol
|
|
* version used in the outer iframe.
|
|
* @param {boolean=} opt_randomChannelNames Whether the different ends of the
|
|
* channel should be allowed to pick differing, random names.
|
|
* @return {string} The name of the created channel.
|
|
* @private
|
|
*/
|
|
Driver.prototype.setConfiguration_ = function(opt_iframeId,
|
|
opt_oneSidedHandshake, opt_channelName, opt_innerProtocolVersion,
|
|
opt_outerProtocolVersion, opt_randomChannelNames) {
|
|
var cfg = {};
|
|
if (opt_iframeId) {
|
|
cfg[goog.net.xpc.CfgFields.IFRAME_ID] = opt_iframeId;
|
|
}
|
|
cfg[goog.net.xpc.CfgFields.PEER_URI] = 'testdata/inner_peer.html';
|
|
if (!opt_randomChannelNames) {
|
|
var channelName = opt_channelName || 'test_channel' + uniqueId++;
|
|
cfg[goog.net.xpc.CfgFields.CHANNEL_NAME] = channelName;
|
|
}
|
|
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] = 'does-not-exist.html';
|
|
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] = 'does-not-exist.html';
|
|
cfg[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE] = !!opt_oneSidedHandshake;
|
|
cfg[goog.net.xpc.CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] =
|
|
opt_outerProtocolVersion;
|
|
function resolveUri(fieldName) {
|
|
cfg[fieldName] =
|
|
goog.Uri.resolve(window.location.href, cfg[fieldName]).toString();
|
|
}
|
|
resolveUri(goog.net.xpc.CfgFields.PEER_URI);
|
|
resolveUri(goog.net.xpc.CfgFields.LOCAL_POLL_URI);
|
|
resolveUri(goog.net.xpc.CfgFields.PEER_POLL_URI);
|
|
this.outerFrameCfg_ = cfg;
|
|
this.innerFrameCfg_ = goog.object.clone(cfg);
|
|
this.innerFrameCfg_[
|
|
goog.net.xpc.CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] =
|
|
opt_innerProtocolVersion;
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates an outer frame channel object.
|
|
* @private
|
|
*/
|
|
Driver.prototype.createChannel_ = function() {
|
|
if (this.channel_) {
|
|
this.channel_.dispose();
|
|
}
|
|
this.channel_ = new goog.net.xpc.CrossPageChannel(this.outerFrameCfg_);
|
|
this.channel_.registerService('echo',
|
|
goog.bind(this.echoHandler_, this));
|
|
this.channel_.registerService('response',
|
|
goog.bind(this.responseHandler_, this));
|
|
|
|
return this.channel_.name;
|
|
};
|
|
|
|
|
|
/**
|
|
* Checks the names of the inner and outer frames meet expectations.
|
|
* @private
|
|
*/
|
|
Driver.prototype.checkChannelNames_ = function() {
|
|
var outerName = this.channel_.name;
|
|
var innerName = this.getInnerPeer_().channel.name;
|
|
var configName = this.innerFrameCfg_[goog.net.xpc.CfgFields.CHANNEL_NAME] ||
|
|
null;
|
|
|
|
// The outer channel never changes its name.
|
|
assertEquals(this.initialOuterChannelName_, outerName);
|
|
// The name should be as configured, if it was configured.
|
|
if (configName) {
|
|
assertEquals(configName, innerName);
|
|
}
|
|
// The names of both ends of the channel should match.
|
|
assertEquals(innerName, outerName);
|
|
G_testRunner.log('Channel name: ' + innerName);
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns the configuration of the xpc.
|
|
* @return {?Object} The configuration of the xpc.
|
|
*/
|
|
Driver.prototype.getInnerFrameConfiguration = function() {
|
|
return this.innerFrameCfg_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates the peer iframe.
|
|
* @param {string=} opt_iframeId If present, the ID of the iframe to create,
|
|
* otherwise, generates an iframe ID.
|
|
* @param {boolean=} opt_oneSidedHandshake Whether a one sided handshake is
|
|
* specified.
|
|
* @param {number=} opt_innerProtocolVersion The native transport protocol
|
|
* version used in the inner iframe.
|
|
* @param {number=} opt_outerProtocolVersion The native transport protocol
|
|
* version used in the outer iframe.
|
|
* @param {boolean=} opt_randomChannelNames Whether the ends of the channel
|
|
* should be allowed to pick differing, random names.
|
|
* @return {!Array<string>} The id of the created iframe and the name of the
|
|
* created channel.
|
|
*/
|
|
Driver.prototype.createPeerIframe = function(opt_iframeId,
|
|
opt_oneSidedHandshake, opt_innerProtocolVersion, opt_outerProtocolVersion,
|
|
opt_randomChannelNames) {
|
|
var expectedIframeId;
|
|
|
|
if (opt_iframeId) {
|
|
expectedIframeId = opt_iframeId = opt_iframeId + uniqueId++;
|
|
} else {
|
|
// Have createPeerIframe() generate an ID
|
|
stubs.set(goog.net.xpc, 'getRandomString', function(length) {
|
|
return '' + length;
|
|
});
|
|
expectedIframeId = 'xpcpeer4';
|
|
}
|
|
assertNull('element[id=' + expectedIframeId + '] exists',
|
|
goog.dom.getElement(expectedIframeId));
|
|
|
|
this.setConfiguration_(opt_iframeId, opt_oneSidedHandshake,
|
|
undefined /* opt_channelName */, opt_innerProtocolVersion,
|
|
opt_outerProtocolVersion, opt_randomChannelNames);
|
|
var channelName = this.createChannel_();
|
|
this.initialOuterChannelName_ = channelName;
|
|
this.iframe_ = this.channel_.createPeerIframe(document.body);
|
|
|
|
assertEquals(expectedIframeId, this.iframe_.id);
|
|
};
|
|
|
|
|
|
/**
|
|
* Checks if the peer iframe has been created.
|
|
*/
|
|
Driver.prototype.checkPeerIframe = function() {
|
|
assertNotNull(this.iframe_);
|
|
var peer = this.getInnerPeer_();
|
|
assertNotNull(peer);
|
|
assertNotNull(peer.document);
|
|
};
|
|
|
|
|
|
/**
|
|
* Starts the connection. The connection happens asynchronously.
|
|
*/
|
|
Driver.prototype.connect = function(fullLifeCycleTest,
|
|
outerFrameReconnectSupported, innerFrameMigrationSupported, reverse) {
|
|
if (!this.isTransportTestable_()) {
|
|
asyncTestCase.continueTesting();
|
|
return;
|
|
}
|
|
|
|
asyncTestCase.waitForAsync('parent and child connect');
|
|
|
|
// Set the criteria for the initial handshake portion of the test.
|
|
this.reinitializeDeferreds_();
|
|
this.innerFrameResponseReceived_.awaitDeferred(
|
|
this.outerFrameResponseReceived_);
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.checkChannelNames_, this));
|
|
|
|
if (fullLifeCycleTest) {
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.testReconnects_, this,
|
|
outerFrameReconnectSupported, innerFrameMigrationSupported));
|
|
} else {
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(asyncTestCase.continueTesting, asyncTestCase));
|
|
}
|
|
|
|
this.continueConnect_(reverse);
|
|
};
|
|
|
|
|
|
Driver.prototype.continueConnect_ = function(reverse) {
|
|
// Wait until the peer is fully established. Establishment is sometimes very
|
|
// slow indeed, especially on virtual machines, so a fixed timeout is not
|
|
// suitable. This wait is required because we want to take precise control
|
|
// of the channel startup timing, and shouldn't be needed in production use,
|
|
// where the inner frame's channel is typically not started by a DOM call as
|
|
// it is here.
|
|
if (!this.getInnerPeer_() || !this.getInnerPeer_().instantiateChannel) {
|
|
window.setTimeout(goog.bind(this.continueConnect_, this, reverse), 100);
|
|
return;
|
|
}
|
|
|
|
var connectFromOuterFrame = goog.bind(this.channel_.connect, this.channel_,
|
|
goog.bind(this.outerFrameConnected_, this));
|
|
var innerConfig = this.innerFrameCfg_;
|
|
var connectFromInnerFrame = goog.bind(this.getInnerPeer_().instantiateChannel,
|
|
this.getInnerPeer_(), innerConfig);
|
|
|
|
// Take control of the timing and reverse of each frame's first SETUP call. If
|
|
// these happen to fire right on top of each other, that tends to mask
|
|
// problems that reliably occur when there is a short delay.
|
|
window.setTimeout(connectFromOuterFrame, reverse ? 1 : 10);
|
|
window.setTimeout(connectFromInnerFrame, reverse ? 10 : 1);
|
|
};
|
|
|
|
|
|
/**
|
|
* Called by the outer frame connection callback.
|
|
* @private
|
|
*/
|
|
Driver.prototype.outerFrameConnected_ = function() {
|
|
var payload = this.outerFrameEchoPayload_ =
|
|
goog.net.xpc.getRandomString(10);
|
|
this.channel_.send('echo', payload);
|
|
};
|
|
|
|
|
|
/**
|
|
* Called by the inner frame connection callback.
|
|
*/
|
|
Driver.prototype.innerFrameConnected = function() {
|
|
var payload = this.innerFrameEchoPayload_ =
|
|
goog.net.xpc.getRandomString(10);
|
|
this.getInnerPeer_().sendEcho(payload);
|
|
};
|
|
|
|
|
|
/**
|
|
* The handler function for incoming echo requests.
|
|
* @param {string} payload The message payload.
|
|
* @private
|
|
*/
|
|
Driver.prototype.echoHandler_ = function(payload) {
|
|
assertTrue('outer frame should be connected', this.channel_.isConnected());
|
|
var peer = this.getInnerPeer_();
|
|
assertTrue('child should be connected', peer.isConnected());
|
|
this.channel_.send('response', payload);
|
|
};
|
|
|
|
|
|
/**
|
|
* The handler function for incoming echo responses.
|
|
* @param {string} payload The message payload.
|
|
* @private
|
|
*/
|
|
Driver.prototype.responseHandler_ = function(payload) {
|
|
assertTrue('outer frame should be connected', this.channel_.isConnected());
|
|
var peer = this.getInnerPeer_();
|
|
assertTrue('child should be connected', peer.isConnected());
|
|
assertEquals(this.outerFrameEchoPayload_, payload);
|
|
this.outerFrameResponseReceived_.callback(true);
|
|
};
|
|
|
|
|
|
/**
|
|
* The handler function for incoming echo replies.
|
|
* @param {string} payload The message payload.
|
|
*/
|
|
Driver.prototype.innerFrameGotResponse = function(payload) {
|
|
assertTrue('outer frame should be connected', this.channel_.isConnected());
|
|
var peer = this.getInnerPeer_();
|
|
assertTrue('child should be connected', peer.isConnected());
|
|
assertEquals(this.innerFrameEchoPayload_, payload);
|
|
this.innerFrameResponseReceived_.callback(true);
|
|
};
|
|
|
|
|
|
/**
|
|
* The second phase of the standard test, where reconnections of both the inner
|
|
* and outer frames are performed.
|
|
* @param {boolean} outerFrameReconnectSupported Whether outer frame reconnects
|
|
* are supported, and should be tested.
|
|
* @private
|
|
*/
|
|
Driver.prototype.testReconnects_ = function(outerFrameReconnectSupported,
|
|
innerFrameMigrationSupported) {
|
|
G_testRunner.log('Performing inner frame reconnect');
|
|
this.reinitializeDeferreds_();
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.checkChannelNames_, this));
|
|
|
|
if (outerFrameReconnectSupported) {
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.performOuterFrameReconnect_, this,
|
|
innerFrameMigrationSupported));
|
|
} else if (innerFrameMigrationSupported) {
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.migrateInnerFrame_, this));
|
|
} else {
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(asyncTestCase.continueTesting, asyncTestCase));
|
|
}
|
|
|
|
this.performInnerFrameReconnect_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Initializes the deferreds and clears the echo payloads, ready for another
|
|
* sub-test.
|
|
* @private
|
|
*/
|
|
Driver.prototype.reinitializeDeferreds_ = function() {
|
|
this.innerFrameEchoPayload_ = null;
|
|
this.outerFrameEchoPayload_ = null;
|
|
this.innerFrameResponseReceived_.cancel();
|
|
this.innerFrameResponseReceived_ = new goog.async.Deferred();
|
|
this.outerFrameResponseReceived_.cancel();
|
|
this.outerFrameResponseReceived_ = new goog.async.Deferred();
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the inner frame to reconnect, and repeat the echo test.
|
|
* @private
|
|
*/
|
|
Driver.prototype.performInnerFrameReconnect_ = function() {
|
|
var peer = this.getInnerPeer_();
|
|
peer.instantiateChannel(this.innerFrameCfg_);
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the outer frame to reconnect, and repeat the echo test.
|
|
* @private
|
|
*/
|
|
Driver.prototype.performOuterFrameReconnect_ = function(
|
|
innerFrameMigrationSupported) {
|
|
G_testRunner.log('Closing channel');
|
|
this.channel_.close();
|
|
|
|
// If there is another channel still open, the native transport's global
|
|
// postMessage listener will still be active. This will mean that messages
|
|
// being sent to the now-closed channel will still be received and delivered,
|
|
// such as transport service traffic from its previous correspondent in the
|
|
// other frame. Ensure these messages don't cause exceptions.
|
|
try {
|
|
this.channel_.xpcDeliver(goog.net.xpc.TRANSPORT_SERVICE_, 'payload');
|
|
} catch (e) {
|
|
fail('Should not throw exception');
|
|
}
|
|
|
|
G_testRunner.log('Reconnecting outer frame');
|
|
this.reinitializeDeferreds_();
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.checkChannelNames_, this));
|
|
if (innerFrameMigrationSupported) {
|
|
this.outerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.migrateInnerFrame_, this));
|
|
} else {
|
|
this.outerFrameResponseReceived_.addCallback(
|
|
goog.bind(asyncTestCase.continueTesting, asyncTestCase));
|
|
}
|
|
this.channel_.connect(goog.bind(this.outerFrameConnected_, this));
|
|
};
|
|
|
|
|
|
/**
|
|
* Migrate the inner frame to the alternate protocol version and reconnect it.
|
|
* @private
|
|
*/
|
|
Driver.prototype.migrateInnerFrame_ = function() {
|
|
G_testRunner.log('Migrating inner frame');
|
|
this.reinitializeDeferreds_();
|
|
var innerFrameProtoVersion = this.innerFrameCfg_[
|
|
goog.net.xpc.CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION];
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(this.checkChannelNames_, this));
|
|
this.innerFrameResponseReceived_.addCallback(
|
|
goog.bind(asyncTestCase.continueTesting, asyncTestCase));
|
|
this.innerFrameCfg_[
|
|
goog.net.xpc.CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] =
|
|
innerFrameProtoVersion == 1 ? 2 : 1;
|
|
this.performInnerFrameReconnect_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Determines if the transport type for the channel is testable.
|
|
* Some transports are misusing global state or making other
|
|
* assumptions that cause connections to fail.
|
|
* @return {boolean} Whether the transport is testable.
|
|
* @private
|
|
*/
|
|
Driver.prototype.isTransportTestable_ = function() {
|
|
var testable = false;
|
|
|
|
var transportType = this.channel_.determineTransportType_();
|
|
switch (transportType) {
|
|
case goog.net.xpc.TransportTypes.IFRAME_RELAY:
|
|
case goog.net.xpc.TransportTypes.IFRAME_POLLING:
|
|
testable = canAccessSameDomainIframe;
|
|
break;
|
|
case goog.net.xpc.TransportTypes.NATIVE_MESSAGING:
|
|
case goog.net.xpc.TransportTypes.FLASH:
|
|
case goog.net.xpc.TransportTypes.DIRECT:
|
|
case goog.net.xpc.TransportTypes.NIX:
|
|
testable = true;
|
|
break;
|
|
}
|
|
|
|
return testable;
|
|
};
|
|
|
|
|
|
/**
|
|
* Connect the outer channel but not the inner one. Wait a short time, then
|
|
* dispose the outer channel and make sure it was torn down properly.
|
|
*/
|
|
Driver.prototype.connectOuterAndDispose = function() {
|
|
this.channel_.connect();
|
|
window.setTimeout(goog.bind(this.disposeAndCheck_, this), 2000);
|
|
};
|
|
|
|
|
|
/**
|
|
* Dispose the cross-page channel. Check that the transport was also
|
|
* disposed, and allow to run briefly to make sure no timers which will cause
|
|
* failures are still running.
|
|
* @private
|
|
*/
|
|
Driver.prototype.disposeAndCheck_ = function() {
|
|
assertFalse(this.channel_.isConnected());
|
|
var transport = this.channel_.transport_;
|
|
this.channel_.dispose();
|
|
assertNull(this.channel_.transport_);
|
|
assertTrue(this.channel_.isDisposed());
|
|
assertTrue(transport.isDisposed());
|
|
|
|
// Let any errors caused by erroneous retries happen.
|
|
window.setTimeout(goog.bind(asyncTestCase.continueTesting, asyncTestCase),
|
|
2000);
|
|
};
|