1// Copyright 2017 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(function() {
6  var internal = mojo.internal;
7
8  // ---------------------------------------------------------------------------
9
10  // |output| could be an interface pointer, InterfacePtrInfo or
11  // AssociatedInterfacePtrInfo.
12  function makeRequest(output) {
13    if (output instanceof mojo.AssociatedInterfacePtrInfo) {
14      var {handle0, handle1} = internal.createPairPendingAssociation();
15      output.interfaceEndpointHandle = handle0;
16      output.version = 0;
17
18      return new mojo.AssociatedInterfaceRequest(handle1);
19    }
20
21    if (output instanceof mojo.InterfacePtrInfo) {
22      var pipe = Mojo.createMessagePipe();
23      output.handle = pipe.handle0;
24      output.version = 0;
25
26      return new mojo.InterfaceRequest(pipe.handle1);
27    }
28
29    var pipe = Mojo.createMessagePipe();
30    output.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0));
31    return new mojo.InterfaceRequest(pipe.handle1);
32  }
33
34  // ---------------------------------------------------------------------------
35
36  // Operations used to setup/configure an interface pointer. Exposed as the
37  // |ptr| field of generated interface pointer classes.
38  // |ptrInfoOrHandle| could be omitted and passed into bind() later.
39  function InterfacePtrController(interfaceType, ptrInfoOrHandle) {
40    this.version = 0;
41
42    this.interfaceType_ = interfaceType;
43    this.router_ = null;
44    this.interfaceEndpointClient_ = null;
45    this.proxy_ = null;
46
47    // |router_| and |interfaceEndpointClient_| are lazily initialized.
48    // |handle_| is valid between bind() and
49    // the initialization of |router_| and |interfaceEndpointClient_|.
50    this.handle_ = null;
51
52    if (ptrInfoOrHandle)
53      this.bind(ptrInfoOrHandle);
54  }
55
56  InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) {
57    this.reset();
58
59    if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) {
60      this.version = ptrInfoOrHandle.version;
61      this.handle_ = ptrInfoOrHandle.handle;
62    } else {
63      this.handle_ = ptrInfoOrHandle;
64    }
65  };
66
67  InterfacePtrController.prototype.isBound = function() {
68    return this.interfaceEndpointClient_ !== null || this.handle_ !== null;
69  };
70
71  // Although users could just discard the object, reset() closes the pipe
72  // immediately.
73  InterfacePtrController.prototype.reset = function() {
74    this.version = 0;
75    if (this.interfaceEndpointClient_) {
76      this.interfaceEndpointClient_.close();
77      this.interfaceEndpointClient_ = null;
78    }
79    if (this.router_) {
80      this.router_.close();
81      this.router_ = null;
82
83      this.proxy_ = null;
84    }
85    if (this.handle_) {
86      this.handle_.close();
87      this.handle_ = null;
88    }
89  };
90
91  InterfacePtrController.prototype.resetWithReason = function(reason) {
92    if (this.isBound()) {
93      this.configureProxyIfNecessary_();
94      this.interfaceEndpointClient_.close(reason);
95      this.interfaceEndpointClient_ = null;
96    }
97    this.reset();
98  };
99
100  InterfacePtrController.prototype.setConnectionErrorHandler = function(
101      callback) {
102    if (!this.isBound())
103      throw new Error("Cannot set connection error handler if not bound.");
104
105    this.configureProxyIfNecessary_();
106    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
107  };
108
109  InterfacePtrController.prototype.passInterface = function() {
110    var result;
111    if (this.router_) {
112      // TODO(yzshen): Fix Router interface to support extracting handle.
113      result = new mojo.InterfacePtrInfo(
114          this.router_.connector_.handle_, this.version);
115      this.router_.connector_.handle_ = null;
116    } else {
117      // This also handles the case when this object is not bound.
118      result = new mojo.InterfacePtrInfo(this.handle_, this.version);
119      this.handle_ = null;
120    }
121
122    this.reset();
123    return result;
124  };
125
126  InterfacePtrController.prototype.getProxy = function() {
127    this.configureProxyIfNecessary_();
128    return this.proxy_;
129  };
130
131  InterfacePtrController.prototype.configureProxyIfNecessary_ = function() {
132    if (!this.handle_)
133      return;
134
135    this.router_ = new internal.Router(this.handle_, true);
136    this.handle_ = null;
137
138    this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
139        this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId));
140
141    this.interfaceEndpointClient_ .setPayloadValidators([
142        this.interfaceType_.validateResponse]);
143    this.proxy_ = new this.interfaceType_.proxyClass(
144        this.interfaceEndpointClient_);
145  };
146
147  InterfacePtrController.prototype.queryVersion = function() {
148    function onQueryVersion(version) {
149      this.version = version;
150      return version;
151    }
152
153    this.configureProxyIfNecessary_();
154    return this.interfaceEndpointClient_.queryVersion().then(
155      onQueryVersion.bind(this));
156  };
157
158  InterfacePtrController.prototype.requireVersion = function(version) {
159    this.configureProxyIfNecessary_();
160
161    if (this.version >= version) {
162      return;
163    }
164    this.version = version;
165    this.interfaceEndpointClient_.requireVersion(version);
166  };
167
168  // ---------------------------------------------------------------------------
169
170  // |request| could be omitted and passed into bind() later.
171  //
172  // Example:
173  //
174  //    // FooImpl implements mojom.Foo.
175  //    function FooImpl() { ... }
176  //    FooImpl.prototype.fooMethod1 = function() { ... }
177  //    FooImpl.prototype.fooMethod2 = function() { ... }
178  //
179  //    var fooPtr = new mojom.FooPtr();
180  //    var request = makeRequest(fooPtr);
181  //    var binding = new Binding(mojom.Foo, new FooImpl(), request);
182  //    fooPtr.fooMethod1();
183  function Binding(interfaceType, impl, requestOrHandle) {
184    this.interfaceType_ = interfaceType;
185    this.impl_ = impl;
186    this.router_ = null;
187    this.interfaceEndpointClient_ = null;
188    this.stub_ = null;
189
190    if (requestOrHandle)
191      this.bind(requestOrHandle);
192  }
193
194  Binding.prototype.isBound = function() {
195    return this.router_ !== null;
196  };
197
198  Binding.prototype.createInterfacePtrAndBind = function() {
199    var ptr = new this.interfaceType_.ptrClass();
200    // TODO(yzshen): Set the version of the interface pointer.
201    this.bind(makeRequest(ptr));
202    return ptr;
203  };
204
205  Binding.prototype.bind = function(requestOrHandle) {
206    this.close();
207
208    var handle = requestOrHandle instanceof mojo.InterfaceRequest ?
209        requestOrHandle.handle : requestOrHandle;
210    if (!(handle instanceof MojoHandle))
211      return;
212
213    this.router_ = new internal.Router(handle);
214
215    this.stub_ = new this.interfaceType_.stubClass(this.impl_);
216    this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
217        this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId),
218        this.stub_, this.interfaceType_.kVersion);
219
220    this.interfaceEndpointClient_ .setPayloadValidators([
221        this.interfaceType_.validateRequest]);
222  };
223
224  Binding.prototype.close = function() {
225    if (!this.isBound())
226      return;
227
228    if (this.interfaceEndpointClient_) {
229      this.interfaceEndpointClient_.close();
230      this.interfaceEndpointClient_ = null;
231    }
232
233    this.router_.close();
234    this.router_ = null;
235    this.stub_ = null;
236  };
237
238  Binding.prototype.closeWithReason = function(reason) {
239    if (this.interfaceEndpointClient_) {
240      this.interfaceEndpointClient_.close(reason);
241      this.interfaceEndpointClient_ = null;
242    }
243    this.close();
244  };
245
246  Binding.prototype.setConnectionErrorHandler = function(callback) {
247    if (!this.isBound()) {
248      throw new Error("Cannot set connection error handler if not bound.");
249    }
250    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
251  };
252
253  Binding.prototype.unbind = function() {
254    if (!this.isBound())
255      return new mojo.InterfaceRequest(null);
256
257    var result = new mojo.InterfaceRequest(this.router_.connector_.handle_);
258    this.router_.connector_.handle_ = null;
259    this.close();
260    return result;
261  };
262
263  // ---------------------------------------------------------------------------
264
265  function BindingSetEntry(bindingSet, interfaceType, bindingType, impl,
266      requestOrHandle, bindingId) {
267    this.bindingSet_ = bindingSet;
268    this.bindingId_ = bindingId;
269    this.binding_ = new bindingType(interfaceType, impl,
270        requestOrHandle);
271
272    this.binding_.setConnectionErrorHandler(function(reason) {
273      this.bindingSet_.onConnectionError(bindingId, reason);
274    }.bind(this));
275  }
276
277  BindingSetEntry.prototype.close = function() {
278    this.binding_.close();
279  };
280
281  function BindingSet(interfaceType) {
282    this.interfaceType_ = interfaceType;
283    this.nextBindingId_ = 0;
284    this.bindings_ = new Map();
285    this.errorHandler_ = null;
286    this.bindingType_ = Binding;
287  }
288
289  BindingSet.prototype.isEmpty = function() {
290    return this.bindings_.size == 0;
291  };
292
293  BindingSet.prototype.addBinding = function(impl, requestOrHandle) {
294    this.bindings_.set(
295        this.nextBindingId_,
296        new BindingSetEntry(this, this.interfaceType_, this.bindingType_, impl,
297            requestOrHandle, this.nextBindingId_));
298    ++this.nextBindingId_;
299  };
300
301  BindingSet.prototype.closeAllBindings = function() {
302    for (var entry of this.bindings_.values())
303      entry.close();
304    this.bindings_.clear();
305  };
306
307  BindingSet.prototype.setConnectionErrorHandler = function(callback) {
308    this.errorHandler_ = callback;
309  };
310
311  BindingSet.prototype.onConnectionError = function(bindingId, reason) {
312    this.bindings_.delete(bindingId);
313
314    if (this.errorHandler_)
315      this.errorHandler_(reason);
316  };
317
318  // ---------------------------------------------------------------------------
319
320  // Operations used to setup/configure an associated interface pointer.
321  // Exposed as |ptr| field of generated associated interface pointer classes.
322  // |associatedPtrInfo| could be omitted and passed into bind() later.
323  //
324  // Example:
325  //    // IntegerSenderImpl implements mojom.IntegerSender
326  //    function IntegerSenderImpl() { ... }
327  //    IntegerSenderImpl.prototype.echo = function() { ... }
328  //
329  //    // IntegerSenderConnectionImpl implements mojom.IntegerSenderConnection
330  //    function IntegerSenderConnectionImpl() {
331  //      this.senderBinding_ = null;
332  //    }
333  //    IntegerSenderConnectionImpl.prototype.getSender = function(
334  //        associatedRequest) {
335  //      this.senderBinding_ = new AssociatedBinding(mojom.IntegerSender,
336  //          new IntegerSenderImpl(),
337  //          associatedRequest);
338  //    }
339  //
340  //    var integerSenderConnection = new mojom.IntegerSenderConnectionPtr();
341  //    var integerSenderConnectionBinding = new Binding(
342  //        mojom.IntegerSenderConnection,
343  //        new IntegerSenderConnectionImpl(),
344  //        mojo.makeRequest(integerSenderConnection));
345  //
346  //    // A locally-created associated interface pointer can only be used to
347  //    // make calls when the corresponding associated request is sent over
348  //    // another interface (either the master interface or another
349  //    // associated interface).
350  //    var associatedInterfacePtrInfo = new AssociatedInterfacePtrInfo();
351  //    var associatedRequest = makeRequest(interfacePtrInfo);
352  //
353  //    integerSenderConnection.getSender(associatedRequest);
354  //
355  //    // Create an associated interface and bind the associated handle.
356  //    var integerSender = new mojom.AssociatedIntegerSenderPtr();
357  //    integerSender.ptr.bind(associatedInterfacePtrInfo);
358  //    integerSender.echo();
359
360  function AssociatedInterfacePtrController(interfaceType, associatedPtrInfo) {
361    this.version = 0;
362
363    this.interfaceType_ = interfaceType;
364    this.interfaceEndpointClient_ = null;
365    this.proxy_ = null;
366
367    if (associatedPtrInfo) {
368      this.bind(associatedPtrInfo);
369    }
370  }
371
372  AssociatedInterfacePtrController.prototype.bind = function(
373      associatedPtrInfo) {
374    this.reset();
375    this.version = associatedPtrInfo.version;
376
377    this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
378        associatedPtrInfo.interfaceEndpointHandle);
379
380    this.interfaceEndpointClient_ .setPayloadValidators([
381        this.interfaceType_.validateResponse]);
382    this.proxy_ = new this.interfaceType_.proxyClass(
383        this.interfaceEndpointClient_);
384  };
385
386  AssociatedInterfacePtrController.prototype.isBound = function() {
387    return this.interfaceEndpointClient_ !== null;
388  };
389
390  AssociatedInterfacePtrController.prototype.reset = function() {
391    this.version = 0;
392    if (this.interfaceEndpointClient_) {
393      this.interfaceEndpointClient_.close();
394      this.interfaceEndpointClient_ = null;
395    }
396    if (this.proxy_) {
397      this.proxy_ = null;
398    }
399  };
400
401  AssociatedInterfacePtrController.prototype.resetWithReason = function(
402      reason) {
403    if (this.isBound()) {
404      this.interfaceEndpointClient_.close(reason);
405      this.interfaceEndpointClient_ = null;
406    }
407    this.reset();
408  };
409
410  // Indicates whether an error has been encountered. If true, method calls
411  // on this interface will be dropped (and may already have been dropped).
412  AssociatedInterfacePtrController.prototype.getEncounteredError = function() {
413    return this.interfaceEndpointClient_ ?
414        this.interfaceEndpointClient_.getEncounteredError() : false;
415  };
416
417  AssociatedInterfacePtrController.prototype.setConnectionErrorHandler =
418      function(callback) {
419    if (!this.isBound()) {
420      throw new Error("Cannot set connection error handler if not bound.");
421    }
422
423    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
424  };
425
426  AssociatedInterfacePtrController.prototype.passInterface = function() {
427    if (!this.isBound()) {
428      return new mojo.AssociatedInterfacePtrInfo(null);
429    }
430
431    var result = new mojo.AssociatedInterfacePtrInfo(
432        this.interfaceEndpointClient_.passHandle(), this.version);
433    this.reset();
434    return result;
435  };
436
437  AssociatedInterfacePtrController.prototype.getProxy = function() {
438    return this.proxy_;
439  };
440
441  AssociatedInterfacePtrController.prototype.queryVersion = function() {
442    function onQueryVersion(version) {
443      this.version = version;
444      return version;
445    }
446
447    return this.interfaceEndpointClient_.queryVersion().then(
448      onQueryVersion.bind(this));
449  };
450
451  AssociatedInterfacePtrController.prototype.requireVersion = function(
452      version) {
453    if (this.version >= version) {
454      return;
455    }
456    this.version = version;
457    this.interfaceEndpointClient_.requireVersion(version);
458  };
459
460  // ---------------------------------------------------------------------------
461
462  // |associatedInterfaceRequest| could be omitted and passed into bind()
463  // later.
464  function AssociatedBinding(interfaceType, impl, associatedInterfaceRequest) {
465    this.interfaceType_ = interfaceType;
466    this.impl_ = impl;
467    this.interfaceEndpointClient_ = null;
468    this.stub_ = null;
469
470    if (associatedInterfaceRequest) {
471      this.bind(associatedInterfaceRequest);
472    }
473  }
474
475  AssociatedBinding.prototype.isBound = function() {
476    return this.interfaceEndpointClient_ !== null;
477  };
478
479  AssociatedBinding.prototype.bind = function(associatedInterfaceRequest) {
480    this.close();
481
482    this.stub_ = new this.interfaceType_.stubClass(this.impl_);
483    this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
484        associatedInterfaceRequest.interfaceEndpointHandle, this.stub_,
485        this.interfaceType_.kVersion);
486
487    this.interfaceEndpointClient_ .setPayloadValidators([
488        this.interfaceType_.validateRequest]);
489  };
490
491
492  AssociatedBinding.prototype.close = function() {
493    if (!this.isBound()) {
494      return;
495    }
496
497    if (this.interfaceEndpointClient_) {
498      this.interfaceEndpointClient_.close();
499      this.interfaceEndpointClient_ = null;
500    }
501
502    this.stub_ = null;
503  };
504
505  AssociatedBinding.prototype.closeWithReason = function(reason) {
506    if (this.interfaceEndpointClient_) {
507      this.interfaceEndpointClient_.close(reason);
508      this.interfaceEndpointClient_ = null;
509    }
510    this.close();
511  };
512
513  AssociatedBinding.prototype.setConnectionErrorHandler = function(callback) {
514    if (!this.isBound()) {
515      throw new Error("Cannot set connection error handler if not bound.");
516    }
517    this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
518  };
519
520  AssociatedBinding.prototype.unbind = function() {
521    if (!this.isBound()) {
522      return new mojo.AssociatedInterfaceRequest(null);
523    }
524
525    var result = new mojo.AssociatedInterfaceRequest(
526        this.interfaceEndpointClient_.passHandle());
527    this.close();
528    return result;
529  };
530
531  // ---------------------------------------------------------------------------
532
533  function AssociatedBindingSet(interfaceType) {
534    mojo.BindingSet.call(this, interfaceType);
535    this.bindingType_ = AssociatedBinding;
536  }
537
538  AssociatedBindingSet.prototype = Object.create(BindingSet.prototype);
539  AssociatedBindingSet.prototype.constructor = AssociatedBindingSet;
540
541  mojo.makeRequest = makeRequest;
542  mojo.AssociatedInterfacePtrController = AssociatedInterfacePtrController;
543  mojo.AssociatedBinding = AssociatedBinding;
544  mojo.AssociatedBindingSet = AssociatedBindingSet;
545  mojo.Binding = Binding;
546  mojo.BindingSet = BindingSet;
547  mojo.InterfacePtrController = InterfacePtrController;
548})();
549