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 var AssociationEvent = { 9 // The interface has been associated with a message pipe. 10 ASSOCIATED: 'associated', 11 // The peer of this object has been closed before association. 12 PEER_CLOSED_BEFORE_ASSOCIATION: 'peer_closed_before_association' 13 }; 14 15 function State(interfaceId, associatedGroupController) { 16 if (interfaceId === undefined) { 17 interfaceId = internal.kInvalidInterfaceId; 18 } 19 20 this.interfaceId = interfaceId; 21 this.associatedGroupController = associatedGroupController; 22 this.pendingAssociation = false; 23 this.disconnectReason = null; 24 this.peerState_ = null; 25 this.associationEventHandler_ = null; 26 } 27 28 State.prototype.initPendingState = function(peer) { 29 this.pendingAssociation = true; 30 this.peerState_ = peer; 31 }; 32 33 State.prototype.isValid = function() { 34 return this.pendingAssociation || 35 internal.isValidInterfaceId(this.interfaceId); 36 }; 37 38 State.prototype.close = function(disconnectReason) { 39 var cachedGroupController; 40 var cachedPeerState; 41 var cachedId = internal.kInvalidInterfaceId; 42 43 if (!this.pendingAssociation) { 44 if (internal.isValidInterfaceId(this.interfaceId)) { 45 cachedGroupController = this.associatedGroupController; 46 this.associatedGroupController = null; 47 cachedId = this.interfaceId; 48 this.interfaceId = internal.kInvalidInterfaceId; 49 } 50 } else { 51 this.pendingAssociation = false; 52 cachedPeerState = this.peerState_; 53 this.peerState_ = null; 54 } 55 56 if (cachedGroupController) { 57 cachedGroupController.closeEndpointHandle(cachedId, 58 disconnectReason); 59 } else if (cachedPeerState) { 60 cachedPeerState.onPeerClosedBeforeAssociation(disconnectReason); 61 } 62 }; 63 64 State.prototype.runAssociationEventHandler = function(associationEvent) { 65 if (this.associationEventHandler_) { 66 var handler = this.associationEventHandler_; 67 this.associationEventHandler_ = null; 68 handler(associationEvent); 69 } 70 }; 71 72 State.prototype.setAssociationEventHandler = function(handler) { 73 if (!this.pendingAssociation && 74 !internal.isValidInterfaceId(this.interfaceId)) { 75 return; 76 } 77 78 if (!handler) { 79 this.associationEventHandler_ = null; 80 return; 81 } 82 83 this.associationEventHandler_ = handler; 84 if (!this.pendingAssociation) { 85 setTimeout(this.runAssociationEventHandler.bind(this, 86 AssociationEvent.ASSOCIATED), 0); 87 } else if (!this.peerState_) { 88 setTimeout(this.runAssociationEventHandler.bind(this, 89 AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION), 0); 90 } 91 }; 92 93 State.prototype.notifyAssociation = function(interfaceId, 94 peerGroupController) { 95 var cachedPeerState = this.peerState_; 96 this.peerState_ = null; 97 98 this.pendingAssociation = false; 99 100 if (cachedPeerState) { 101 cachedPeerState.onAssociated(interfaceId, peerGroupController); 102 return true; 103 } 104 return false; 105 }; 106 107 State.prototype.onAssociated = function(interfaceId, 108 associatedGroupController) { 109 if (!this.pendingAssociation) { 110 return; 111 } 112 113 this.pendingAssociation = false; 114 this.peerState_ = null; 115 this.interfaceId = interfaceId; 116 this.associatedGroupController = associatedGroupController; 117 this.runAssociationEventHandler(AssociationEvent.ASSOCIATED); 118 }; 119 120 State.prototype.onPeerClosedBeforeAssociation = function(disconnectReason) { 121 if (!this.pendingAssociation) { 122 return; 123 } 124 125 this.peerState_ = null; 126 this.disconnectReason = disconnectReason; 127 128 this.runAssociationEventHandler( 129 AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION); 130 }; 131 132 function createPairPendingAssociation() { 133 var handle0 = new InterfaceEndpointHandle(); 134 var handle1 = new InterfaceEndpointHandle(); 135 handle0.state_.initPendingState(handle1.state_); 136 handle1.state_.initPendingState(handle0.state_); 137 return {handle0: handle0, handle1: handle1}; 138 } 139 140 function InterfaceEndpointHandle(interfaceId, associatedGroupController) { 141 this.state_ = new State(interfaceId, associatedGroupController); 142 } 143 144 InterfaceEndpointHandle.prototype.isValid = function() { 145 return this.state_.isValid(); 146 }; 147 148 InterfaceEndpointHandle.prototype.pendingAssociation = function() { 149 return this.state_.pendingAssociation; 150 }; 151 152 InterfaceEndpointHandle.prototype.id = function() { 153 return this.state_.interfaceId; 154 }; 155 156 InterfaceEndpointHandle.prototype.groupController = function() { 157 return this.state_.associatedGroupController; 158 }; 159 160 InterfaceEndpointHandle.prototype.disconnectReason = function() { 161 return this.state_.disconnectReason; 162 }; 163 164 InterfaceEndpointHandle.prototype.setAssociationEventHandler = function( 165 handler) { 166 this.state_.setAssociationEventHandler(handler); 167 }; 168 169 InterfaceEndpointHandle.prototype.notifyAssociation = function(interfaceId, 170 peerGroupController) { 171 return this.state_.notifyAssociation(interfaceId, peerGroupController); 172 }; 173 174 InterfaceEndpointHandle.prototype.reset = function(reason) { 175 this.state_.close(reason); 176 this.state_ = new State(); 177 }; 178 179 internal.AssociationEvent = AssociationEvent; 180 internal.InterfaceEndpointHandle = InterfaceEndpointHandle; 181 internal.createPairPendingAssociation = createPairPendingAssociation; 182})(); 183