portchannel_test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. // Copyright 2010 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.messaging.PortChannelTest');
  15. goog.setTestOnly('goog.messaging.PortChannelTest');
  16. goog.require('goog.Promise');
  17. goog.require('goog.Timer');
  18. goog.require('goog.dom');
  19. goog.require('goog.dom.TagName');
  20. goog.require('goog.events');
  21. goog.require('goog.events.EventTarget');
  22. goog.require('goog.events.EventType');
  23. goog.require('goog.json');
  24. goog.require('goog.messaging.PortChannel');
  25. goog.require('goog.testing.MockControl');
  26. goog.require('goog.testing.TestCase');
  27. goog.require('goog.testing.jsunit');
  28. goog.require('goog.testing.messaging.MockMessageEvent');
  29. var mockControl;
  30. var mockPort;
  31. var portChannel;
  32. var workerChannel;
  33. var setUpPromise;
  34. var timer;
  35. var frameDiv;
  36. function setUpPage() {
  37. frameDiv = goog.dom.getElement('frame');
  38. // Use a relatively long timeout because the iframe created by createIframe
  39. // can take a couple seconds to load its JS.
  40. goog.testing.TestCase.getActiveTestCase().promiseTimeout = 3 * 1000;
  41. if (!('Worker' in goog.global)) {
  42. return;
  43. }
  44. var worker = new Worker('testdata/portchannel_worker.js');
  45. workerChannel = new goog.messaging.PortChannel(worker);
  46. setUpPromise = new goog.Promise(function(resolve, reject) {
  47. worker.onmessage = function(e) {
  48. if (e.data == 'loaded') {
  49. resolve();
  50. }
  51. };
  52. });
  53. }
  54. function tearDownPage() {
  55. goog.dispose(workerChannel);
  56. }
  57. function setUp() {
  58. timer = new goog.Timer(50);
  59. mockControl = new goog.testing.MockControl();
  60. mockPort = new goog.events.EventTarget();
  61. mockPort.postMessage = mockControl.createFunctionMock('postMessage');
  62. portChannel = new goog.messaging.PortChannel(mockPort);
  63. if ('Worker' in goog.global) {
  64. // Ensure the worker channel has started before running each test.
  65. return setUpPromise;
  66. }
  67. }
  68. function tearDown() {
  69. goog.dispose(timer);
  70. portChannel.dispose();
  71. goog.dom.removeChildren(frameDiv);
  72. mockControl.$verifyAll();
  73. }
  74. /**
  75. * Registers a service on a channel that will accept a single test message and
  76. * then fire a Promise.
  77. *
  78. * @param {!goog.messaging.MessageChannel} channel
  79. * @param {string} name The service name.
  80. * @param {boolean=} opt_objectPayload Whether incoming payloads should be
  81. * parsed as Objects instead of raw strings.
  82. * @return {!goog.Promise<(string|!Object)>} A promise that resolves with the
  83. * value of the first message received on the service.
  84. */
  85. function registerService(channel, name, opt_objectPayload) {
  86. return new goog.Promise(function(resolve, reject) {
  87. channel.registerService(name, resolve, opt_objectPayload);
  88. });
  89. }
  90. function makeMessage(serviceName, payload) {
  91. var msg = {'serviceName': serviceName, 'payload': payload};
  92. msg[goog.messaging.PortChannel.FLAG] = true;
  93. if (goog.messaging.PortChannel.REQUIRES_SERIALIZATION_) {
  94. msg = goog.json.serialize(msg);
  95. }
  96. return msg;
  97. }
  98. function expectNoMessage() {
  99. portChannel.registerDefaultService(
  100. mockControl.createFunctionMock('expectNoMessage'));
  101. }
  102. function receiveMessage(serviceName, payload, opt_origin, opt_ports) {
  103. mockPort.dispatchEvent(
  104. goog.testing.messaging.MockMessageEvent.wrap(
  105. makeMessage(serviceName, payload), opt_origin || 'http://google.com',
  106. undefined, undefined, opt_ports));
  107. }
  108. function receiveNonChannelMessage(data) {
  109. if (goog.messaging.PortChannel.REQUIRES_SERIALIZATION_ &&
  110. !goog.isString(data)) {
  111. data = goog.json.serialize(data);
  112. }
  113. mockPort.dispatchEvent(
  114. goog.testing.messaging.MockMessageEvent.wrap(data, 'http://google.com'));
  115. }
  116. function testPostMessage() {
  117. mockPort.postMessage(makeMessage('foobar', 'This is a value'), []);
  118. mockControl.$replayAll();
  119. portChannel.send('foobar', 'This is a value');
  120. }
  121. function testPostMessageWithPorts() {
  122. if (!('MessageChannel' in goog.global)) {
  123. return;
  124. }
  125. var channel = new MessageChannel();
  126. var port1 = channel.port1;
  127. var port2 = channel.port2;
  128. mockPort.postMessage(
  129. makeMessage('foobar', {
  130. 'val': [
  131. {'_port': {'type': 'real', 'index': 0}},
  132. {'_port': {'type': 'real', 'index': 1}}
  133. ]
  134. }),
  135. [port1, port2]);
  136. mockControl.$replayAll();
  137. portChannel.send('foobar', {'val': [port1, port2]});
  138. }
  139. function testReceiveMessage() {
  140. var promise = registerService(portChannel, 'foobar');
  141. receiveMessage('foobar', 'This is a string');
  142. return promise.then(function(msg) { assertEquals(msg, 'This is a string'); });
  143. }
  144. function testReceiveMessageWithPorts() {
  145. if (!('MessageChannel' in goog.global)) {
  146. return;
  147. }
  148. var channel = new MessageChannel();
  149. var port1 = channel.port1;
  150. var port2 = channel.port2;
  151. var promise = registerService(portChannel, 'foobar', true);
  152. receiveMessage(
  153. 'foobar', {
  154. 'val': [
  155. {'_port': {'type': 'real', 'index': 0}},
  156. {'_port': {'type': 'real', 'index': 1}}
  157. ]
  158. },
  159. null, [port1, port2]);
  160. return promise.then(function(msg) {
  161. assertObjectEquals(msg, {'val': [port1, port2]});
  162. });
  163. }
  164. function testReceiveNonChannelMessageWithStringBody() {
  165. expectNoMessage();
  166. mockControl.$replayAll();
  167. receiveNonChannelMessage('Foo bar');
  168. }
  169. function testReceiveNonChannelMessageWithArrayBody() {
  170. expectNoMessage();
  171. mockControl.$replayAll();
  172. receiveNonChannelMessage([5, 'Foo bar']);
  173. }
  174. function testReceiveNonChannelMessageWithNoFlag() {
  175. expectNoMessage();
  176. mockControl.$replayAll();
  177. receiveNonChannelMessage(
  178. {serviceName: 'foobar', payload: 'this is a payload'});
  179. }
  180. function testReceiveNonChannelMessageWithFalseFlag() {
  181. expectNoMessage();
  182. mockControl.$replayAll();
  183. var body = {serviceName: 'foobar', payload: 'this is a payload'};
  184. body[goog.messaging.PortChannel.FLAG] = false;
  185. receiveNonChannelMessage(body);
  186. }
  187. // Integration tests
  188. function testWorker() {
  189. if (!('Worker' in goog.global)) {
  190. return;
  191. }
  192. var promise = registerService(workerChannel, 'pong', true);
  193. workerChannel.send('ping', {'val': 'fizzbang'});
  194. return promise.then(function(msg) {
  195. assertObjectEquals({'val': 'fizzbang'}, msg);
  196. });
  197. }
  198. function testWorkerWithPorts() {
  199. if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
  200. return;
  201. }
  202. var messageChannel = new MessageChannel();
  203. var promise = registerService(workerChannel, 'pong', true);
  204. workerChannel.send('ping', {'port': messageChannel.port1});
  205. return promise.then(function(msg) {
  206. return assertPortsEntangled(msg['port'], messageChannel.port2);
  207. });
  208. }
  209. function testPort() {
  210. if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
  211. return;
  212. }
  213. var messageChannel = new MessageChannel();
  214. workerChannel.send('addPort', messageChannel.port1);
  215. messageChannel.port2.start();
  216. var realPortChannel = new goog.messaging.PortChannel(messageChannel.port2);
  217. var promise = registerService(realPortChannel, 'pong', true);
  218. realPortChannel.send('ping', {'val': 'fizzbang'});
  219. return promise.then(function(msg) {
  220. assertObjectEquals({'val': 'fizzbang'}, msg);
  221. messageChannel.port2.close();
  222. realPortChannel.dispose();
  223. });
  224. }
  225. function testPortIgnoresOrigin() {
  226. if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
  227. return;
  228. }
  229. var messageChannel = new MessageChannel();
  230. workerChannel.send('addPort', messageChannel.port1);
  231. messageChannel.port2.start();
  232. var realPortChannel = new goog.messaging.PortChannel(
  233. messageChannel.port2, 'http://somewhere-else.com');
  234. var promise = registerService(realPortChannel, 'pong', true);
  235. realPortChannel.send('ping', {'val': 'fizzbang'});
  236. return promise.then(function(msg) {
  237. assertObjectEquals({'val': 'fizzbang'}, msg);
  238. messageChannel.port2.close();
  239. realPortChannel.dispose();
  240. });
  241. }
  242. function testWindow() {
  243. if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
  244. return;
  245. }
  246. return createIframe().then(function(iframe) {
  247. var peerOrigin = window.location.protocol + '//' + window.location.host;
  248. var iframeChannel =
  249. goog.messaging.PortChannel.forEmbeddedWindow(iframe, peerOrigin, timer);
  250. var promise = registerService(iframeChannel, 'pong');
  251. iframeChannel.send('ping', 'fizzbang');
  252. return promise.then(function(msg) {
  253. assertEquals('fizzbang', msg);
  254. goog.dispose(iframeChannel);
  255. });
  256. });
  257. }
  258. function testWindowCanceled() {
  259. if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
  260. return;
  261. }
  262. return createIframe().then(function(iframe) {
  263. var peerOrigin = window.location.protocol + '//' + window.location.host;
  264. var iframeChannel =
  265. goog.messaging.PortChannel.forEmbeddedWindow(iframe, peerOrigin, timer);
  266. iframeChannel.cancel();
  267. var promise = registerService(iframeChannel, 'pong').then(function(msg) {
  268. fail('no messages should be received due to cancellation');
  269. });
  270. iframeChannel.send('ping', 'fizzbang');
  271. // Leave plenty of time for the connection to be made if the test fails, but
  272. // stop the test before the timeout is hit.
  273. return goog.Promise.race([promise, goog.Timer.promise(500)])
  274. .thenAlways(function() { iframeChannel.dispose(); });
  275. });
  276. }
  277. function testWindowWontSendToWrongOrigin() {
  278. if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
  279. return;
  280. }
  281. return createIframe().then(function(iframe) {
  282. var iframeChannel = goog.messaging.PortChannel.forEmbeddedWindow(
  283. iframe, 'http://somewhere-else.com', timer);
  284. var promise = registerService(iframeChannel, 'pong').then(function(msg) {
  285. fail('Should not receive pong from unexpected origin');
  286. });
  287. iframeChannel.send('ping', 'fizzbang');
  288. return goog.Promise.race([promise, goog.Timer.promise(500)])
  289. .thenAlways(function() { iframeChannel.dispose(); });
  290. });
  291. }
  292. function testWindowWontReceiveFromWrongOrigin() {
  293. if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
  294. return;
  295. }
  296. return createIframe('testdata/portchannel_wrong_origin_inner.html')
  297. .then(function(iframe) {
  298. var peerOrigin = window.location.protocol + '//' + window.location.host;
  299. var iframeChannel = goog.messaging.PortChannel.forEmbeddedWindow(
  300. iframe, peerOrigin, timer);
  301. var promise =
  302. registerService(iframeChannel, 'pong').then(function(msg) {
  303. fail('Should not receive pong from unexpected origin');
  304. });
  305. iframeChannel.send('ping', 'fizzbang');
  306. return goog.Promise.race([promise, goog.Timer.promise(500)])
  307. .thenAlways(function() { iframeChannel.dispose(); });
  308. });
  309. }
  310. /**
  311. * Assert that two HTML5 MessagePorts are entangled by posting messages from
  312. * each to the other.
  313. *
  314. * @param {!MessagePort} port1
  315. * @param {!MessagePort} port2
  316. * @return {!goog.Promise} Promise that settles when the assertion is complete.
  317. */
  318. function assertPortsEntangled(port1, port2) {
  319. var port2Promise =
  320. new goog.Promise(function(resolve, reject) { port2.onmessage = resolve; })
  321. .then(function(e) {
  322. assertEquals(
  323. 'First port 1 should send a message to port 2',
  324. 'port1 to port2', e.data);
  325. port2.postMessage('port2 to port1');
  326. });
  327. var port1Promise =
  328. new goog.Promise(function(resolve, reject) { port1.onmessage = resolve; })
  329. .then(function(e) {
  330. assertEquals(
  331. 'Then port 2 should respond to port 1', 'port2 to port1',
  332. e.data);
  333. });
  334. port1.postMessage('port1 to port2');
  335. return goog.Promise.all([port1Promise, port2Promise]);
  336. }
  337. /**
  338. * @param {string=} opt_url A URL to use for the iframe src (defaults to
  339. * "testdata/portchannel_inner.html").
  340. * @return {!goog.Promise<HTMLIframeElement>} A promise that resolves with the
  341. * loaded iframe.
  342. */
  343. function createIframe(opt_url) {
  344. var iframe = goog.dom.createDom(goog.dom.TagName.IFRAME, {
  345. style: 'display: none',
  346. src: opt_url || 'testdata/portchannel_inner.html'
  347. });
  348. return new goog
  349. .Promise(function(resolve, reject) {
  350. goog.events.listenOnce(iframe, goog.events.EventType.LOAD, resolve);
  351. goog.dom.appendChild(frameDiv, iframe);
  352. })
  353. .then(function(e) { return iframe.contentWindow; });
  354. }