// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Mock out the support module to avoid depending on the message loop. define("mojo/public/js/bindings/support", ["timer"], function(timer) { var waitingCallbacks = []; function WaitCookie(id) { this.id = id; } function asyncWait(handle, flags, callback) { var id = waitingCallbacks.length; waitingCallbacks.push(callback); return new WaitCookie(id); } function cancelWait(cookie) { waitingCallbacks[cookie.id] = null; } function numberOfWaitingCallbacks() { var count = 0; for (var i = 0; i < waitingCallbacks.length; ++i) { if (waitingCallbacks[i]) ++count; } return count; } function pumpOnce(result) { var callbacks = waitingCallbacks; waitingCallbacks = []; for (var i = 0; i < callbacks.length; ++i) { if (callbacks[i]) callbacks[i](result); } } // Queue up a pumpOnce call to execute after the stack unwinds. Use // this to trigger a pump after all Promises are executed. function queuePump(result) { timer.createOneShot(0, pumpOnce.bind(undefined, result)); } var exports = {}; exports.asyncWait = asyncWait; exports.cancelWait = cancelWait; exports.numberOfWaitingCallbacks = numberOfWaitingCallbacks; exports.pumpOnce = pumpOnce; exports.queuePump = queuePump; return exports; }); define([ "gin/test/expect", "mojo/public/js/bindings/support", "mojo/public/js/bindings/core", "mojo/public/js/bindings/connection", "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom", "mojo/public/interfaces/bindings/tests/sample_service.mojom", "mojo/apps/js/bindings/threading", "gc", ], function(expect, mockSupport, core, connection, sample_interfaces, sample_service, threading, gc) { testClientServer(); testWriteToClosedPipe(); testRequestResponse().then(function() { this.result = "PASS"; gc.collectGarbage(); // should not crash threading.quit(); }.bind(this)).catch(function(e) { this.result = "FAIL: " + (e.stack || e); threading.quit(); }.bind(this)); function testClientServer() { var receivedFrobinate = false; var receivedDidFrobinate = false; // ServiceImpl ------------------------------------------------------------- function ServiceImpl(peer) { this.peer = peer; } ServiceImpl.prototype = Object.create(sample_service.ServiceStub.prototype); ServiceImpl.prototype.frobinate = function(foo, baz, port) { receivedFrobinate = true; expect(foo.name).toBe("Example name"); expect(baz).toBeTruthy(); expect(core.close(port)).toBe(core.RESULT_OK); this.peer.didFrobinate(42); }; // ServiceImpl ------------------------------------------------------------- function ServiceClientImpl(peer) { this.peer = peer; } ServiceClientImpl.prototype = Object.create(sample_service.ServiceClientStub.prototype); ServiceClientImpl.prototype.didFrobinate = function(result) { receivedDidFrobinate = true; expect(result).toBe(42); }; var pipe = core.createMessagePipe(); var anotherPipe = core.createMessagePipe(); var sourcePipe = core.createMessagePipe(); var connection0 = new connection.Connection( pipe.handle0, ServiceImpl, sample_service.ServiceClientProxy); var connection1 = new connection.Connection( pipe.handle1, ServiceClientImpl, sample_service.ServiceProxy); var foo = new sample_service.Foo(); foo.bar = new sample_service.Bar(); foo.name = "Example name"; foo.source = sourcePipe.handle0; connection1.remote.frobinate(foo, true, anotherPipe.handle0); mockSupport.pumpOnce(core.RESULT_OK); expect(receivedFrobinate).toBeTruthy(); expect(receivedDidFrobinate).toBeTruthy(); connection0.close(); connection1.close(); expect(mockSupport.numberOfWaitingCallbacks()).toBe(0); // sourcePipe.handle0 was closed automatically when sent over IPC. expect(core.close(sourcePipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT); // sourcePipe.handle1 hasn't been closed yet. expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK); // anotherPipe.handle0 was closed automatically when sent over IPC. expect(core.close(anotherPipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT); // anotherPipe.handle1 hasn't been closed yet. expect(core.close(anotherPipe.handle1)).toBe(core.RESULT_OK); // The Connection object is responsible for closing these handles. expect(core.close(pipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT); expect(core.close(pipe.handle1)).toBe(core.RESULT_INVALID_ARGUMENT); } function testWriteToClosedPipe() { var pipe = core.createMessagePipe(); var connection1 = new connection.Connection( pipe.handle1, function() {}, sample_service.ServiceProxy); // Close the other end of the pipe. core.close(pipe.handle0); // Not observed yet because we haven't pumped events yet. expect(connection1.encounteredError()).toBeFalsy(); var foo = new sample_service.Foo(); foo.bar = new sample_service.Bar(); // TODO(darin): crbug.com/357043: pass null in place of |foo| here. connection1.remote.frobinate(foo, true, null); // Write failures are not reported. expect(connection1.encounteredError()).toBeFalsy(); // Pump events, and then we should start observing the closed pipe. mockSupport.pumpOnce(core.RESULT_OK); expect(connection1.encounteredError()).toBeTruthy(); connection1.close(); } function testRequestResponse() { // ProviderImpl ------------------------------------------------------------ function ProviderImpl(peer) { this.peer = peer; } ProviderImpl.prototype = Object.create(sample_interfaces.ProviderStub.prototype); ProviderImpl.prototype.echoString = function(a) { mockSupport.queuePump(core.RESULT_OK); return Promise.resolve({a: a}); }; ProviderImpl.prototype.echoStrings = function(a, b) { mockSupport.queuePump(core.RESULT_OK); return Promise.resolve({a: a, b: b}); }; // ProviderClientImpl ------------------------------------------------------ function ProviderClientImpl(peer) { this.peer = peer; } ProviderClientImpl.prototype = Object.create(sample_interfaces.ProviderClientStub.prototype); var pipe = core.createMessagePipe(); var connection0 = new connection.Connection( pipe.handle0, ProviderImpl, sample_interfaces.ProviderClientProxy); var connection1 = new connection.Connection( pipe.handle1, ProviderClientImpl, sample_interfaces.ProviderProxy); var origReadMessage = core.readMessage; // echoString mockSupport.queuePump(core.RESULT_OK); return connection1.remote.echoString("hello").then(function(response) { expect(response.a).toBe("hello"); }).then(function() { // echoStrings mockSupport.queuePump(core.RESULT_OK); return connection1.remote.echoStrings("hello", "world"); }).then(function(response) { expect(response.a).toBe("hello"); expect(response.b).toBe("world"); }).then(function() { // Mock a read failure, expect it to fail. core.readMessage = function() { return { result: core.RESULT_UNKNOWN }; }; mockSupport.queuePump(core.RESULT_OK); return connection1.remote.echoString("goodbye"); }).then(function() { throw Error("Expected echoString to fail."); }, function(error) { expect(error.message).toBe("Connection error: " + core.RESULT_UNKNOWN); // Clean up. core.readMessage = origReadMessage; }); } });