Home | History | Annotate | Download | only in bindings
      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.
      6 define("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 
     56 define([
     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