1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// Mock out the support module to avoid depending on the message loop. 6define("mojo/public/js/bindings/support", ["timer"], function(timer) { 7 var waitingCallbacks = []; 8 9 function WaitCookie(id) { 10 this.id = id; 11 } 12 13 function asyncWait(handle, flags, callback) { 14 var id = waitingCallbacks.length; 15 waitingCallbacks.push(callback); 16 return new WaitCookie(id); 17 } 18 19 function cancelWait(cookie) { 20 waitingCallbacks[cookie.id] = null; 21 } 22 23 function numberOfWaitingCallbacks() { 24 var count = 0; 25 for (var i = 0; i < waitingCallbacks.length; ++i) { 26 if (waitingCallbacks[i]) 27 ++count; 28 } 29 return count; 30 } 31 32 function pumpOnce(result) { 33 var callbacks = waitingCallbacks; 34 waitingCallbacks = []; 35 for (var i = 0; i < callbacks.length; ++i) { 36 if (callbacks[i]) 37 callbacks[i](result); 38 } 39 } 40 41 // Queue up a pumpOnce call to execute after the stack unwinds. Use 42 // this to trigger a pump after all Promises are executed. 43 function queuePump(result) { 44 timer.createOneShot(0, pumpOnce.bind(undefined, result)); 45 } 46 47 var exports = {}; 48 exports.asyncWait = asyncWait; 49 exports.cancelWait = cancelWait; 50 exports.numberOfWaitingCallbacks = numberOfWaitingCallbacks; 51 exports.pumpOnce = pumpOnce; 52 exports.queuePump = queuePump; 53 return exports; 54}); 55 56define([ 57 "gin/test/expect", 58 "mojo/public/js/bindings/support", 59 "mojo/public/js/bindings/core", 60 "mojo/public/js/bindings/connection", 61 "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom", 62 "mojo/public/interfaces/bindings/tests/sample_service.mojom", 63 "mojo/apps/js/bindings/threading", 64 "gc", 65], function(expect, 66 mockSupport, 67 core, 68 connection, 69 sample_interfaces, 70 sample_service, 71 threading, 72 gc) { 73 testClientServer(); 74 testWriteToClosedPipe(); 75 testRequestResponse().then(function() { 76 this.result = "PASS"; 77 gc.collectGarbage(); // should not crash 78 threading.quit(); 79 }.bind(this)).catch(function(e) { 80 this.result = "FAIL: " + (e.stack || e); 81 threading.quit(); 82 }.bind(this)); 83 84 function testClientServer() { 85 var receivedFrobinate = false; 86 var receivedDidFrobinate = false; 87 88 // ServiceImpl ------------------------------------------------------------- 89 90 function ServiceImpl(peer) { 91 this.peer = peer; 92 } 93 94 ServiceImpl.prototype = Object.create(sample_service.ServiceStub.prototype); 95 96 ServiceImpl.prototype.frobinate = function(foo, baz, port) { 97 receivedFrobinate = true; 98 99 expect(foo.name).toBe("Example name"); 100 expect(baz).toBeTruthy(); 101 expect(core.close(port)).toBe(core.RESULT_OK); 102 103 this.peer.didFrobinate(42); 104 }; 105 106 // ServiceImpl ------------------------------------------------------------- 107 108 function ServiceClientImpl(peer) { 109 this.peer = peer; 110 } 111 112 ServiceClientImpl.prototype = 113 Object.create(sample_service.ServiceClientStub.prototype); 114 115 ServiceClientImpl.prototype.didFrobinate = function(result) { 116 receivedDidFrobinate = true; 117 118 expect(result).toBe(42); 119 }; 120 121 var pipe = core.createMessagePipe(); 122 var anotherPipe = core.createMessagePipe(); 123 var sourcePipe = core.createMessagePipe(); 124 125 var connection0 = new connection.Connection( 126 pipe.handle0, ServiceImpl, sample_service.ServiceClientProxy); 127 128 var connection1 = new connection.Connection( 129 pipe.handle1, ServiceClientImpl, sample_service.ServiceProxy); 130 131 var foo = new sample_service.Foo(); 132 foo.bar = new sample_service.Bar(); 133 foo.name = "Example name"; 134 foo.source = sourcePipe.handle0; 135 connection1.remote.frobinate(foo, true, anotherPipe.handle0); 136 137 mockSupport.pumpOnce(core.RESULT_OK); 138 139 expect(receivedFrobinate).toBeTruthy(); 140 expect(receivedDidFrobinate).toBeTruthy(); 141 142 connection0.close(); 143 connection1.close(); 144 145 expect(mockSupport.numberOfWaitingCallbacks()).toBe(0); 146 147 // sourcePipe.handle0 was closed automatically when sent over IPC. 148 expect(core.close(sourcePipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT); 149 // sourcePipe.handle1 hasn't been closed yet. 150 expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK); 151 152 // anotherPipe.handle0 was closed automatically when sent over IPC. 153 expect(core.close(anotherPipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT); 154 // anotherPipe.handle1 hasn't been closed yet. 155 expect(core.close(anotherPipe.handle1)).toBe(core.RESULT_OK); 156 157 // The Connection object is responsible for closing these handles. 158 expect(core.close(pipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT); 159 expect(core.close(pipe.handle1)).toBe(core.RESULT_INVALID_ARGUMENT); 160 } 161 162 function testWriteToClosedPipe() { 163 var pipe = core.createMessagePipe(); 164 165 var connection1 = new connection.Connection( 166 pipe.handle1, function() {}, sample_service.ServiceProxy); 167 168 // Close the other end of the pipe. 169 core.close(pipe.handle0); 170 171 // Not observed yet because we haven't pumped events yet. 172 expect(connection1.encounteredError()).toBeFalsy(); 173 174 var foo = new sample_service.Foo(); 175 foo.bar = new sample_service.Bar(); 176 // TODO(darin): crbug.com/357043: pass null in place of |foo| here. 177 connection1.remote.frobinate(foo, true, null); 178 179 // Write failures are not reported. 180 expect(connection1.encounteredError()).toBeFalsy(); 181 182 // Pump events, and then we should start observing the closed pipe. 183 mockSupport.pumpOnce(core.RESULT_OK); 184 185 expect(connection1.encounteredError()).toBeTruthy(); 186 187 connection1.close(); 188 } 189 190 function testRequestResponse() { 191 192 // ProviderImpl ------------------------------------------------------------ 193 194 function ProviderImpl(peer) { 195 this.peer = peer; 196 } 197 198 ProviderImpl.prototype = 199 Object.create(sample_interfaces.ProviderStub.prototype); 200 201 ProviderImpl.prototype.echoString = function(a) { 202 mockSupport.queuePump(core.RESULT_OK); 203 return Promise.resolve({a: a}); 204 }; 205 206 ProviderImpl.prototype.echoStrings = function(a, b) { 207 mockSupport.queuePump(core.RESULT_OK); 208 return Promise.resolve({a: a, b: b}); 209 }; 210 211 // ProviderClientImpl ------------------------------------------------------ 212 213 function ProviderClientImpl(peer) { 214 this.peer = peer; 215 } 216 217 ProviderClientImpl.prototype = 218 Object.create(sample_interfaces.ProviderClientStub.prototype); 219 220 var pipe = core.createMessagePipe(); 221 222 var connection0 = new connection.Connection( 223 pipe.handle0, ProviderImpl, sample_interfaces.ProviderClientProxy); 224 225 var connection1 = new connection.Connection( 226 pipe.handle1, ProviderClientImpl, sample_interfaces.ProviderProxy); 227 228 var origReadMessage = core.readMessage; 229 // echoString 230 mockSupport.queuePump(core.RESULT_OK); 231 return connection1.remote.echoString("hello").then(function(response) { 232 expect(response.a).toBe("hello"); 233 }).then(function() { 234 // echoStrings 235 mockSupport.queuePump(core.RESULT_OK); 236 return connection1.remote.echoStrings("hello", "world"); 237 }).then(function(response) { 238 expect(response.a).toBe("hello"); 239 expect(response.b).toBe("world"); 240 }).then(function() { 241 // Mock a read failure, expect it to fail. 242 core.readMessage = function() { 243 return { result: core.RESULT_UNKNOWN }; 244 }; 245 mockSupport.queuePump(core.RESULT_OK); 246 return connection1.remote.echoString("goodbye"); 247 }).then(function() { 248 throw Error("Expected echoString to fail."); 249 }, function(error) { 250 expect(error.message).toBe("Connection error: " + core.RESULT_UNKNOWN); 251 252 // Clean up. 253 core.readMessage = origReadMessage; 254 }); 255 } 256}); 257