crosspagechannel_test.js 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. // Copyright 2009 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.net.xpc.CrossPageChannelTest');
  15. goog.setTestOnly('goog.net.xpc.CrossPageChannelTest');
  16. goog.require('goog.Disposable');
  17. goog.require('goog.Promise');
  18. goog.require('goog.Timer');
  19. goog.require('goog.Uri');
  20. goog.require('goog.dom');
  21. goog.require('goog.dom.TagName');
  22. goog.require('goog.labs.userAgent.browser');
  23. goog.require('goog.log');
  24. goog.require('goog.log.Level');
  25. goog.require('goog.net.xpc');
  26. goog.require('goog.net.xpc.CfgFields');
  27. goog.require('goog.net.xpc.CrossPageChannel');
  28. goog.require('goog.net.xpc.CrossPageChannelRole');
  29. goog.require('goog.net.xpc.TransportTypes');
  30. goog.require('goog.object');
  31. goog.require('goog.testing.PropertyReplacer');
  32. goog.require('goog.testing.TestCase');
  33. goog.require('goog.testing.jsunit');
  34. // Set this to false when working on this test. It needs to be true for
  35. // automated testing, as some browsers (eg IE8) choke on the large numbers of
  36. // iframes this test would otherwise leave active.
  37. var CLEAN_UP_IFRAMES = true;
  38. var IFRAME_LOAD_WAIT_MS = 1000;
  39. var stubs = new goog.testing.PropertyReplacer();
  40. var uniqueId = 0;
  41. var driver;
  42. var canAccessSameDomainIframe = true;
  43. var accessCheckPromise = null;
  44. function setUpPage() {
  45. // This test is insanely slow on IE8 for some reason.
  46. goog.testing.TestCase.getActiveTestCase().promiseTimeout = 20 * 1000;
  47. // Show debug log
  48. var debugDiv = goog.dom.getElement('debugDiv');
  49. var logger = goog.log.getLogger('goog.net.xpc');
  50. logger.setLevel(goog.log.Level.ALL);
  51. goog.log.addHandler(logger, function(logRecord) {
  52. var msgElm = goog.dom.createDom(goog.dom.TagName.DIV);
  53. msgElm.innerHTML = logRecord.getMessage();
  54. goog.dom.appendChild(debugDiv, msgElm);
  55. });
  56. accessCheckPromise = new goog.Promise(function(resolve, reject) {
  57. var accessCheckIframes = [];
  58. accessCheckIframes.push(
  59. create1x1Iframe('nonexistent', 'testdata/i_am_non_existent.html'));
  60. window.setTimeout(function() {
  61. accessCheckIframes.push(
  62. create1x1Iframe('existent', 'testdata/access_checker.html'));
  63. }, 10);
  64. // Called from testdata/access_checker.html
  65. window['sameDomainIframeAccessComplete'] = function(canAccess) {
  66. canAccessSameDomainIframe = canAccess;
  67. for (var i = 0; i < accessCheckIframes.length; i++) {
  68. document.body.removeChild(accessCheckIframes[i]);
  69. }
  70. resolve();
  71. };
  72. });
  73. }
  74. function setUp() {
  75. driver = new Driver();
  76. // Ensure that the access check is complete before starting each test.
  77. return accessCheckPromise;
  78. }
  79. function tearDown() {
  80. stubs.reset();
  81. driver.dispose();
  82. }
  83. function create1x1Iframe(iframeId, src) {
  84. var iframeAccessChecker = goog.dom.createElement(goog.dom.TagName.IFRAME);
  85. iframeAccessChecker.id = iframeAccessChecker.name = iframeId;
  86. iframeAccessChecker.style.width = iframeAccessChecker.style.height = '1px';
  87. iframeAccessChecker.src = src;
  88. document.body.insertBefore(iframeAccessChecker, document.body.firstChild);
  89. return iframeAccessChecker;
  90. }
  91. function testCreateIframeSpecifyId() {
  92. driver.createPeerIframe('new_iframe');
  93. return goog.Timer.promise(IFRAME_LOAD_WAIT_MS).then(function() {
  94. driver.checkPeerIframe();
  95. });
  96. }
  97. function testCreateIframeRandomId() {
  98. driver.createPeerIframe();
  99. return goog.Timer.promise(IFRAME_LOAD_WAIT_MS).then(function() {
  100. driver.checkPeerIframe();
  101. });
  102. }
  103. function testGetRole() {
  104. var cfg = {};
  105. cfg[goog.net.xpc.CfgFields.ROLE] = goog.net.xpc.CrossPageChannelRole.OUTER;
  106. var channel = new goog.net.xpc.CrossPageChannel(cfg);
  107. // If the configured role is ignored, this will cause the dynamicly
  108. // determined role to become INNER.
  109. channel.peerWindowObject_ = window.parent;
  110. assertEquals(
  111. 'Channel should use role from the config.',
  112. goog.net.xpc.CrossPageChannelRole.OUTER, channel.getRole());
  113. channel.dispose();
  114. }
  115. // The following batch of tests:
  116. // * Establishes a peer iframe
  117. // * Connects an XPC channel between the frames
  118. // * From the connection callback in each frame, sends an 'echo' request, and
  119. // expects a 'response' response.
  120. // * Reconnects the inner frame, sends an 'echo', expects a 'response'.
  121. // * Optionally, reconnects the outer frame, sends an 'echo', expects a
  122. // 'response'.
  123. // * Optionally, reconnects the inner frame, but first reconfigures it to the
  124. // alternate protocol version, simulating an inner frame navigation that
  125. // picks up a new/old version.
  126. //
  127. // Every valid combination of protocol versions is tested, with both single and
  128. // double ended handshakes. Two timing scenarios are tested per combination,
  129. // which is what the 'reverse' parameter distinguishes.
  130. //
  131. // Where single sided handshake is in use, reconnection by the outer frame is
  132. // not supported, and therefore is not tested.
  133. //
  134. // The only known issue migrating to V2 is that once two V2 peers have
  135. // connected, replacing either peer with a V1 peer will not work. Upgrading V1
  136. // peers to v2 is supported, as is replacing the only v2 peer in a connection
  137. // with a v1.
  138. function testLifeCycle_v1_v1() {
  139. return checkLifeCycle(
  140. false /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  141. 1 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  142. true /* innerFrameMigrationSupported */, false /* reverse */);
  143. }
  144. function testLifeCycle_v1_v1_rev() {
  145. return checkLifeCycle(
  146. false /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  147. 1 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  148. true /* innerFrameMigrationSupported */, true /* reverse */);
  149. }
  150. function testLifeCycle_v1_v1_onesided() {
  151. return checkLifeCycle(
  152. true /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  153. 1 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  154. true /* innerFrameMigrationSupported */, false /* reverse */);
  155. }
  156. function testLifeCycle_v1_v1_onesided_rev() {
  157. return checkLifeCycle(
  158. true /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  159. 1 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  160. true /* innerFrameMigrationSupported */, true /* reverse */);
  161. }
  162. function testLifeCycle_v1_v2() {
  163. return checkLifeCycle(
  164. false /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  165. 2 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  166. true /* innerFrameMigrationSupported */, false /* reverse */);
  167. }
  168. function testLifeCycle_v1_v2_rev() {
  169. return checkLifeCycle(
  170. false /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  171. 2 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  172. true /* innerFrameMigrationSupported */, true /* reverse */);
  173. }
  174. function testLifeCycle_v1_v2_onesided() {
  175. return checkLifeCycle(
  176. true /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  177. 2 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  178. true /* innerFrameMigrationSupported */, false /* reverse */);
  179. }
  180. function testLifeCycle_v1_v2_onesided_rev() {
  181. return checkLifeCycle(
  182. true /* oneSidedHandshake */, 1 /* innerProtocolVersion */,
  183. 2 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  184. true /* innerFrameMigrationSupported */, true /* reverse */);
  185. }
  186. function testLifeCycle_v2_v1() {
  187. return checkLifeCycle(
  188. false /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  189. 1 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  190. true /* innerFrameMigrationSupported */, false /* reverse */);
  191. }
  192. function testLifeCycle_v2_v1_rev() {
  193. return checkLifeCycle(
  194. false /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  195. 1 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  196. true /* innerFrameMigrationSupported */, true /* reverse */);
  197. }
  198. function testLifeCycle_v2_v1_onesided() {
  199. return checkLifeCycle(
  200. true /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  201. 1 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  202. true /* innerFrameMigrationSupported */, false /* reverse */);
  203. }
  204. function testLifeCycle_v2_v1_onesided_rev() {
  205. return checkLifeCycle(
  206. true /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  207. 1 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  208. true /* innerFrameMigrationSupported */, true /* reverse */);
  209. }
  210. function testLifeCycle_v2_v2() {
  211. // Test flakes on IE 10+ and Chrome: see b/22873770 and b/18595666.
  212. if ((goog.labs.userAgent.browser.isIE() &&
  213. goog.labs.userAgent.browser.isVersionOrHigher(10)) ||
  214. goog.labs.userAgent.browser.isChrome()) {
  215. return;
  216. }
  217. return checkLifeCycle(
  218. false /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  219. 2 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  220. false /* innerFrameMigrationSupported */, false /* reverse */);
  221. }
  222. function testLifeCycle_v2_v2_rev() {
  223. return checkLifeCycle(
  224. false /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  225. 2 /* outerProtocolVersion */, true /* outerFrameReconnectSupported */,
  226. false /* innerFrameMigrationSupported */, true /* reverse */);
  227. }
  228. function testLifeCycle_v2_v2_onesided() {
  229. return checkLifeCycle(
  230. true /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  231. 2 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  232. false /* innerFrameMigrationSupported */, false /* reverse */);
  233. }
  234. function testLifeCycle_v2_v2_onesided_rev() {
  235. return checkLifeCycle(
  236. true /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  237. 2 /* outerProtocolVersion */, false /* outerFrameReconnectSupported */,
  238. false /* innerFrameMigrationSupported */, true /* reverse */);
  239. }
  240. function checkLifeCycle(
  241. oneSidedHandshake, innerProtocolVersion, outerProtocolVersion,
  242. outerFrameReconnectSupported, innerFrameMigrationSupported, reverse) {
  243. driver.createPeerIframe(
  244. 'new_iframe', oneSidedHandshake, innerProtocolVersion,
  245. outerProtocolVersion);
  246. return driver.connect(
  247. true /* fullLifeCycleTest */, outerFrameReconnectSupported,
  248. innerFrameMigrationSupported, reverse);
  249. }
  250. // testConnectMismatchedNames have been flaky on IEs.
  251. // Flakiness is tracked in http://b/18595666
  252. // For now, not running these tests on IE.
  253. function testConnectMismatchedNames_v1_v1() {
  254. if (goog.labs.userAgent.browser.isIE()) {
  255. return;
  256. }
  257. return checkConnectMismatchedNames(
  258. 1 /* innerProtocolVersion */, 1 /* outerProtocolVersion */,
  259. false /* reverse */);
  260. }
  261. function testConnectMismatchedNames_v1_v1_rev() {
  262. if (goog.labs.userAgent.browser.isIE()) {
  263. return;
  264. }
  265. return checkConnectMismatchedNames(
  266. 1 /* innerProtocolVersion */, 1 /* outerProtocolVersion */,
  267. true /* reverse */);
  268. }
  269. function testConnectMismatchedNames_v1_v2() {
  270. if (goog.labs.userAgent.browser.isIE()) {
  271. return;
  272. }
  273. return checkConnectMismatchedNames(
  274. 1 /* innerProtocolVersion */, 2 /* outerProtocolVersion */,
  275. false /* reverse */);
  276. }
  277. function testConnectMismatchedNames_v1_v2_rev() {
  278. if (goog.labs.userAgent.browser.isIE()) {
  279. return;
  280. }
  281. return checkConnectMismatchedNames(
  282. 1 /* innerProtocolVersion */, 2 /* outerProtocolVersion */,
  283. true /* reverse */);
  284. }
  285. function testConnectMismatchedNames_v2_v1() {
  286. if (goog.labs.userAgent.browser.isIE()) {
  287. return;
  288. }
  289. return checkConnectMismatchedNames(
  290. 2 /* innerProtocolVersion */, 1 /* outerProtocolVersion */,
  291. false /* reverse */);
  292. }
  293. function testConnectMismatchedNames_v2_v1_rev() {
  294. if (goog.labs.userAgent.browser.isIE()) {
  295. return;
  296. }
  297. return checkConnectMismatchedNames(
  298. 2 /* innerProtocolVersion */, 1 /* outerProtocolVersion */,
  299. true /* reverse */);
  300. }
  301. function testConnectMismatchedNames_v2_v2() {
  302. if (goog.labs.userAgent.browser.isIE()) {
  303. return;
  304. }
  305. return checkConnectMismatchedNames(
  306. 2 /* innerProtocolVersion */, 2 /* outerProtocolVersion */,
  307. false /* reverse */);
  308. }
  309. function testConnectMismatchedNames_v2_v2_rev() {
  310. if (goog.labs.userAgent.browser.isIE()) {
  311. return;
  312. }
  313. return checkConnectMismatchedNames(
  314. 2 /* innerProtocolVersion */, 2 /* outerProtocolVersion */,
  315. true /* reverse */);
  316. }
  317. function checkConnectMismatchedNames(
  318. innerProtocolVersion, outerProtocolVersion, reverse) {
  319. driver.createPeerIframe(
  320. 'new_iframe', false /* oneSidedHandshake */, innerProtocolVersion,
  321. outerProtocolVersion, true /* opt_randomChannelNames */);
  322. return driver.connect(
  323. false /* fullLifeCycleTest */, false /* outerFrameReconnectSupported */,
  324. false /* innerFrameMigrationSupported */, reverse /* reverse */);
  325. }
  326. function testEscapeServiceName() {
  327. var escape = goog.net.xpc.CrossPageChannel.prototype.escapeServiceName_;
  328. assertEquals(
  329. 'Shouldn\'t escape alphanumeric name', 'fooBar123', escape('fooBar123'));
  330. assertEquals(
  331. 'Shouldn\'t escape most non-alphanumeric characters',
  332. '`~!@#$^&*()_-=+ []{}\'";,<.>/?\\',
  333. escape('`~!@#$^&*()_-=+ []{}\'";,<.>/?\\'));
  334. assertEquals(
  335. 'Should escape %, |, and :', 'foo%3ABar%7C123%25',
  336. escape('foo:Bar|123%'));
  337. assertEquals('Should escape tp', '%25tp', escape('tp'));
  338. assertEquals('Should escape %tp', '%25%25tp', escape('%tp'));
  339. assertEquals('Should not escape stp', 'stp', escape('stp'));
  340. assertEquals('Should not escape s%tp', 's%25tp', escape('s%tp'));
  341. }
  342. function testSameDomainCheck_noMessageOrigin() {
  343. var channel = new goog.net.xpc.CrossPageChannel(
  344. goog.object.create(
  345. goog.net.xpc.CfgFields.PEER_HOSTNAME, 'http://foo.com'));
  346. assertTrue(channel.isMessageOriginAcceptable(undefined));
  347. }
  348. function testSameDomainCheck_noPeerHostname() {
  349. var channel = new goog.net.xpc.CrossPageChannel({});
  350. assertTrue(channel.isMessageOriginAcceptable('http://foo.com'));
  351. }
  352. function testSameDomainCheck_unconfigured() {
  353. var channel = new goog.net.xpc.CrossPageChannel({});
  354. assertTrue(channel.isMessageOriginAcceptable(undefined));
  355. }
  356. function testSameDomainCheck_originsMatch() {
  357. var channel = new goog.net.xpc.CrossPageChannel(
  358. goog.object.create(
  359. goog.net.xpc.CfgFields.PEER_HOSTNAME, 'http://foo.com'));
  360. assertTrue(channel.isMessageOriginAcceptable('http://foo.com'));
  361. }
  362. function testSameDomainCheck_originsMismatch() {
  363. var channel = new goog.net.xpc.CrossPageChannel(
  364. goog.object.create(
  365. goog.net.xpc.CfgFields.PEER_HOSTNAME, 'http://foo.com'));
  366. assertFalse(channel.isMessageOriginAcceptable('http://nasty.com'));
  367. }
  368. function testUnescapeServiceName() {
  369. var unescape = goog.net.xpc.CrossPageChannel.prototype.unescapeServiceName_;
  370. assertEquals(
  371. 'Shouldn\'t modify alphanumeric name', 'fooBar123',
  372. unescape('fooBar123'));
  373. assertEquals(
  374. 'Shouldn\'t modify most non-alphanumeric characters',
  375. '`~!@#$^&*()_-=+ []{}\'";,<.>/?\\',
  376. unescape('`~!@#$^&*()_-=+ []{}\'";,<.>/?\\'));
  377. assertEquals(
  378. 'Should unescape URL-escapes', 'foo:Bar|123%',
  379. unescape('foo%3ABar%7C123%25'));
  380. assertEquals('Should unescape tp', 'tp', unescape('%25tp'));
  381. assertEquals('Should unescape %tp', '%tp', unescape('%25%25tp'));
  382. assertEquals('Should not escape stp', 'stp', unescape('stp'));
  383. assertEquals('Should not escape s%tp', 's%tp', unescape('s%25tp'));
  384. }
  385. /**
  386. * Tests the case where the channel is disposed before it is fully connected.
  387. */
  388. function testDisposeBeforeConnect() {
  389. // Test flakes on IE: see b/22873770 and b/18595666.
  390. if (goog.labs.userAgent.browser.isIE() &&
  391. goog.labs.userAgent.browser.isVersionOrHigher(9)) {
  392. return;
  393. }
  394. driver.createPeerIframe(
  395. 'new_iframe', false /* oneSidedHandshake */, 2 /* innerProtocolVersion */,
  396. 2 /* outerProtocolVersion */, true /* opt_randomChannelNames */);
  397. return driver.connectOuterAndDispose();
  398. }
  399. /**
  400. * Driver for the tests for CrossPageChannel.
  401. *
  402. * @constructor
  403. * @extends {goog.Disposable}
  404. */
  405. Driver = function() {
  406. goog.Disposable.call(this);
  407. /**
  408. * The peer iframe.
  409. * @type {!Element}
  410. * @private
  411. */
  412. this.iframe_ = null;
  413. /**
  414. * The channel to use.
  415. * @type {goog.net.xpc.CrossPageChannel}
  416. * @private
  417. */
  418. this.channel_ = null;
  419. /**
  420. * Outer frame configuration object.
  421. * @type {Object}
  422. * @private
  423. */
  424. this.outerFrameCfg_ = null;
  425. /**
  426. * The initial name of the outer channel.
  427. * @type {?string}
  428. * @private
  429. */
  430. this.initialOuterChannelName_ = null;
  431. /**
  432. * Inner frame configuration object.
  433. * @type {Object}
  434. * @private
  435. */
  436. this.innerFrameCfg_ = null;
  437. /**
  438. * The contents of the payload of the 'echo' request sent by the inner frame.
  439. * @type {?string}
  440. * @private
  441. */
  442. this.innerFrameEchoPayload_ = null;
  443. /**
  444. * The contents of the payload of the 'echo' request sent by the outer frame.
  445. * @type {?string}
  446. * @private
  447. */
  448. this.outerFrameEchoPayload_ = null;
  449. /**
  450. * A resolver which fires its promise when the inner frame receives an echo.
  451. * @type {!goog.promise.Resolver}
  452. * @private
  453. */
  454. this.innerFrameResponseReceived_ = goog.Promise.withResolver();
  455. /**
  456. * A resolver which fires its promise when the outer frame receives an echo.
  457. * @type {!goog.promise.Resolver}
  458. * @private
  459. */
  460. this.outerFrameResponseReceived_ = goog.Promise.withResolver();
  461. };
  462. goog.inherits(Driver, goog.Disposable);
  463. /** @override */
  464. Driver.prototype.disposeInternal = function() {
  465. // Required to make this test perform acceptably (and pass) on slow browsers,
  466. // esp IE8.
  467. if (CLEAN_UP_IFRAMES) {
  468. goog.dom.removeNode(this.iframe_);
  469. delete this.iframe_;
  470. }
  471. goog.dispose(this.channel_);
  472. this.innerFrameResponseReceived_.promise.cancel();
  473. this.outerFrameResponseReceived_.promise.cancel();
  474. Driver.base(this, 'disposeInternal');
  475. };
  476. /**
  477. * Returns the child peer's window object.
  478. * @return {Window} Child peer's window.
  479. * @private
  480. */
  481. Driver.prototype.getInnerPeer_ = function() {
  482. return this.iframe_.contentWindow;
  483. };
  484. /**
  485. * Sets up the configuration objects for the inner and outer frames.
  486. * @param {string=} opt_iframeId If present, the ID of the iframe to use,
  487. * otherwise, tells the channel to generate an iframe ID.
  488. * @param {boolean=} opt_oneSidedHandshake Whether the one sided handshake
  489. * config option should be set.
  490. * @param {string=} opt_channelName The name of the channel to use, or null
  491. * to generate one.
  492. * @param {number=} opt_innerProtocolVersion The native transport protocol
  493. * version used in the inner iframe.
  494. * @param {number=} opt_outerProtocolVersion The native transport protocol
  495. * version used in the outer iframe.
  496. * @param {boolean=} opt_randomChannelNames Whether the different ends of the
  497. * channel should be allowed to pick differing, random names.
  498. * @return {string} The name of the created channel.
  499. * @private
  500. */
  501. Driver.prototype.setConfiguration_ = function(
  502. opt_iframeId, opt_oneSidedHandshake, opt_channelName,
  503. opt_innerProtocolVersion, opt_outerProtocolVersion,
  504. opt_randomChannelNames) {
  505. var cfg = {};
  506. if (opt_iframeId) {
  507. cfg[goog.net.xpc.CfgFields.IFRAME_ID] = opt_iframeId;
  508. }
  509. cfg[goog.net.xpc.CfgFields.PEER_URI] = 'testdata/inner_peer.html';
  510. if (!opt_randomChannelNames) {
  511. var channelName = opt_channelName || 'test_channel' + uniqueId++;
  512. cfg[goog.net.xpc.CfgFields.CHANNEL_NAME] = channelName;
  513. }
  514. cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] = 'does-not-exist.html';
  515. cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] = 'does-not-exist.html';
  516. cfg[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE] = !!opt_oneSidedHandshake;
  517. cfg[goog.net.xpc.CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] =
  518. opt_outerProtocolVersion;
  519. function resolveUri(fieldName) {
  520. cfg[fieldName] =
  521. goog.Uri.resolve(window.location.href, cfg[fieldName]).toString();
  522. }
  523. resolveUri(goog.net.xpc.CfgFields.PEER_URI);
  524. resolveUri(goog.net.xpc.CfgFields.LOCAL_POLL_URI);
  525. resolveUri(goog.net.xpc.CfgFields.PEER_POLL_URI);
  526. this.outerFrameCfg_ = cfg;
  527. this.innerFrameCfg_ = goog.object.clone(cfg);
  528. this.innerFrameCfg_[goog.net.xpc.CfgFields
  529. .NATIVE_TRANSPORT_PROTOCOL_VERSION] =
  530. opt_innerProtocolVersion;
  531. };
  532. /**
  533. * Creates an outer frame channel object.
  534. * @private
  535. */
  536. Driver.prototype.createChannel_ = function() {
  537. if (this.channel_) {
  538. this.channel_.dispose();
  539. }
  540. this.channel_ = new goog.net.xpc.CrossPageChannel(this.outerFrameCfg_);
  541. this.channel_.registerService('echo', goog.bind(this.echoHandler_, this));
  542. this.channel_.registerService(
  543. 'response', goog.bind(this.responseHandler_, this));
  544. return this.channel_.name;
  545. };
  546. /**
  547. * Checks the names of the inner and outer frames meet expectations.
  548. * @private
  549. */
  550. Driver.prototype.checkChannelNames_ = function() {
  551. var outerName = this.channel_.name;
  552. var innerName = this.getInnerPeer_().channel.name;
  553. var configName =
  554. this.innerFrameCfg_[goog.net.xpc.CfgFields.CHANNEL_NAME] || null;
  555. // The outer channel never changes its name.
  556. assertEquals(this.initialOuterChannelName_, outerName);
  557. // The name should be as configured, if it was configured.
  558. if (configName) {
  559. assertEquals(configName, innerName);
  560. }
  561. // The names of both ends of the channel should match.
  562. assertEquals(innerName, outerName);
  563. G_testRunner.log('Channel name: ' + innerName);
  564. };
  565. /**
  566. * Returns the configuration of the xpc.
  567. * @return {?Object} The configuration of the xpc.
  568. */
  569. Driver.prototype.getInnerFrameConfiguration = function() {
  570. return this.innerFrameCfg_;
  571. };
  572. /**
  573. * Creates the peer iframe.
  574. * @param {string=} opt_iframeId If present, the ID of the iframe to create,
  575. * otherwise, generates an iframe ID.
  576. * @param {boolean=} opt_oneSidedHandshake Whether a one sided handshake is
  577. * specified.
  578. * @param {number=} opt_innerProtocolVersion The native transport protocol
  579. * version used in the inner iframe.
  580. * @param {number=} opt_outerProtocolVersion The native transport protocol
  581. * version used in the outer iframe.
  582. * @param {boolean=} opt_randomChannelNames Whether the ends of the channel
  583. * should be allowed to pick differing, random names.
  584. * @return {!Array<string>} The id of the created iframe and the name of the
  585. * created channel.
  586. */
  587. Driver.prototype.createPeerIframe = function(
  588. opt_iframeId, opt_oneSidedHandshake, opt_innerProtocolVersion,
  589. opt_outerProtocolVersion, opt_randomChannelNames) {
  590. var expectedIframeId;
  591. if (opt_iframeId) {
  592. expectedIframeId = opt_iframeId = opt_iframeId + uniqueId++;
  593. } else {
  594. // Have createPeerIframe() generate an ID
  595. stubs.set(goog.net.xpc, 'getRandomString', function(length) {
  596. return '' + length;
  597. });
  598. expectedIframeId = 'xpcpeer4';
  599. }
  600. assertNull(
  601. 'element[id=' + expectedIframeId + '] exists',
  602. goog.dom.getElement(expectedIframeId));
  603. this.setConfiguration_(
  604. opt_iframeId, opt_oneSidedHandshake, undefined /* opt_channelName */,
  605. opt_innerProtocolVersion, opt_outerProtocolVersion,
  606. opt_randomChannelNames);
  607. var channelName = this.createChannel_();
  608. this.initialOuterChannelName_ = channelName;
  609. this.iframe_ = this.channel_.createPeerIframe(document.body);
  610. assertEquals(expectedIframeId, this.iframe_.id);
  611. };
  612. /**
  613. * Checks if the peer iframe has been created.
  614. */
  615. Driver.prototype.checkPeerIframe = function() {
  616. assertNotNull(this.iframe_);
  617. var peer = this.getInnerPeer_();
  618. assertNotNull(peer);
  619. assertNotNull(peer.document);
  620. };
  621. /**
  622. * Starts the connection. The connection happens asynchronously.
  623. */
  624. Driver.prototype.connect = function(
  625. fullLifeCycleTest, outerFrameReconnectSupported,
  626. innerFrameMigrationSupported, reverse) {
  627. if (!this.isTransportTestable_()) {
  628. return;
  629. }
  630. // Set the criteria for the initial handshake portion of the test.
  631. this.reinitializePromises_();
  632. this.innerFrameResponseReceived_.promise.then(
  633. this.checkChannelNames_, null, this);
  634. if (fullLifeCycleTest) {
  635. this.innerFrameResponseReceived_.promise.then(
  636. goog.bind(
  637. this.testReconnects_, this, outerFrameReconnectSupported,
  638. innerFrameMigrationSupported));
  639. }
  640. this.continueConnect_(reverse);
  641. return this.innerFrameResponseReceived_.promise;
  642. };
  643. Driver.prototype.continueConnect_ = function(reverse) {
  644. // Wait until the peer is fully established. Establishment is sometimes very
  645. // slow indeed, especially on virtual machines, so a fixed timeout is not
  646. // suitable. This wait is required because we want to take precise control
  647. // of the channel startup timing, and shouldn't be needed in production use,
  648. // where the inner frame's channel is typically not started by a DOM call as
  649. // it is here.
  650. if (!this.getInnerPeer_() || !this.getInnerPeer_().instantiateChannel) {
  651. window.setTimeout(goog.bind(this.continueConnect_, this, reverse), 100);
  652. return;
  653. }
  654. var connectFromOuterFrame = goog.bind(
  655. this.channel_.connect, this.channel_,
  656. goog.bind(this.outerFrameConnected_, this));
  657. var innerConfig = this.innerFrameCfg_;
  658. var connectFromInnerFrame = goog.bind(
  659. this.getInnerPeer_().instantiateChannel, this.getInnerPeer_(),
  660. innerConfig);
  661. // Take control of the timing and reverse of each frame's first SETUP call. If
  662. // these happen to fire right on top of each other, that tends to mask
  663. // problems that reliably occur when there is a short delay.
  664. window.setTimeout(connectFromOuterFrame, reverse ? 1 : 10);
  665. window.setTimeout(connectFromInnerFrame, reverse ? 10 : 1);
  666. };
  667. /**
  668. * Called by the outer frame connection callback.
  669. * @private
  670. */
  671. Driver.prototype.outerFrameConnected_ = function() {
  672. var payload = this.outerFrameEchoPayload_ = goog.net.xpc.getRandomString(10);
  673. this.channel_.send('echo', payload);
  674. };
  675. /**
  676. * Called by the inner frame connection callback in inner_peer.html.
  677. */
  678. Driver.prototype.innerFrameConnected = function() {
  679. var payload = this.innerFrameEchoPayload_ = goog.net.xpc.getRandomString(10);
  680. this.getInnerPeer_().sendEcho(payload);
  681. };
  682. /**
  683. * The handler function for incoming echo requests.
  684. * @param {string} payload The message payload.
  685. * @private
  686. */
  687. Driver.prototype.echoHandler_ = function(payload) {
  688. assertTrue('outer frame should be connected', this.channel_.isConnected());
  689. var peer = this.getInnerPeer_();
  690. assertTrue('child should be connected', peer.isConnected());
  691. this.channel_.send('response', payload);
  692. };
  693. /**
  694. * The handler function for incoming echo responses.
  695. * @param {string} payload The message payload.
  696. * @private
  697. */
  698. Driver.prototype.responseHandler_ = function(payload) {
  699. assertTrue('outer frame should be connected', this.channel_.isConnected());
  700. var peer = this.getInnerPeer_();
  701. assertTrue('child should be connected', peer.isConnected());
  702. assertEquals(this.outerFrameEchoPayload_, payload);
  703. this.outerFrameResponseReceived_.resolve(true);
  704. };
  705. /**
  706. * The handler function for incoming echo replies. Called from inner_peer.html.
  707. * @param {string} payload The message payload.
  708. */
  709. Driver.prototype.innerFrameGotResponse = function(payload) {
  710. assertTrue('outer frame should be connected', this.channel_.isConnected());
  711. var peer = this.getInnerPeer_();
  712. assertTrue('child should be connected', peer.isConnected());
  713. assertEquals(this.innerFrameEchoPayload_, payload);
  714. this.innerFrameResponseReceived_.resolve(true);
  715. };
  716. /**
  717. * The second phase of the standard test, where reconnections of both the inner
  718. * and outer frames are performed.
  719. * @param {boolean} outerFrameReconnectSupported Whether outer frame reconnects
  720. * are supported, and should be tested.
  721. * @private
  722. */
  723. Driver.prototype.testReconnects_ = function(
  724. outerFrameReconnectSupported, innerFrameMigrationSupported) {
  725. G_testRunner.log('Performing inner frame reconnect');
  726. this.reinitializePromises_();
  727. this.innerFrameResponseReceived_.promise.then(
  728. this.checkChannelNames_, null, this);
  729. if (outerFrameReconnectSupported) {
  730. this.innerFrameResponseReceived_.promise.then(
  731. goog.bind(
  732. this.performOuterFrameReconnect_, this,
  733. innerFrameMigrationSupported));
  734. } else if (innerFrameMigrationSupported) {
  735. this.innerFrameResponseReceived_.promise.then(
  736. this.migrateInnerFrame_, null, this);
  737. }
  738. this.performInnerFrameReconnect_();
  739. };
  740. /**
  741. * Initializes the promise resolvers and clears the echo payloads, ready for
  742. * another sub-test.
  743. * @private
  744. */
  745. Driver.prototype.reinitializePromises_ = function() {
  746. this.innerFrameEchoPayload_ = null;
  747. this.outerFrameEchoPayload_ = null;
  748. this.innerFrameResponseReceived_.promise.cancel();
  749. this.innerFrameResponseReceived_ = goog.Promise.withResolver();
  750. this.outerFrameResponseReceived_.promise.cancel();
  751. this.outerFrameResponseReceived_ = goog.Promise.withResolver();
  752. };
  753. /**
  754. * Get the inner frame to reconnect, and repeat the echo test.
  755. * @private
  756. */
  757. Driver.prototype.performInnerFrameReconnect_ = function() {
  758. var peer = this.getInnerPeer_();
  759. peer.instantiateChannel(this.innerFrameCfg_);
  760. };
  761. /**
  762. * Get the outer frame to reconnect, and repeat the echo test.
  763. * @private
  764. */
  765. Driver.prototype.performOuterFrameReconnect_ = function(
  766. innerFrameMigrationSupported) {
  767. G_testRunner.log('Closing channel');
  768. this.channel_.close();
  769. // If there is another channel still open, the native transport's global
  770. // postMessage listener will still be active. This will mean that messages
  771. // being sent to the now-closed channel will still be received and delivered,
  772. // such as transport service traffic from its previous correspondent in the
  773. // other frame. Ensure these messages don't cause exceptions.
  774. try {
  775. this.channel_.xpcDeliver(goog.net.xpc.TRANSPORT_SERVICE_, 'payload');
  776. } catch (e) {
  777. fail('Should not throw exception');
  778. }
  779. G_testRunner.log('Reconnecting outer frame');
  780. this.reinitializePromises_();
  781. this.innerFrameResponseReceived_.promise.then(
  782. this.checkChannelNames_, null, this);
  783. if (innerFrameMigrationSupported) {
  784. this.outerFrameResponseReceived_.promise.then(
  785. this.migrateInnerFrame_, null, this);
  786. }
  787. this.channel_.connect(goog.bind(this.outerFrameConnected_, this));
  788. };
  789. /**
  790. * Migrate the inner frame to the alternate protocol version and reconnect it.
  791. * @private
  792. */
  793. Driver.prototype.migrateInnerFrame_ = function() {
  794. G_testRunner.log('Migrating inner frame');
  795. this.reinitializePromises_();
  796. var innerFrameProtoVersion =
  797. this.innerFrameCfg_[goog.net.xpc.CfgFields
  798. .NATIVE_TRANSPORT_PROTOCOL_VERSION];
  799. this.innerFrameResponseReceived_.promise.then(
  800. this.checkChannelNames_, null, this);
  801. this.innerFrameCfg_[goog.net.xpc.CfgFields
  802. .NATIVE_TRANSPORT_PROTOCOL_VERSION] =
  803. innerFrameProtoVersion == 1 ? 2 : 1;
  804. this.performInnerFrameReconnect_();
  805. };
  806. /**
  807. * Determines if the transport type for the channel is testable.
  808. * Some transports are misusing global state or making other
  809. * assumptions that cause connections to fail.
  810. * @return {boolean} Whether the transport is testable.
  811. * @private
  812. */
  813. Driver.prototype.isTransportTestable_ = function() {
  814. var testable = false;
  815. var transportType = this.channel_.determineTransportType_();
  816. switch (transportType) {
  817. case goog.net.xpc.TransportTypes.IFRAME_RELAY:
  818. case goog.net.xpc.TransportTypes.IFRAME_POLLING:
  819. testable = canAccessSameDomainIframe;
  820. break;
  821. case goog.net.xpc.TransportTypes.NATIVE_MESSAGING:
  822. case goog.net.xpc.TransportTypes.FLASH:
  823. case goog.net.xpc.TransportTypes.DIRECT:
  824. case goog.net.xpc.TransportTypes.NIX:
  825. testable = true;
  826. break;
  827. }
  828. return testable;
  829. };
  830. /**
  831. * Connect the outer channel but not the inner one. Wait a short time, then
  832. * dispose the outer channel and make sure it was torn down properly.
  833. */
  834. Driver.prototype.connectOuterAndDispose = function() {
  835. this.channel_.connect();
  836. return goog.Timer.promise(2000).then(this.disposeAndCheck_, null, this);
  837. };
  838. /**
  839. * Dispose the cross-page channel. Check that the transport was also
  840. * disposed, and allow to run briefly to make sure no timers which will cause
  841. * failures are still running.
  842. * @private
  843. */
  844. Driver.prototype.disposeAndCheck_ = function() {
  845. assertFalse(this.channel_.isConnected());
  846. var transport = this.channel_.transport_;
  847. this.channel_.dispose();
  848. assertNull(this.channel_.transport_);
  849. assertTrue(this.channel_.isDisposed());
  850. assertTrue(transport.isDisposed());
  851. // Let any errors caused by erroneous retries happen.
  852. return goog.Timer.promise(2000);
  853. };