1// Copyright 2014 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  function Connector(handle) {
9    if (!(handle instanceof MojoHandle))
10      throw new Error("Connector: not a handle " + handle);
11    this.handle_ = handle;
12    this.dropWrites_ = false;
13    this.error_ = false;
14    this.incomingReceiver_ = null;
15    this.readWatcher_ = null;
16    this.errorHandler_ = null;
17    this.paused_ = false;
18
19    this.waitToReadMore();
20  }
21
22  Connector.prototype.close = function() {
23    this.cancelWait();
24    if (this.handle_ != null) {
25      this.handle_.close();
26      this.handle_ = null;
27    }
28  };
29
30  Connector.prototype.pauseIncomingMethodCallProcessing = function() {
31    if (this.paused_) {
32      return;
33    }
34    this.paused_= true;
35    this.cancelWait();
36  };
37
38  Connector.prototype.resumeIncomingMethodCallProcessing = function() {
39    if (!this.paused_) {
40      return;
41    }
42    this.paused_= false;
43    this.waitToReadMore();
44  };
45
46  Connector.prototype.accept = function(message) {
47    if (this.error_)
48      return false;
49
50    if (this.dropWrites_)
51      return true;
52
53    var result = this.handle_.writeMessage(
54        new Uint8Array(message.buffer.arrayBuffer), message.handles);
55    switch (result) {
56      case Mojo.RESULT_OK:
57        // The handles were successfully transferred, so we don't own them
58        // anymore.
59        message.handles = [];
60        break;
61      case Mojo.RESULT_FAILED_PRECONDITION:
62        // There's no point in continuing to write to this pipe since the other
63        // end is gone. Avoid writing any future messages. Hide write failures
64        // from the caller since we'd like them to continue consuming any
65        // backlog of incoming messages before regarding the message pipe as
66        // closed.
67        this.dropWrites_ = true;
68        break;
69      default:
70        // This particular write was rejected, presumably because of bad input.
71        // The pipe is not necessarily in a bad state.
72        return false;
73    }
74    return true;
75  };
76
77  Connector.prototype.setIncomingReceiver = function(receiver) {
78    this.incomingReceiver_ = receiver;
79  };
80
81  Connector.prototype.setErrorHandler = function(handler) {
82    this.errorHandler_ = handler;
83  };
84
85  Connector.prototype.readMore_ = function(result) {
86    for (;;) {
87      if (this.paused_) {
88        return;
89      }
90
91      var read = this.handle_.readMessage();
92      if (this.handle_ == null) // The connector has been closed.
93        return;
94      if (read.result == Mojo.RESULT_SHOULD_WAIT)
95        return;
96      if (read.result != Mojo.RESULT_OK) {
97        this.handleError(read.result !== Mojo.RESULT_FAILED_PRECONDITION,
98            false);
99        return;
100      }
101      var messageBuffer = new internal.Buffer(read.buffer);
102      var message = new internal.Message(messageBuffer, read.handles);
103      var receiverResult = this.incomingReceiver_ &&
104          this.incomingReceiver_.accept(message);
105
106      // Dispatching the message may have closed the connector.
107      if (this.handle_ == null)
108        return;
109
110      // Handle invalid incoming message.
111      if (!internal.isTestingMode() && !receiverResult) {
112        // TODO(yzshen): Consider notifying the embedder.
113        this.handleError(true, false);
114      }
115    }
116  };
117
118  Connector.prototype.cancelWait = function() {
119    if (this.readWatcher_) {
120      this.readWatcher_.cancel();
121      this.readWatcher_ = null;
122    }
123  };
124
125  Connector.prototype.waitToReadMore = function() {
126    if (this.handle_) {
127      this.readWatcher_ = this.handle_.watch({readable: true},
128                                             this.readMore_.bind(this));
129    }
130  };
131
132  Connector.prototype.handleError = function(forcePipeReset,
133                                             forceAsyncHandler) {
134    if (this.error_ || this.handle_ === null) {
135      return;
136    }
137
138    if (this.paused_) {
139      // Enforce calling the error handler asynchronously if the user has
140      // paused receiving messages. We need to wait until the user starts
141      // receiving messages again.
142      forceAsyncHandler = true;
143    }
144
145    if (!forcePipeReset && forceAsyncHandler) {
146      forcePipeReset = true;
147    }
148
149    this.cancelWait();
150    if (forcePipeReset) {
151      this.handle_.close();
152      var dummyPipe = Mojo.createMessagePipe();
153      this.handle_ = dummyPipe.handle0;
154    }
155
156    if (forceAsyncHandler) {
157      if (!this.paused_) {
158        this.waitToReadMore();
159      }
160    } else {
161      this.error_ = true;
162      if (this.errorHandler_) {
163        this.errorHandler_.onError();
164      }
165    }
166  };
167
168  internal.Connector = Connector;
169})();
170