OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of html; | 5 part of html; |
6 | 6 |
7 /** | 7 /** |
8 * A factory to expose DOM events as Streams. | 8 * A factory to expose DOM events as Streams. |
9 */ | 9 */ |
10 class EventStreamProvider<T extends Event> { | 10 class EventStreamProvider<T extends Event> { |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
112 * | 112 * |
113 * ## Other resources | 113 * ## Other resources |
114 * | 114 * |
115 * * [Event Capture](http://www.w3.org/TR/DOM-Level-2-Events/events.html#Event s-flow-capture) | 115 * * [Event Capture](http://www.w3.org/TR/DOM-Level-2-Events/events.html#Event s-flow-capture) |
116 * from the W3C DOM Events specification. | 116 * from the W3C DOM Events specification. |
117 */ | 117 */ |
118 StreamSubscription<T> capture(void onData(T event)); | 118 StreamSubscription<T> capture(void onData(T event)); |
119 } | 119 } |
120 | 120 |
121 /** | 121 /** |
122 * Task specification for Dom Events. | |
Lasse Reichstein Nielsen
2016/06/07 12:21:44
Dom -> DOM
floitsch
2016/06/10 15:43:58
Done.
| |
123 */ | |
Lasse Reichstein Nielsen
2016/06/07 12:21:44
I noticed you used /// below. Feel free to use it
floitsch
2016/06/10 15:43:58
done.
| |
124 class EventSubscriptionSpecification<T extends Event> | |
125 implements TaskSpecification { | |
126 final String name; | |
Lasse Reichstein Nielsen
2016/06/07 12:21:44
Document the name. What is it? What values will it
floitsch
2016/06/10 15:43:58
Should inherit the documentation from `TaskSpecifi
| |
127 final bool isOneShot; | |
Lasse Reichstein Nielsen
2016/06/07 12:21:44
Document:
/// A one-shot event task is guaranteed
floitsch
2016/06/10 15:43:58
ditto.
| |
128 | |
129 final EventTarget target; | |
130 final String eventType; | |
Lasse Reichstein Nielsen
2016/06/07 12:21:44
I assume this is the eventType of the event, so "c
floitsch
2016/06/10 15:43:58
yes.
added a bit of documentation (not a lot, thou
| |
131 // TODO(floitsch): the first generic argument should be 'void'. | |
132 final ZoneUnaryCallback<dynamic, T> onData; | |
133 final bool useCapture; | |
134 | |
135 EventSubscriptionSpecification({this.name, this.isOneShot, this.target, | |
136 this.eventType, void this.onData(T event), this.useCapture}); | |
137 | |
138 /// Returns a copy of this instance, with every non-null argument replaced | |
139 /// by the given value. | |
140 EventSubscriptionSpecification<T> replace( | |
141 {String name, bool isOneShot, EventTarget target, | |
142 String eventType, void onData(T event), bool useCapture}) { | |
143 return new EventSubscriptionSpecification<T>( | |
144 name: name ?? this.name, | |
145 isOneShot: isOneShot ?? this.isOneShot, | |
146 target: target ?? this.target, | |
147 eventType: eventType ?? this.eventType, | |
148 onData: onData ?? this.onData, | |
149 useCapture: useCapture ?? this.useCapture); | |
150 } | |
151 } | |
152 | |
153 /** | |
122 * Adapter for exposing DOM events as Dart streams. | 154 * Adapter for exposing DOM events as Dart streams. |
123 */ | 155 */ |
124 class _EventStream<T extends Event> extends Stream<T> { | 156 class _EventStream<T extends Event> extends Stream<T> { |
125 final EventTarget _target; | 157 final EventTarget _target; |
126 final String _eventType; | 158 final String _eventType; |
127 final bool _useCapture; | 159 final bool _useCapture; |
160 /// The name that is used in the task specification. | |
161 final String _name; | |
162 /// Whether the stream can trigger multiple times. | |
163 final bool _isOneShot; | |
128 | 164 |
129 _EventStream(this._target, this._eventType, this._useCapture); | 165 _EventStream(this._target, String eventType, this._useCapture, |
166 {String name, bool isOneShot: false}) | |
167 : _eventType = eventType, | |
168 _isOneShot = isOneShot, | |
169 _name = name ?? "dart.html.event.$eventType"; | |
Lasse Reichstein Nielsen
2016/06/07 12:21:44
That looks potentially expensive if you have many
floitsch
2016/06/10 15:43:58
I think the EventStreamProvider (which is a `const
| |
130 | 170 |
131 // DOM events are inherently multi-subscribers. | 171 // DOM events are inherently multi-subscribers. |
132 Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription) , | 172 Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription) , |
133 void onCancel(StreamSubscription<T> subscription) }) | 173 void onCancel(StreamSubscription<T> subscription) }) |
134 => this; | 174 => this; |
135 bool get isBroadcast => true; | 175 bool get isBroadcast => true; |
136 | 176 |
137 StreamSubscription<T> listen(void onData(T event), | 177 StreamSubscription<T> listen(void onData(T event), |
138 { Function onError, | 178 { Function onError, |
139 void onDone(), | 179 void onDone(), |
140 bool cancelOnError}) { | 180 bool cancelOnError}) { |
141 | 181 |
142 return new _EventStreamSubscription<T>( | 182 if (identical(Zone.current, Zone.ROOT)) { |
143 this._target, this._eventType, onData, this._useCapture); | 183 return new _EventStreamSubscription<T>( |
184 this._target, this._eventType, onData, this._useCapture, | |
185 Zone.current); | |
186 } | |
187 | |
188 var specification = new EventSubscriptionSpecification<T>( | |
189 name: this._name, isOneShot: this._isOneShot, | |
190 target: this._target, eventType: this._eventType, | |
191 onData: onData, useCapture: this._useCapture); | |
192 // We need to wrap the _createStreamSubscription call, since a tear-off | |
193 // would not bind the generic type 'T'. | |
194 return Zone.current.createTask((spec, Zone zone) { | |
195 return _createStreamSubscription/*<T>*/(spec, zone); | |
196 }, specification); | |
144 } | 197 } |
145 } | 198 } |
146 | 199 |
147 bool _matchesWithAncestors(Event event, String selector) { | 200 bool _matchesWithAncestors(Event event, String selector) { |
148 var target = event.target; | 201 var target = event.target; |
149 return target is Element ? target.matchesWithAncestors(selector) : false; | 202 return target is Element ? target.matchesWithAncestors(selector) : false; |
150 } | 203 } |
151 | 204 |
152 /** | 205 /** |
153 * Adapter for exposing DOM Element events as streams, while also allowing | 206 * Adapter for exposing DOM Element events as streams, while also allowing |
154 * event delegation. | 207 * event delegation. |
155 */ | 208 */ |
156 class _ElementEventStreamImpl<T extends Event> extends _EventStream<T> | 209 class _ElementEventStreamImpl<T extends Event> extends _EventStream<T> |
157 implements ElementStream<T> { | 210 implements ElementStream<T> { |
158 _ElementEventStreamImpl(target, eventType, useCapture) : | 211 _ElementEventStreamImpl(target, eventType, useCapture, |
159 super(target, eventType, useCapture); | 212 {String name, bool isOneShot: false}) : |
213 super(target, eventType, useCapture, name: name, isOneShot: isOneShot); | |
160 | 214 |
161 Stream<T> matches(String selector) => this.where( | 215 Stream<T> matches(String selector) => this.where( |
162 (event) => _matchesWithAncestors(event, selector)).map((e) { | 216 (event) => _matchesWithAncestors(event, selector)).map((e) { |
163 e._selector = selector; | 217 e._selector = selector; |
164 return e; | 218 return e; |
165 }); | 219 }); |
166 | 220 |
167 StreamSubscription<T> capture(void onData(T event)) => | 221 StreamSubscription<T> capture(void onData(T event)) { |
168 new _EventStreamSubscription<T>( | 222 |
169 this._target, this._eventType, onData, true); | 223 if (identical(Zone.current, Zone.ROOT)) { |
224 return new _EventStreamSubscription<T>( | |
225 this._target, this._eventType, onData, true, Zone.current); | |
226 } | |
227 | |
228 var specification = new EventSubscriptionSpecification<T>( | |
229 name: this._name, isOneShot: this._isOneShot, | |
230 target: this._target, eventType: this._eventType, | |
231 onData: onData, useCapture: true); | |
232 // We need to wrap the _createStreamSubscription call, since a tear-off | |
233 // would not bind the generic type 'T'. | |
234 return Zone.current.createTask((spec, Zone zone) { | |
235 return _createStreamSubscription/*<T>*/(spec, zone); | |
236 }, specification); | |
237 } | |
Lasse Reichstein Nielsen
2016/06/07 12:21:44
This function is almost identical to listen above.
floitsch
2016/06/10 15:43:58
Done.
| |
170 } | 238 } |
171 | 239 |
172 /** | 240 /** |
173 * Adapter for exposing events on a collection of DOM Elements as streams, | 241 * Adapter for exposing events on a collection of DOM Elements as streams, |
174 * while also allowing event delegation. | 242 * while also allowing event delegation. |
175 */ | 243 */ |
176 class _ElementListEventStreamImpl<T extends Event> extends Stream<T> | 244 class _ElementListEventStreamImpl<T extends Event> extends Stream<T> |
177 implements ElementStream<T> { | 245 implements ElementStream<T> { |
178 final Iterable<Element> _targetList; | 246 final Iterable<Element> _targetList; |
179 final bool _useCapture; | 247 final bool _useCapture; |
(...skipping 28 matching lines...) Expand all Loading... | |
208 } | 276 } |
209 return pool.stream.listen(onData); | 277 return pool.stream.listen(onData); |
210 } | 278 } |
211 | 279 |
212 Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription) , | 280 Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription) , |
213 void onCancel(StreamSubscription<T> subscription) }) | 281 void onCancel(StreamSubscription<T> subscription) }) |
214 => this; | 282 => this; |
215 bool get isBroadcast => true; | 283 bool get isBroadcast => true; |
216 } | 284 } |
217 | 285 |
218 // We would like this to just be EventListener<T> but that typdef cannot | 286 StreamSubscription/*<T>*/ _createStreamSubscription/*<T>*/( |
287 EventSubscriptionSpecification/*<T>*/ spec, Zone zone) { | |
288 return new _EventStreamSubscription/*<T>*/(spec.target, spec.eventType, | |
289 spec.onData, spec.useCapture, zone); | |
290 } | |
291 | |
292 // We would like this to just be EventListener<T> but that typedef cannot | |
219 // use generics until dartbug/26276 is fixed. | 293 // use generics until dartbug/26276 is fixed. |
220 typedef _EventListener<T extends Event>(T event); | 294 typedef _EventListener<T extends Event>(T event); |
221 | 295 |
222 class _EventStreamSubscription<T extends Event> extends StreamSubscription<T> { | 296 class _EventStreamSubscription<T extends Event> extends StreamSubscription<T> { |
223 int _pauseCount = 0; | 297 int _pauseCount = 0; |
224 EventTarget _target; | 298 EventTarget _target; |
225 final String _eventType; | 299 final String _eventType; |
226 EventListener _onData; | 300 EventListener _onData; |
301 EventListener _domCallback; | |
227 final bool _useCapture; | 302 final bool _useCapture; |
303 final Zone _zone; | |
228 | 304 |
229 // TODO(jacobr): for full strong mode correctness we should write | 305 // TODO(jacobr): for full strong mode correctness we should write |
230 // _onData = onData == null ? null : _wrapZone/*<Event, dynamic>*/((e) => onDa ta(e as T)) | 306 // _onData = onData == null ? null : _wrapZone/*<dynamic, Event>*/((e) => onDa ta(e as T)) |
231 // but that breaks 114 co19 tests as well as multiple html tests as it is reas onable | 307 // but that breaks 114 co19 tests as well as multiple html tests as it is reas onable |
232 // to pass the wrong type of event object to an event listener as part of a | 308 // to pass the wrong type of event object to an event listener as part of a |
233 // test. | 309 // test. |
234 _EventStreamSubscription(this._target, this._eventType, void onData(T event), | 310 _EventStreamSubscription(this._target, this._eventType, void onData(T event), |
235 this._useCapture) : _onData = _wrapZone/*<Event, dynamic>*/(onData) { | 311 this._useCapture, Zone zone) |
312 : _zone = zone, | |
313 _onData = _registerZone/*<dynamic, Event>*/(zone, onData) { | |
236 _tryResume(); | 314 _tryResume(); |
237 } | 315 } |
238 | 316 |
239 Future cancel() { | 317 Future cancel() { |
240 if (_canceled) return null; | 318 if (_canceled) return null; |
241 | 319 |
242 _unlisten(); | 320 _unlisten(); |
243 // Clear out the target to indicate this is complete. | 321 // Clear out the target to indicate this is complete. |
244 _target = null; | 322 _target = null; |
245 _onData = null; | 323 _onData = null; |
246 return null; | 324 return null; |
247 } | 325 } |
248 | 326 |
249 bool get _canceled => _target == null; | 327 bool get _canceled => _target == null; |
250 | 328 |
251 void onData(void handleData(T event)) { | 329 void onData(void handleData(T event)) { |
252 if (_canceled) { | 330 if (_canceled) { |
253 throw new StateError("Subscription has been canceled."); | 331 throw new StateError("Subscription has been canceled."); |
254 } | 332 } |
255 // Remove current event listener. | 333 // Remove current event listener. |
256 _unlisten(); | 334 _unlisten(); |
257 _onData = _wrapZone/*<Event, dynamic>*/(handleData); | 335 _onData = _registerZone/*<dynamic, Event>*/(_zone, handleData); |
258 _tryResume(); | 336 _tryResume(); |
259 } | 337 } |
260 | 338 |
261 /// Has no effect. | 339 /// Has no effect. |
262 void onError(Function handleError) {} | 340 void onError(Function handleError) {} |
263 | 341 |
264 /// Has no effect. | 342 /// Has no effect. |
265 void onDone(void handleDone()) {} | 343 void onDone(void handleDone()) {} |
266 | 344 |
267 void pause([Future resumeSignal]) { | 345 void pause([Future resumeSignal]) { |
268 if (_canceled) return; | 346 if (_canceled) return; |
269 ++_pauseCount; | 347 ++_pauseCount; |
270 _unlisten(); | 348 _unlisten(); |
271 | 349 |
272 if (resumeSignal != null) { | 350 if (resumeSignal != null) { |
273 resumeSignal.whenComplete(resume); | 351 resumeSignal.whenComplete(resume); |
274 } | 352 } |
275 } | 353 } |
276 | 354 |
277 bool get isPaused => _pauseCount > 0; | 355 bool get isPaused => _pauseCount > 0; |
278 | 356 |
279 void resume() { | 357 void resume() { |
280 if (_canceled || !isPaused) return; | 358 if (_canceled || !isPaused) return; |
281 --_pauseCount; | 359 --_pauseCount; |
282 _tryResume(); | 360 _tryResume(); |
283 } | 361 } |
284 | 362 |
285 void _tryResume() { | 363 void _tryResume() { |
286 if (_onData != null && !isPaused) { | 364 if (_onData == null || isPaused) return; |
287 _target.addEventListener(_eventType, _onData, _useCapture); | 365 if (identical(_zone, Zone.ROOT)) { |
366 _domCallback = _onData; | |
367 } else { | |
368 _domCallback = (event) { | |
369 _zone.runTask(_runEventNotification, this, event); | |
370 }; | |
288 } | 371 } |
372 _target.addEventListener(_eventType, _domCallback, _useCapture); | |
373 } | |
374 | |
375 static void _runEventNotification/*<T>*/( | |
376 _EventStreamSubscription/*<T>*/ subscription, /*=T*/ event) { | |
377 subscription._onData(event); | |
289 } | 378 } |
290 | 379 |
291 void _unlisten() { | 380 void _unlisten() { |
292 if (_onData != null) { | 381 if (_onData != null) { |
293 _target.removeEventListener(_eventType, _onData, _useCapture); | 382 _target.removeEventListener(_eventType, _domCallback, _useCapture); |
294 } | 383 } |
295 } | 384 } |
296 | 385 |
297 Future/*<E>*/ asFuture/*<E>*/([var/*=E*/ futureValue]) { | 386 Future/*<E>*/ asFuture/*<E>*/([var/*=E*/ futureValue]) { |
298 // We just need a future that will never succeed or fail. | 387 // We just need a future that will never succeed or fail. |
299 var completer = new Completer/*<E>*/(); | 388 var completer = new Completer/*<E>*/(); |
300 return completer.future; | 389 return completer.future; |
301 } | 390 } |
302 } | 391 } |
303 | 392 |
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
437 return new _ElementListEventStreamImpl<T>(e, _eventTypeGetter(e), useCapture ); | 526 return new _ElementListEventStreamImpl<T>(e, _eventTypeGetter(e), useCapture ); |
438 } | 527 } |
439 | 528 |
440 String getEventType(EventTarget target) { | 529 String getEventType(EventTarget target) { |
441 return _eventTypeGetter(target); | 530 return _eventTypeGetter(target); |
442 } | 531 } |
443 | 532 |
444 String get _eventType => | 533 String get _eventType => |
445 throw new UnsupportedError('Access type through getEventType method.'); | 534 throw new UnsupportedError('Access type through getEventType method.'); |
446 } | 535 } |
OLD | NEW |