// Copyright 2017 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. (function() { var internal = mojo.internal; // --------------------------------------------------------------------------- // |output| could be an interface pointer, InterfacePtrInfo or // AssociatedInterfacePtrInfo. function makeRequest(output) { if (output instanceof mojo.AssociatedInterfacePtrInfo) { var {handle0, handle1} = internal.createPairPendingAssociation(); output.interfaceEndpointHandle = handle0; output.version = 0; return new mojo.AssociatedInterfaceRequest(handle1); } if (output instanceof mojo.InterfacePtrInfo) { var pipe = Mojo.createMessagePipe(); output.handle = pipe.handle0; output.version = 0; return new mojo.InterfaceRequest(pipe.handle1); } var pipe = Mojo.createMessagePipe(); output.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0)); return new mojo.InterfaceRequest(pipe.handle1); } // --------------------------------------------------------------------------- // Operations used to setup/configure an interface pointer. Exposed as the // |ptr| field of generated interface pointer classes. // |ptrInfoOrHandle| could be omitted and passed into bind() later. function InterfacePtrController(interfaceType, ptrInfoOrHandle) { this.version = 0; this.interfaceType_ = interfaceType; this.router_ = null; this.interfaceEndpointClient_ = null; this.proxy_ = null; // |router_| and |interfaceEndpointClient_| are lazily initialized. // |handle_| is valid between bind() and // the initialization of |router_| and |interfaceEndpointClient_|. this.handle_ = null; if (ptrInfoOrHandle) this.bind(ptrInfoOrHandle); } InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) { this.reset(); if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) { this.version = ptrInfoOrHandle.version; this.handle_ = ptrInfoOrHandle.handle; } else { this.handle_ = ptrInfoOrHandle; } }; InterfacePtrController.prototype.isBound = function() { return this.interfaceEndpointClient_ !== null || this.handle_ !== null; }; // Although users could just discard the object, reset() closes the pipe // immediately. InterfacePtrController.prototype.reset = function() { this.version = 0; if (this.interfaceEndpointClient_) { this.interfaceEndpointClient_.close(); this.interfaceEndpointClient_ = null; } if (this.router_) { this.router_.close(); this.router_ = null; this.proxy_ = null; } if (this.handle_) { this.handle_.close(); this.handle_ = null; } }; InterfacePtrController.prototype.resetWithReason = function(reason) { if (this.isBound()) { this.configureProxyIfNecessary_(); this.interfaceEndpointClient_.close(reason); this.interfaceEndpointClient_ = null; } this.reset(); }; InterfacePtrController.prototype.setConnectionErrorHandler = function( callback) { if (!this.isBound()) throw new Error("Cannot set connection error handler if not bound."); this.configureProxyIfNecessary_(); this.interfaceEndpointClient_.setConnectionErrorHandler(callback); }; InterfacePtrController.prototype.passInterface = function() { var result; if (this.router_) { // TODO(yzshen): Fix Router interface to support extracting handle. result = new mojo.InterfacePtrInfo( this.router_.connector_.handle_, this.version); this.router_.connector_.handle_ = null; } else { // This also handles the case when this object is not bound. result = new mojo.InterfacePtrInfo(this.handle_, this.version); this.handle_ = null; } this.reset(); return result; }; InterfacePtrController.prototype.getProxy = function() { this.configureProxyIfNecessary_(); return this.proxy_; }; InterfacePtrController.prototype.configureProxyIfNecessary_ = function() { if (!this.handle_) return; this.router_ = new internal.Router(this.handle_, true); this.handle_ = null; this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient( this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId)); this.interfaceEndpointClient_ .setPayloadValidators([ this.interfaceType_.validateResponse]); this.proxy_ = new this.interfaceType_.proxyClass( this.interfaceEndpointClient_); }; InterfacePtrController.prototype.queryVersion = function() { function onQueryVersion(version) { this.version = version; return version; } this.configureProxyIfNecessary_(); return this.interfaceEndpointClient_.queryVersion().then( onQueryVersion.bind(this)); }; InterfacePtrController.prototype.requireVersion = function(version) { this.configureProxyIfNecessary_(); if (this.version >= version) { return; } this.version = version; this.interfaceEndpointClient_.requireVersion(version); }; // --------------------------------------------------------------------------- // |request| could be omitted and passed into bind() later. // // Example: // // // FooImpl implements mojom.Foo. // function FooImpl() { ... } // FooImpl.prototype.fooMethod1 = function() { ... } // FooImpl.prototype.fooMethod2 = function() { ... } // // var fooPtr = new mojom.FooPtr(); // var request = makeRequest(fooPtr); // var binding = new Binding(mojom.Foo, new FooImpl(), request); // fooPtr.fooMethod1(); function Binding(interfaceType, impl, requestOrHandle) { this.interfaceType_ = interfaceType; this.impl_ = impl; this.router_ = null; this.interfaceEndpointClient_ = null; this.stub_ = null; if (requestOrHandle) this.bind(requestOrHandle); } Binding.prototype.isBound = function() { return this.router_ !== null; }; Binding.prototype.createInterfacePtrAndBind = function() { var ptr = new this.interfaceType_.ptrClass(); // TODO(yzshen): Set the version of the interface pointer. this.bind(makeRequest(ptr)); return ptr; }; Binding.prototype.bind = function(requestOrHandle) { this.close(); var handle = requestOrHandle instanceof mojo.InterfaceRequest ? requestOrHandle.handle : requestOrHandle; if (!(handle instanceof MojoHandle)) return; this.router_ = new internal.Router(handle); this.stub_ = new this.interfaceType_.stubClass(this.impl_); this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient( this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId), this.stub_, this.interfaceType_.kVersion); this.interfaceEndpointClient_ .setPayloadValidators([ this.interfaceType_.validateRequest]); }; Binding.prototype.close = function() { if (!this.isBound()) return; if (this.interfaceEndpointClient_) { this.interfaceEndpointClient_.close(); this.interfaceEndpointClient_ = null; } this.router_.close(); this.router_ = null; this.stub_ = null; }; Binding.prototype.closeWithReason = function(reason) { if (this.interfaceEndpointClient_) { this.interfaceEndpointClient_.close(reason); this.interfaceEndpointClient_ = null; } this.close(); }; Binding.prototype.setConnectionErrorHandler = function(callback) { if (!this.isBound()) { throw new Error("Cannot set connection error handler if not bound."); } this.interfaceEndpointClient_.setConnectionErrorHandler(callback); }; Binding.prototype.unbind = function() { if (!this.isBound()) return new mojo.InterfaceRequest(null); var result = new mojo.InterfaceRequest(this.router_.connector_.handle_); this.router_.connector_.handle_ = null; this.close(); return result; }; // --------------------------------------------------------------------------- function BindingSetEntry(bindingSet, interfaceType, bindingType, impl, requestOrHandle, bindingId) { this.bindingSet_ = bindingSet; this.bindingId_ = bindingId; this.binding_ = new bindingType(interfaceType, impl, requestOrHandle); this.binding_.setConnectionErrorHandler(function(reason) { this.bindingSet_.onConnectionError(bindingId, reason); }.bind(this)); } BindingSetEntry.prototype.close = function() { this.binding_.close(); }; function BindingSet(interfaceType) { this.interfaceType_ = interfaceType; this.nextBindingId_ = 0; this.bindings_ = new Map(); this.errorHandler_ = null; this.bindingType_ = Binding; } BindingSet.prototype.isEmpty = function() { return this.bindings_.size == 0; }; BindingSet.prototype.addBinding = function(impl, requestOrHandle) { this.bindings_.set( this.nextBindingId_, new BindingSetEntry(this, this.interfaceType_, this.bindingType_, impl, requestOrHandle, this.nextBindingId_)); ++this.nextBindingId_; }; BindingSet.prototype.closeAllBindings = function() { for (var entry of this.bindings_.values()) entry.close(); this.bindings_.clear(); }; BindingSet.prototype.setConnectionErrorHandler = function(callback) { this.errorHandler_ = callback; }; BindingSet.prototype.onConnectionError = function(bindingId, reason) { this.bindings_.delete(bindingId); if (this.errorHandler_) this.errorHandler_(reason); }; // --------------------------------------------------------------------------- // Operations used to setup/configure an associated interface pointer. // Exposed as |ptr| field of generated associated interface pointer classes. // |associatedPtrInfo| could be omitted and passed into bind() later. // // Example: // // IntegerSenderImpl implements mojom.IntegerSender // function IntegerSenderImpl() { ... } // IntegerSenderImpl.prototype.echo = function() { ... } // // // IntegerSenderConnectionImpl implements mojom.IntegerSenderConnection // function IntegerSenderConnectionImpl() { // this.senderBinding_ = null; // } // IntegerSenderConnectionImpl.prototype.getSender = function( // associatedRequest) { // this.senderBinding_ = new AssociatedBinding(mojom.IntegerSender, // new IntegerSenderImpl(), // associatedRequest); // } // // var integerSenderConnection = new mojom.IntegerSenderConnectionPtr(); // var integerSenderConnectionBinding = new Binding( // mojom.IntegerSenderConnection, // new IntegerSenderConnectionImpl(), // mojo.makeRequest(integerSenderConnection)); // // // A locally-created associated interface pointer can only be used to // // make calls when the corresponding associated request is sent over // // another interface (either the master interface or another // // associated interface). // var associatedInterfacePtrInfo = new AssociatedInterfacePtrInfo(); // var associatedRequest = makeRequest(interfacePtrInfo); // // integerSenderConnection.getSender(associatedRequest); // // // Create an associated interface and bind the associated handle. // var integerSender = new mojom.AssociatedIntegerSenderPtr(); // integerSender.ptr.bind(associatedInterfacePtrInfo); // integerSender.echo(); function AssociatedInterfacePtrController(interfaceType, associatedPtrInfo) { this.version = 0; this.interfaceType_ = interfaceType; this.interfaceEndpointClient_ = null; this.proxy_ = null; if (associatedPtrInfo) { this.bind(associatedPtrInfo); } } AssociatedInterfacePtrController.prototype.bind = function( associatedPtrInfo) { this.reset(); this.version = associatedPtrInfo.version; this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient( associatedPtrInfo.interfaceEndpointHandle); this.interfaceEndpointClient_ .setPayloadValidators([ this.interfaceType_.validateResponse]); this.proxy_ = new this.interfaceType_.proxyClass( this.interfaceEndpointClient_); }; AssociatedInterfacePtrController.prototype.isBound = function() { return this.interfaceEndpointClient_ !== null; }; AssociatedInterfacePtrController.prototype.reset = function() { this.version = 0; if (this.interfaceEndpointClient_) { this.interfaceEndpointClient_.close(); this.interfaceEndpointClient_ = null; } if (this.proxy_) { this.proxy_ = null; } }; AssociatedInterfacePtrController.prototype.resetWithReason = function( reason) { if (this.isBound()) { this.interfaceEndpointClient_.close(reason); this.interfaceEndpointClient_ = null; } this.reset(); }; // Indicates whether an error has been encountered. If true, method calls // on this interface will be dropped (and may already have been dropped). AssociatedInterfacePtrController.prototype.getEncounteredError = function() { return this.interfaceEndpointClient_ ? this.interfaceEndpointClient_.getEncounteredError() : false; }; AssociatedInterfacePtrController.prototype.setConnectionErrorHandler = function(callback) { if (!this.isBound()) { throw new Error("Cannot set connection error handler if not bound."); } this.interfaceEndpointClient_.setConnectionErrorHandler(callback); }; AssociatedInterfacePtrController.prototype.passInterface = function() { if (!this.isBound()) { return new mojo.AssociatedInterfacePtrInfo(null); } var result = new mojo.AssociatedInterfacePtrInfo( this.interfaceEndpointClient_.passHandle(), this.version); this.reset(); return result; }; AssociatedInterfacePtrController.prototype.getProxy = function() { return this.proxy_; }; AssociatedInterfacePtrController.prototype.queryVersion = function() { function onQueryVersion(version) { this.version = version; return version; } return this.interfaceEndpointClient_.queryVersion().then( onQueryVersion.bind(this)); }; AssociatedInterfacePtrController.prototype.requireVersion = function( version) { if (this.version >= version) { return; } this.version = version; this.interfaceEndpointClient_.requireVersion(version); }; // --------------------------------------------------------------------------- // |associatedInterfaceRequest| could be omitted and passed into bind() // later. function AssociatedBinding(interfaceType, impl, associatedInterfaceRequest) { this.interfaceType_ = interfaceType; this.impl_ = impl; this.interfaceEndpointClient_ = null; this.stub_ = null; if (associatedInterfaceRequest) { this.bind(associatedInterfaceRequest); } } AssociatedBinding.prototype.isBound = function() { return this.interfaceEndpointClient_ !== null; }; AssociatedBinding.prototype.bind = function(associatedInterfaceRequest) { this.close(); this.stub_ = new this.interfaceType_.stubClass(this.impl_); this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient( associatedInterfaceRequest.interfaceEndpointHandle, this.stub_, this.interfaceType_.kVersion); this.interfaceEndpointClient_ .setPayloadValidators([ this.interfaceType_.validateRequest]); }; AssociatedBinding.prototype.close = function() { if (!this.isBound()) { return; } if (this.interfaceEndpointClient_) { this.interfaceEndpointClient_.close(); this.interfaceEndpointClient_ = null; } this.stub_ = null; }; AssociatedBinding.prototype.closeWithReason = function(reason) { if (this.interfaceEndpointClient_) { this.interfaceEndpointClient_.close(reason); this.interfaceEndpointClient_ = null; } this.close(); }; AssociatedBinding.prototype.setConnectionErrorHandler = function(callback) { if (!this.isBound()) { throw new Error("Cannot set connection error handler if not bound."); } this.interfaceEndpointClient_.setConnectionErrorHandler(callback); }; AssociatedBinding.prototype.unbind = function() { if (!this.isBound()) { return new mojo.AssociatedInterfaceRequest(null); } var result = new mojo.AssociatedInterfaceRequest( this.interfaceEndpointClient_.passHandle()); this.close(); return result; }; // --------------------------------------------------------------------------- function AssociatedBindingSet(interfaceType) { mojo.BindingSet.call(this, interfaceType); this.bindingType_ = AssociatedBinding; } AssociatedBindingSet.prototype = Object.create(BindingSet.prototype); AssociatedBindingSet.prototype.constructor = AssociatedBindingSet; mojo.makeRequest = makeRequest; mojo.AssociatedInterfacePtrController = AssociatedInterfacePtrController; mojo.AssociatedBinding = AssociatedBinding; mojo.AssociatedBindingSet = AssociatedBindingSet; mojo.Binding = Binding; mojo.BindingSet = BindingSet; mojo.InterfacePtrController = InterfacePtrController; })();