1 | // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #ifndef RUNTIME_BIN_EVENTHANDLER_H_ |
6 | #define RUNTIME_BIN_EVENTHANDLER_H_ |
7 | |
8 | #include "bin/builtin.h" |
9 | #include "bin/dartutils.h" |
10 | #include "bin/isolate_data.h" |
11 | |
12 | #include "platform/hashmap.h" |
13 | |
14 | namespace dart { |
15 | namespace bin { |
16 | |
17 | // Flags used to provide information and actions to the eventhandler |
18 | // when sending a message about a file descriptor. These flags should |
19 | // be kept in sync with the constants in socket_patch.dart. For more |
20 | // information see the comments in socket_patch.dart |
21 | enum MessageFlags { |
22 | kInEvent = 0, |
23 | kOutEvent = 1, |
24 | kErrorEvent = 2, |
25 | kCloseEvent = 3, |
26 | kDestroyedEvent = 4, |
27 | kCloseCommand = 8, |
28 | kShutdownReadCommand = 9, |
29 | kShutdownWriteCommand = 10, |
30 | kReturnTokenCommand = 11, |
31 | kSetEventMaskCommand = 12, |
32 | kListeningSocket = 16, |
33 | kPipe = 17, |
34 | }; |
35 | |
36 | // clang-format off |
37 | #define COMMAND_MASK ((1 << kCloseCommand) | \ |
38 | (1 << kShutdownReadCommand) | \ |
39 | (1 << kShutdownWriteCommand) | \ |
40 | (1 << kReturnTokenCommand) | \ |
41 | (1 << kSetEventMaskCommand)) |
42 | #define EVENT_MASK ((1 << kInEvent) | \ |
43 | (1 << kOutEvent) | \ |
44 | (1 << kErrorEvent) | \ |
45 | (1 << kCloseEvent) | \ |
46 | (1 << kDestroyedEvent)) |
47 | #define IS_COMMAND(data, command_bit) \ |
48 | ((data & COMMAND_MASK) == (1 << command_bit)) // NOLINT |
49 | #define IS_EVENT(data, event_bit) \ |
50 | ((data & EVENT_MASK) == (1 << event_bit)) // NOLINT |
51 | #define IS_IO_EVENT(data) \ |
52 | ((data & (1 << kInEvent | 1 << kOutEvent | 1 << kCloseEvent)) != 0 && \ |
53 | (data & ~(1 << kInEvent | 1 << kOutEvent | 1 << kCloseEvent)) == 0) |
54 | #define IS_LISTENING_SOCKET(data) \ |
55 | ((data & (1 << kListeningSocket)) != 0) // NOLINT |
56 | #define TOKEN_COUNT(data) (data & ((1 << kCloseCommand) - 1)) |
57 | // clang-format on |
58 | |
59 | class TimeoutQueue { |
60 | private: |
61 | class Timeout { |
62 | public: |
63 | Timeout(Dart_Port port, int64_t timeout, Timeout* next) |
64 | : port_(port), timeout_(timeout), next_(next) {} |
65 | |
66 | Dart_Port port() const { return port_; } |
67 | |
68 | int64_t timeout() const { return timeout_; } |
69 | void set_timeout(int64_t timeout) { |
70 | ASSERT(timeout >= 0); |
71 | timeout_ = timeout; |
72 | } |
73 | |
74 | Timeout* next() const { return next_; } |
75 | void set_next(Timeout* next) { next_ = next; } |
76 | |
77 | private: |
78 | Dart_Port port_; |
79 | int64_t timeout_; |
80 | Timeout* next_; |
81 | }; |
82 | |
83 | public: |
84 | TimeoutQueue() : next_timeout_(NULL), timeouts_(NULL) {} |
85 | |
86 | ~TimeoutQueue() { |
87 | while (HasTimeout()) |
88 | RemoveCurrent(); |
89 | } |
90 | |
91 | bool HasTimeout() const { return next_timeout_ != NULL; } |
92 | |
93 | int64_t CurrentTimeout() const { |
94 | ASSERT(next_timeout_ != NULL); |
95 | return next_timeout_->timeout(); |
96 | } |
97 | |
98 | Dart_Port CurrentPort() const { |
99 | ASSERT(next_timeout_ != NULL); |
100 | return next_timeout_->port(); |
101 | } |
102 | |
103 | void RemoveCurrent() { UpdateTimeout(CurrentPort(), -1); } |
104 | |
105 | void UpdateTimeout(Dart_Port port, int64_t timeout); |
106 | |
107 | private: |
108 | Timeout* next_timeout_; |
109 | Timeout* timeouts_; |
110 | |
111 | DISALLOW_COPY_AND_ASSIGN(TimeoutQueue); |
112 | }; |
113 | |
114 | class InterruptMessage { |
115 | public: |
116 | intptr_t id; |
117 | Dart_Port dart_port; |
118 | int64_t data; |
119 | }; |
120 | |
121 | static const int kInterruptMessageSize = sizeof(InterruptMessage); |
122 | static const int kInfinityTimeout = -1; |
123 | static const int kTimerId = -1; |
124 | static const int kShutdownId = -2; |
125 | |
126 | template <typename T> |
127 | class CircularLinkedList { |
128 | public: |
129 | CircularLinkedList() : head_(NULL) {} |
130 | |
131 | typedef void (*ClearFun)(void* value); |
132 | |
133 | // Returns true if the list was empty. |
134 | bool Add(T t) { |
135 | Entry* e = new Entry(t); |
136 | if (head_ == NULL) { |
137 | // Empty list, make e head, and point to itself. |
138 | e->next_ = e; |
139 | e->prev_ = e; |
140 | head_ = e; |
141 | return true; |
142 | } else { |
143 | // Insert e as the last element in the list. |
144 | e->prev_ = head_->prev_; |
145 | e->next_ = head_; |
146 | e->prev_->next_ = e; |
147 | head_->prev_ = e; |
148 | return false; |
149 | } |
150 | } |
151 | |
152 | void RemoveHead(ClearFun clear = NULL) { |
153 | ASSERT(head_ != NULL); |
154 | |
155 | Entry* e = head_; |
156 | if (e->next_ == e) { |
157 | head_ = NULL; |
158 | } else { |
159 | e->prev_->next_ = e->next_; |
160 | e->next_->prev_ = e->prev_; |
161 | head_ = e->next_; |
162 | } |
163 | if (clear != NULL) { |
164 | clear(reinterpret_cast<void*>(e->t)); |
165 | } |
166 | delete e; |
167 | } |
168 | |
169 | void Remove(T item) { |
170 | if (head_ == NULL) { |
171 | return; |
172 | } else if (head_ == head_->next_) { |
173 | if (head_->t == item) { |
174 | delete head_; |
175 | head_ = NULL; |
176 | return; |
177 | } |
178 | } else { |
179 | Entry* current = head_; |
180 | do { |
181 | if (current->t == item) { |
182 | Entry* next = current->next_; |
183 | Entry* prev = current->prev_; |
184 | prev->next_ = next; |
185 | next->prev_ = prev; |
186 | |
187 | if (current == head_) { |
188 | head_ = head_->next_; |
189 | } |
190 | |
191 | delete current; |
192 | return; |
193 | } |
194 | current = current->next_; |
195 | } while (current != head_); |
196 | } |
197 | } |
198 | |
199 | void RemoveAll(ClearFun clear = NULL) { |
200 | while (HasHead()) { |
201 | RemoveHead(clear); |
202 | } |
203 | } |
204 | |
205 | T head() const { return head_->t; } |
206 | |
207 | bool HasHead() const { return head_ != NULL; } |
208 | |
209 | void Rotate() { |
210 | if (head_ != NULL) { |
211 | ASSERT(head_->next_ != NULL); |
212 | head_ = head_->next_; |
213 | } |
214 | } |
215 | |
216 | private: |
217 | struct Entry { |
218 | explicit Entry(const T& t) : t(t), next_(NULL), prev_(NULL) {} |
219 | const T t; |
220 | Entry* next_; |
221 | Entry* prev_; |
222 | }; |
223 | |
224 | Entry* head_; |
225 | |
226 | DISALLOW_COPY_AND_ASSIGN(CircularLinkedList); |
227 | }; |
228 | |
229 | class DescriptorInfoBase { |
230 | public: |
231 | explicit DescriptorInfoBase(intptr_t fd) : fd_(fd) { ASSERT(fd_ != -1); } |
232 | |
233 | virtual ~DescriptorInfoBase() {} |
234 | |
235 | // The OS descriptor. |
236 | intptr_t fd() { return fd_; } |
237 | |
238 | // Whether this descriptor refers to an underlying listening OS socket. |
239 | virtual bool IsListeningSocket() const = 0; |
240 | |
241 | // Inserts or updates a new Dart_Port which is interested in events specified |
242 | // in `mask`. |
243 | virtual void SetPortAndMask(Dart_Port port, intptr_t mask) = 0; |
244 | |
245 | // Removes a port from the interested listeners. |
246 | virtual void RemovePort(Dart_Port port) = 0; |
247 | |
248 | // Removes all ports from the interested listeners. |
249 | virtual void RemoveAllPorts() = 0; |
250 | |
251 | // Returns a port to which `events_ready` can be sent to. It will also |
252 | // decrease the token count by 1 for this port. |
253 | virtual Dart_Port NextNotifyDartPort(intptr_t events_ready) = 0; |
254 | |
255 | // Will post `data` to all known Dart_Ports. It will also decrease the token |
256 | // count by 1 for all ports. |
257 | virtual void NotifyAllDartPorts(uintptr_t events) = 0; |
258 | |
259 | // Returns `count` tokens for the given port. |
260 | virtual void ReturnTokens(Dart_Port port, int count) = 0; |
261 | |
262 | // Returns the union of event masks of all ports. If a port has a non-positive |
263 | // token count it's mask is assumed to be 0. |
264 | virtual intptr_t Mask() = 0; |
265 | |
266 | // Closes this descriptor. |
267 | virtual void Close() = 0; |
268 | |
269 | protected: |
270 | intptr_t fd_; |
271 | |
272 | private: |
273 | DISALLOW_COPY_AND_ASSIGN(DescriptorInfoBase); |
274 | }; |
275 | |
276 | // Describes a OS descriptor (e.g. file descriptor on linux or HANDLE on |
277 | // windows) which is connected to a single Dart_Port. |
278 | // |
279 | // Subclasses of this class can be e.g. connected tcp sockets. |
280 | template <typename DI> |
281 | class DescriptorInfoSingleMixin : public DI { |
282 | private: |
283 | static const int kTokenCount = 16; |
284 | |
285 | public: |
286 | DescriptorInfoSingleMixin(intptr_t fd, bool disable_tokens) |
287 | : DI(fd), |
288 | port_(0), |
289 | tokens_(kTokenCount), |
290 | mask_(0), |
291 | disable_tokens_(disable_tokens) {} |
292 | |
293 | virtual ~DescriptorInfoSingleMixin() {} |
294 | |
295 | virtual bool IsListeningSocket() const { return false; } |
296 | |
297 | virtual void SetPortAndMask(Dart_Port port, intptr_t mask) { |
298 | ASSERT(port_ == 0 || port == port_); |
299 | port_ = port; |
300 | mask_ = mask; |
301 | } |
302 | |
303 | virtual void RemovePort(Dart_Port port) { |
304 | // TODO(dart:io): Find out where we call RemovePort() with the invalid |
305 | // port. Afterwards remove the part in the ASSERT here. |
306 | ASSERT(port_ == 0 || port_ == port); |
307 | port_ = 0; |
308 | mask_ = 0; |
309 | } |
310 | |
311 | virtual void RemoveAllPorts() { |
312 | port_ = 0; |
313 | mask_ = 0; |
314 | } |
315 | |
316 | virtual Dart_Port NextNotifyDartPort(intptr_t events_ready) { |
317 | ASSERT(IS_IO_EVENT(events_ready) || |
318 | IS_EVENT(events_ready, kDestroyedEvent)); |
319 | if (!disable_tokens_) { |
320 | tokens_--; |
321 | } |
322 | return port_; |
323 | } |
324 | |
325 | virtual void NotifyAllDartPorts(uintptr_t events) { |
326 | // Unexpected close, asynchronous destroy or error events are the only |
327 | // ones we broadcast to all listeners. |
328 | ASSERT(IS_EVENT(events, kCloseEvent) || IS_EVENT(events, kErrorEvent) || |
329 | IS_EVENT(events, kDestroyedEvent)); |
330 | |
331 | if (port_ != 0) { |
332 | DartUtils::PostInt32(port_, events); |
333 | } |
334 | if (!disable_tokens_) { |
335 | tokens_--; |
336 | } |
337 | } |
338 | |
339 | virtual void ReturnTokens(Dart_Port port, int count) { |
340 | ASSERT(port_ == port); |
341 | if (!disable_tokens_) { |
342 | tokens_ += count; |
343 | } |
344 | ASSERT(tokens_ <= kTokenCount); |
345 | } |
346 | |
347 | virtual intptr_t Mask() { |
348 | if (tokens_ <= 0) { |
349 | return 0; |
350 | } |
351 | return mask_; |
352 | } |
353 | |
354 | virtual void Close() { DI::Close(); } |
355 | |
356 | private: |
357 | Dart_Port port_; |
358 | int tokens_; |
359 | intptr_t mask_; |
360 | bool disable_tokens_; |
361 | |
362 | DISALLOW_COPY_AND_ASSIGN(DescriptorInfoSingleMixin); |
363 | }; |
364 | |
365 | // Describes a OS descriptor (e.g. file descriptor on linux or HANDLE on |
366 | // windows) which is connected to multiple Dart_Port's. |
367 | // |
368 | // Subclasses of this class can be e.g. a listening socket which multiple |
369 | // isolates are listening on. |
370 | template <typename DI> |
371 | class DescriptorInfoMultipleMixin : public DI { |
372 | private: |
373 | static const int kTokenCount = 4; |
374 | |
375 | static bool SamePortValue(void* key1, void* key2) { |
376 | return reinterpret_cast<Dart_Port>(key1) == |
377 | reinterpret_cast<Dart_Port>(key2); |
378 | } |
379 | |
380 | static uint32_t GetHashmapHashFromPort(Dart_Port port) { |
381 | return static_cast<uint32_t>(port & 0xFFFFFFFF); |
382 | } |
383 | |
384 | static void* GetHashmapKeyFromPort(Dart_Port port) { |
385 | return reinterpret_cast<void*>(port); |
386 | } |
387 | |
388 | static bool IsReadingMask(intptr_t mask) { |
389 | if (mask == (1 << kInEvent)) { |
390 | return true; |
391 | } else { |
392 | ASSERT(mask == 0); |
393 | return false; |
394 | } |
395 | } |
396 | |
397 | struct PortEntry { |
398 | Dart_Port dart_port; |
399 | intptr_t is_reading; |
400 | intptr_t token_count; |
401 | |
402 | bool IsReady() { return token_count > 0 && is_reading; } |
403 | }; |
404 | |
405 | public: |
406 | DescriptorInfoMultipleMixin(intptr_t fd, bool disable_tokens) |
407 | : DI(fd), |
408 | tokens_map_(&SamePortValue, kTokenCount), |
409 | disable_tokens_(disable_tokens) {} |
410 | |
411 | virtual ~DescriptorInfoMultipleMixin() { RemoveAllPorts(); } |
412 | |
413 | virtual bool IsListeningSocket() const { return true; } |
414 | |
415 | virtual void SetPortAndMask(Dart_Port port, intptr_t mask) { |
416 | SimpleHashMap::Entry* entry = tokens_map_.Lookup( |
417 | GetHashmapKeyFromPort(port), GetHashmapHashFromPort(port), true); |
418 | PortEntry* pentry; |
419 | if (entry->value == NULL) { |
420 | pentry = new PortEntry(); |
421 | pentry->dart_port = port; |
422 | pentry->token_count = kTokenCount; |
423 | pentry->is_reading = IsReadingMask(mask); |
424 | entry->value = reinterpret_cast<void*>(pentry); |
425 | |
426 | if (pentry->IsReady()) { |
427 | active_readers_.Add(pentry); |
428 | } |
429 | } else { |
430 | pentry = reinterpret_cast<PortEntry*>(entry->value); |
431 | bool was_ready = pentry->IsReady(); |
432 | pentry->is_reading = IsReadingMask(mask); |
433 | bool is_ready = pentry->IsReady(); |
434 | |
435 | if (was_ready && !is_ready) { |
436 | active_readers_.Remove(pentry); |
437 | } else if (!was_ready && is_ready) { |
438 | active_readers_.Add(pentry); |
439 | } |
440 | } |
441 | |
442 | #ifdef DEBUG |
443 | // To ensure that all readers are ready. |
444 | int ready_count = 0; |
445 | |
446 | if (active_readers_.HasHead()) { |
447 | PortEntry* root = reinterpret_cast<PortEntry*>(active_readers_.head()); |
448 | PortEntry* current = root; |
449 | do { |
450 | ASSERT(current->IsReady()); |
451 | ready_count++; |
452 | active_readers_.Rotate(); |
453 | current = active_readers_.head(); |
454 | } while (current != root); |
455 | } |
456 | |
457 | for (SimpleHashMap::Entry* entry = tokens_map_.Start(); entry != NULL; |
458 | entry = tokens_map_.Next(entry)) { |
459 | PortEntry* pentry = reinterpret_cast<PortEntry*>(entry->value); |
460 | if (pentry->IsReady()) { |
461 | ready_count--; |
462 | } |
463 | } |
464 | // Ensure all ready items are in `active_readers_`. |
465 | ASSERT(ready_count == 0); |
466 | #endif |
467 | } |
468 | |
469 | virtual void RemovePort(Dart_Port port) { |
470 | SimpleHashMap::Entry* entry = tokens_map_.Lookup( |
471 | GetHashmapKeyFromPort(port), GetHashmapHashFromPort(port), false); |
472 | if (entry != NULL) { |
473 | PortEntry* pentry = reinterpret_cast<PortEntry*>(entry->value); |
474 | if (pentry->IsReady()) { |
475 | active_readers_.Remove(pentry); |
476 | } |
477 | tokens_map_.Remove(GetHashmapKeyFromPort(port), |
478 | GetHashmapHashFromPort(port)); |
479 | delete pentry; |
480 | } else { |
481 | // NOTE: This is a listening socket which has been immediately closed. |
482 | // |
483 | // If a listening socket is not listened on, the event handler does not |
484 | // know about it beforehand. So the first time the event handler knows |
485 | // about it, is when it is supposed to be closed. We therefore do nothing |
486 | // here. |
487 | // |
488 | // But whether to close it, depends on whether other isolates have it open |
489 | // as well or not. |
490 | } |
491 | } |
492 | |
493 | virtual void RemoveAllPorts() { |
494 | for (SimpleHashMap::Entry* entry = tokens_map_.Start(); entry != NULL; |
495 | entry = tokens_map_.Next(entry)) { |
496 | PortEntry* pentry = reinterpret_cast<PortEntry*>(entry->value); |
497 | entry->value = NULL; |
498 | active_readers_.Remove(pentry); |
499 | delete pentry; |
500 | } |
501 | tokens_map_.Clear(); |
502 | active_readers_.RemoveAll(DeletePortEntry); |
503 | } |
504 | |
505 | virtual Dart_Port NextNotifyDartPort(intptr_t events_ready) { |
506 | // We're only sending `kInEvents` if there are multiple listeners (which is |
507 | // listening socktes). |
508 | ASSERT(IS_EVENT(events_ready, kInEvent) || |
509 | IS_EVENT(events_ready, kDestroyedEvent)); |
510 | |
511 | if (active_readers_.HasHead()) { |
512 | PortEntry* pentry = reinterpret_cast<PortEntry*>(active_readers_.head()); |
513 | |
514 | // Update token count. |
515 | if (!disable_tokens_) { |
516 | pentry->token_count--; |
517 | } |
518 | if (pentry->token_count <= 0) { |
519 | active_readers_.RemoveHead(); |
520 | } else { |
521 | active_readers_.Rotate(); |
522 | } |
523 | |
524 | return pentry->dart_port; |
525 | } |
526 | return 0; |
527 | } |
528 | |
529 | virtual void NotifyAllDartPorts(uintptr_t events) { |
530 | // Unexpected close, asynchronous destroy or error events are the only |
531 | // ones we broadcast to all listeners. |
532 | ASSERT(IS_EVENT(events, kCloseEvent) || IS_EVENT(events, kErrorEvent) || |
533 | IS_EVENT(events, kDestroyedEvent)); |
534 | |
535 | for (SimpleHashMap::Entry* entry = tokens_map_.Start(); entry != NULL; |
536 | entry = tokens_map_.Next(entry)) { |
537 | PortEntry* pentry = reinterpret_cast<PortEntry*>(entry->value); |
538 | DartUtils::PostInt32(pentry->dart_port, events); |
539 | |
540 | // Update token count. |
541 | bool was_ready = pentry->IsReady(); |
542 | if (!disable_tokens_) { |
543 | pentry->token_count--; |
544 | } |
545 | |
546 | if (was_ready && (pentry->token_count <= 0)) { |
547 | active_readers_.Remove(pentry); |
548 | } |
549 | } |
550 | } |
551 | |
552 | virtual void ReturnTokens(Dart_Port port, int count) { |
553 | SimpleHashMap::Entry* entry = tokens_map_.Lookup( |
554 | GetHashmapKeyFromPort(port), GetHashmapHashFromPort(port), false); |
555 | ASSERT(entry != NULL); |
556 | |
557 | PortEntry* pentry = reinterpret_cast<PortEntry*>(entry->value); |
558 | bool was_ready = pentry->IsReady(); |
559 | if (!disable_tokens_) { |
560 | pentry->token_count += count; |
561 | } |
562 | ASSERT(pentry->token_count <= kTokenCount); |
563 | bool is_ready = pentry->IsReady(); |
564 | if (!was_ready && is_ready) { |
565 | active_readers_.Add(pentry); |
566 | } |
567 | } |
568 | |
569 | virtual intptr_t Mask() { |
570 | if (active_readers_.HasHead()) { |
571 | return 1 << kInEvent; |
572 | } |
573 | return 0; |
574 | } |
575 | |
576 | virtual void Close() { DI::Close(); } |
577 | |
578 | private: |
579 | static void DeletePortEntry(void* data) { |
580 | PortEntry* entry = reinterpret_cast<PortEntry*>(data); |
581 | delete entry; |
582 | } |
583 | |
584 | // The [Dart_Port]s which are not paused (i.e. are interested in read events, |
585 | // i.e. `mask == (1 << kInEvent)`) and we have enough tokens to communicate |
586 | // with them. |
587 | CircularLinkedList<PortEntry*> active_readers_; |
588 | |
589 | // A convenience mapping: |
590 | // Dart_Port -> struct PortEntry { dart_port, mask, token_count } |
591 | SimpleHashMap tokens_map_; |
592 | |
593 | bool disable_tokens_; |
594 | |
595 | DISALLOW_COPY_AND_ASSIGN(DescriptorInfoMultipleMixin); |
596 | }; |
597 | |
598 | } // namespace bin |
599 | } // namespace dart |
600 | |
601 | // The event handler delegation class is OS specific. |
602 | #if defined(HOST_OS_ANDROID) |
603 | #include "bin/eventhandler_android.h" |
604 | #elif defined(HOST_OS_FUCHSIA) |
605 | #include "bin/eventhandler_fuchsia.h" |
606 | #elif defined(HOST_OS_LINUX) |
607 | #include "bin/eventhandler_linux.h" |
608 | #elif defined(HOST_OS_MACOS) |
609 | #include "bin/eventhandler_macos.h" |
610 | #elif defined(HOST_OS_WINDOWS) |
611 | #include "bin/eventhandler_win.h" |
612 | #else |
613 | #error Unknown target os. |
614 | #endif |
615 | |
616 | namespace dart { |
617 | namespace bin { |
618 | |
619 | class EventHandler { |
620 | public: |
621 | EventHandler() {} |
622 | void SendData(intptr_t id, Dart_Port dart_port, int64_t data) { |
623 | delegate_.SendData(id, dart_port, data); |
624 | } |
625 | |
626 | /** |
627 | * Signal to main thread that event handler is done. |
628 | */ |
629 | void NotifyShutdownDone(); |
630 | |
631 | /** |
632 | * Start the event-handler. |
633 | */ |
634 | static void Start(); |
635 | |
636 | /** |
637 | * Stop the event-handler. It's expected that there will be no further calls |
638 | * to SendData after a call to Stop. |
639 | */ |
640 | static void Stop(); |
641 | |
642 | static EventHandlerImplementation* delegate(); |
643 | |
644 | static void SendFromNative(intptr_t id, Dart_Port port, int64_t data); |
645 | |
646 | private: |
647 | friend class EventHandlerImplementation; |
648 | EventHandlerImplementation delegate_; |
649 | |
650 | DISALLOW_COPY_AND_ASSIGN(EventHandler); |
651 | }; |
652 | |
653 | } // namespace bin |
654 | } // namespace dart |
655 | |
656 | #endif // RUNTIME_BIN_EVENTHANDLER_H_ |
657 | |