|  | // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | part of html; | 
|  |  | 
|  | /** | 
|  | * A factory to expose DOM events as Streams. | 
|  | */ | 
|  | class EventStreamProvider<T extends Event> { | 
|  | final String _eventType; | 
|  |  | 
|  | const EventStreamProvider(this._eventType); | 
|  |  | 
|  | /** | 
|  | * Gets a [Stream] for this event type, on the specified target. | 
|  | * | 
|  | * This will always return a broadcast stream so multiple listeners can be | 
|  | * used simultaneously. | 
|  | * | 
|  | * This may be used to capture DOM events: | 
|  | * | 
|  | *     Element.keyDownEvent.forTarget(element, useCapture: true).listen(...); | 
|  | * | 
|  | *     // Alternate method: | 
|  | *     Element.keyDownEvent.forTarget(element).capture(...); | 
|  | * | 
|  | * Or for listening to an event which will bubble through the DOM tree: | 
|  | * | 
|  | *     MediaElement.pauseEvent.forTarget(document.body).listen(...); | 
|  | * | 
|  | * See also: | 
|  | * | 
|  | * [addEventListener](http://docs.webplatform.org/wiki/dom/methods/addEventListener) | 
|  | */ | 
|  | Stream<T> forTarget(EventTarget e, {bool useCapture: false}) => | 
|  | new _EventStream(e, _eventType, useCapture); | 
|  |  | 
|  | /** | 
|  | * Gets an [ElementEventStream] for this event type, on the specified element. | 
|  | * | 
|  | * This will always return a broadcast stream so multiple listeners can be | 
|  | * used simultaneously. | 
|  | * | 
|  | * This may be used to capture DOM events: | 
|  | * | 
|  | *     Element.keyDownEvent.forElement(element, useCapture: true).listen(...); | 
|  | * | 
|  | *     // Alternate method: | 
|  | *     Element.keyDownEvent.forElement(element).capture(...); | 
|  | * | 
|  | * Or for listening to an event which will bubble through the DOM tree: | 
|  | * | 
|  | *     MediaElement.pauseEvent.forElement(document.body).listen(...); | 
|  | * | 
|  | * See also: | 
|  | * | 
|  | * [addEventListener](http://docs.webplatform.org/wiki/dom/methods/addEventListener) | 
|  | */ | 
|  | ElementStream<T> forElement(Element e, {bool useCapture: false}) { | 
|  | return new _ElementEventStreamImpl(e, _eventType, useCapture); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets an [ElementEventStream] for this event type, on the list of elements. | 
|  | * | 
|  | * This will always return a broadcast stream so multiple listeners can be | 
|  | * used simultaneously. | 
|  | * | 
|  | * This may be used to capture DOM events: | 
|  | * | 
|  | *     Element.keyDownEvent._forElementList(element, useCapture: true).listen(...); | 
|  | * | 
|  | * See also: | 
|  | * | 
|  | * [addEventListener](http://docs.webplatform.org/wiki/dom/methods/addEventListener) | 
|  | */ | 
|  | ElementStream<T> _forElementList(ElementList e, {bool useCapture: false}) { | 
|  | return new _ElementListEventStreamImpl(e, _eventType, useCapture); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the type of the event which this would listen for on the specified | 
|  | * event target. | 
|  | * | 
|  | * The target is necessary because some browsers may use different event names | 
|  | * for the same purpose and the target allows differentiating browser support. | 
|  | */ | 
|  | String getEventType(EventTarget target) { | 
|  | return _eventType; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** A specialized Stream available to [Element]s to enable event delegation. */ | 
|  | abstract class ElementStream<T extends Event> implements Stream<T> { | 
|  | /** | 
|  | * Return a stream that only fires when the particular event fires for | 
|  | * elements matching the specified CSS selector. | 
|  | * | 
|  | * This is the Dart equivalent to jQuery's | 
|  | * [delegate](http://api.jquery.com/delegate/). | 
|  | */ | 
|  | Stream<T> matches(String selector); | 
|  |  | 
|  | /** | 
|  | * Adds a capturing subscription to this stream. | 
|  | * | 
|  | * If the target of the event is a descendant of the element from which this | 
|  | * stream derives then [onData] is called before the event propagates down to | 
|  | * the target. This is the opposite of bubbling behavior, where the event | 
|  | * is first processed for the event target and then bubbles upward. | 
|  | * | 
|  | * ## Other resources | 
|  | * | 
|  | * * [Event Capture] | 
|  | * (http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow-capture) | 
|  | * from the W3C DOM Events specification. | 
|  | */ | 
|  | StreamSubscription<T> capture(void onData(T event)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adapter for exposing DOM events as Dart streams. | 
|  | */ | 
|  | class _EventStream<T extends Event> extends Stream<T> { | 
|  | final EventTarget _target; | 
|  | final String _eventType; | 
|  | final bool _useCapture; | 
|  |  | 
|  | _EventStream(this._target, this._eventType, this._useCapture); | 
|  |  | 
|  | // DOM events are inherently multi-subscribers. | 
|  | Stream<T> asBroadcastStream({void onListen(StreamSubscription subscription), | 
|  | void onCancel(StreamSubscription subscription)}) | 
|  | => this; | 
|  | bool get isBroadcast => true; | 
|  |  | 
|  | StreamSubscription<T> listen(void onData(T event), | 
|  | { Function onError, | 
|  | void onDone(), | 
|  | bool cancelOnError}) { | 
|  |  | 
|  | return new _EventStreamSubscription<T>( | 
|  | this._target, this._eventType, onData, this._useCapture); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adapter for exposing DOM Element events as streams, while also allowing | 
|  | * event delegation. | 
|  | */ | 
|  | class _ElementEventStreamImpl<T extends Event> extends _EventStream<T> | 
|  | implements ElementStream<T> { | 
|  | _ElementEventStreamImpl(target, eventType, useCapture) : | 
|  | super(target, eventType, useCapture); | 
|  |  | 
|  | Stream<T> matches(String selector) => this.where( | 
|  | (event) => event.target.matchesWithAncestors(selector)).map((e) { | 
|  | e._selector = selector; | 
|  | return e; | 
|  | }); | 
|  |  | 
|  | StreamSubscription<T> capture(void onData(T event)) => | 
|  | new _EventStreamSubscription<T>( | 
|  | this._target, this._eventType, onData, true); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adapter for exposing events on a collection of DOM Elements as streams, | 
|  | * while also allowing event delegation. | 
|  | */ | 
|  | class _ElementListEventStreamImpl<T extends Event> extends Stream<T> | 
|  | implements ElementStream<T> { | 
|  | final Iterable<Element> _targetList; | 
|  | final bool _useCapture; | 
|  | final String _eventType; | 
|  |  | 
|  | _ElementListEventStreamImpl( | 
|  | this._targetList, this._eventType, this._useCapture); | 
|  |  | 
|  | Stream<T> matches(String selector) => this.where( | 
|  | (event) => event.target.matchesWithAncestors(selector)).map((e) { | 
|  | e._selector = selector; | 
|  | return e; | 
|  | }); | 
|  |  | 
|  | // Delegate all regular Stream behavior to a wrapped Stream. | 
|  | StreamSubscription<T> listen(void onData(T event), | 
|  | { Function onError, | 
|  | void onDone(), | 
|  | bool cancelOnError}) { | 
|  | var pool = new _StreamPool.broadcast(); | 
|  | for (var target in _targetList) { | 
|  | pool.add(new _EventStream(target, _eventType, _useCapture)); | 
|  | } | 
|  | return pool.stream.listen(onData, onError: onError, onDone: onDone, | 
|  | cancelOnError: cancelOnError); | 
|  | } | 
|  |  | 
|  | StreamSubscription<T> capture(void onData(T event)) { | 
|  | var pool = new _StreamPool.broadcast(); | 
|  | for (var target in _targetList) { | 
|  | pool.add(new _EventStream(target, _eventType, true)); | 
|  | } | 
|  | return pool.stream.listen(onData); | 
|  | } | 
|  |  | 
|  | Stream<T> asBroadcastStream({void onListen(StreamSubscription subscription), | 
|  | void onCancel(StreamSubscription subscription)}) | 
|  | => this; | 
|  | bool get isBroadcast => true; | 
|  | } | 
|  |  | 
|  | class _EventStreamSubscription<T extends Event> extends StreamSubscription<T> { | 
|  | int _pauseCount = 0; | 
|  | EventTarget _target; | 
|  | final String _eventType; | 
|  | var _onData; | 
|  | final bool _useCapture; | 
|  |  | 
|  | _EventStreamSubscription(this._target, this._eventType, onData, | 
|  | this._useCapture) : _onData = _wrapZone(onData) { | 
|  | _tryResume(); | 
|  | } | 
|  |  | 
|  | Future cancel() { | 
|  | if (_canceled) return null; | 
|  |  | 
|  | _unlisten(); | 
|  | // Clear out the target to indicate this is complete. | 
|  | _target = null; | 
|  | _onData = null; | 
|  | return null; | 
|  | } | 
|  |  | 
|  | bool get _canceled => _target == null; | 
|  |  | 
|  | void onData(void handleData(T event)) { | 
|  | if (_canceled) { | 
|  | throw new StateError("Subscription has been canceled."); | 
|  | } | 
|  | // Remove current event listener. | 
|  | _unlisten(); | 
|  |  | 
|  | _onData = _wrapZone(handleData); | 
|  | _tryResume(); | 
|  | } | 
|  |  | 
|  | /// Has no effect. | 
|  | void onError(Function handleError) {} | 
|  |  | 
|  | /// Has no effect. | 
|  | void onDone(void handleDone()) {} | 
|  |  | 
|  | void pause([Future resumeSignal]) { | 
|  | if (_canceled) return; | 
|  | ++_pauseCount; | 
|  | _unlisten(); | 
|  |  | 
|  | if (resumeSignal != null) { | 
|  | resumeSignal.whenComplete(resume); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool get isPaused => _pauseCount > 0; | 
|  |  | 
|  | void resume() { | 
|  | if (_canceled || !isPaused) return; | 
|  | --_pauseCount; | 
|  | _tryResume(); | 
|  | } | 
|  |  | 
|  | void _tryResume() { | 
|  | if (_onData != null && !isPaused) { | 
|  | _target.addEventListener(_eventType, _onData, _useCapture); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _unlisten() { | 
|  | if (_onData != null) { | 
|  | _target.removeEventListener(_eventType, _onData, _useCapture); | 
|  | } | 
|  | } | 
|  |  | 
|  | Future asFuture([var futureValue]) { | 
|  | // We just need a future that will never succeed or fail. | 
|  | Completer completer = new Completer(); | 
|  | return completer.future; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A stream of custom events, which enables the user to "fire" (add) their own | 
|  | * custom events to a stream. | 
|  | */ | 
|  | abstract class CustomStream<T extends Event> implements Stream<T> { | 
|  | /** | 
|  | * Add the following custom event to the stream for dispatching to interested | 
|  | * listeners. | 
|  | */ | 
|  | void add(T event); | 
|  | } | 
|  |  | 
|  | class _CustomEventStreamImpl<T extends Event> extends Stream<T> | 
|  | implements CustomStream<T> { | 
|  | StreamController<T> _streamController; | 
|  | /** The type of event this stream is providing (e.g. "keydown"). */ | 
|  | String _type; | 
|  |  | 
|  | _CustomEventStreamImpl(String type) { | 
|  | _type = type; | 
|  | _streamController = new StreamController.broadcast(sync: true); | 
|  | } | 
|  |  | 
|  | // Delegate all regular Stream behavior to our wrapped Stream. | 
|  | StreamSubscription<T> listen(void onData(T event), | 
|  | { Function onError, | 
|  | void onDone(), | 
|  | bool cancelOnError}) { | 
|  | return _streamController.stream.listen(onData, onError: onError, | 
|  | onDone: onDone, cancelOnError: cancelOnError); | 
|  | } | 
|  |  | 
|  | Stream<T> asBroadcastStream({void onListen(StreamSubscription subscription), | 
|  | void onCancel(StreamSubscription subscription)}) | 
|  | => _streamController.stream; | 
|  |  | 
|  | bool get isBroadcast => true; | 
|  |  | 
|  | void add(T event) { | 
|  | if (event.type == _type) _streamController.add(event); | 
|  | } | 
|  | } | 
|  |  | 
|  | class _CustomKeyEventStreamImpl extends _CustomEventStreamImpl<KeyEvent> | 
|  | implements CustomStream<KeyEvent> { | 
|  | _CustomKeyEventStreamImpl(String type) : super(type); | 
|  |  | 
|  | void add(KeyEvent event) { | 
|  | if (event.type == _type) { | 
|  | event.currentTarget.dispatchEvent(event._parent); | 
|  | _streamController.add(event); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A pool of streams whose events are unified and emitted through a central | 
|  | * stream. | 
|  | */ | 
|  | // TODO (efortuna): Remove this when Issue 12218 is addressed. | 
|  | class _StreamPool<T> { | 
|  | StreamController<T> _controller; | 
|  |  | 
|  | /// Subscriptions to the streams that make up the pool. | 
|  | var _subscriptions = new Map<Stream<T>, StreamSubscription<T>>(); | 
|  |  | 
|  | /** | 
|  | * Creates a new stream pool where [stream] can be listened to more than | 
|  | * once. | 
|  | * | 
|  | * Any events from buffered streams in the pool will be emitted immediately, | 
|  | * regardless of whether [stream] has any subscribers. | 
|  | */ | 
|  | _StreamPool.broadcast() { | 
|  | _controller = new StreamController<T>.broadcast(sync: true, | 
|  | onCancel: close); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * The stream through which all events from streams in the pool are emitted. | 
|  | */ | 
|  | Stream<T> get stream => _controller.stream; | 
|  |  | 
|  | /** | 
|  | * Adds [stream] as a member of this pool. | 
|  | * | 
|  | * Any events from [stream] will be emitted through [this.stream]. If | 
|  | * [stream] is sync, they'll be emitted synchronously; if [stream] is async, | 
|  | * they'll be emitted asynchronously. | 
|  | */ | 
|  | void add(Stream<T> stream) { | 
|  | if (_subscriptions.containsKey(stream)) return; | 
|  | _subscriptions[stream] = stream.listen(_controller.add, | 
|  | onError: _controller.addError, | 
|  | onDone: () => remove(stream)); | 
|  | } | 
|  |  | 
|  | /** Removes [stream] as a member of this pool. */ | 
|  | void remove(Stream<T> stream) { | 
|  | var subscription = _subscriptions.remove(stream); | 
|  | if (subscription != null) subscription.cancel(); | 
|  | } | 
|  |  | 
|  | /** Removes all streams from this pool and closes [stream]. */ | 
|  | void close() { | 
|  | for (var subscription in _subscriptions.values) { | 
|  | subscription.cancel(); | 
|  | } | 
|  | _subscriptions.clear(); | 
|  | _controller.close(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A factory to expose DOM events as streams, where the DOM event name has to | 
|  | * be determined on the fly (for example, mouse wheel events). | 
|  | */ | 
|  | class _CustomEventStreamProvider<T extends Event> | 
|  | implements EventStreamProvider<T> { | 
|  |  | 
|  | final _eventTypeGetter; | 
|  | const _CustomEventStreamProvider(this._eventTypeGetter); | 
|  |  | 
|  | Stream<T> forTarget(EventTarget e, {bool useCapture: false}) { | 
|  | return new _EventStream(e, _eventTypeGetter(e), useCapture); | 
|  | } | 
|  |  | 
|  | ElementStream<T> forElement(Element e, {bool useCapture: false}) { | 
|  | return new _ElementEventStreamImpl(e, _eventTypeGetter(e), useCapture); | 
|  | } | 
|  |  | 
|  | ElementStream<T> _forElementList(ElementList e, | 
|  | {bool useCapture: false}) { | 
|  | return new _ElementListEventStreamImpl(e, _eventTypeGetter(e), useCapture); | 
|  | } | 
|  |  | 
|  | String getEventType(EventTarget target) { | 
|  | return _eventTypeGetter(target); | 
|  | } | 
|  |  | 
|  | String get _eventType => | 
|  | throw new UnsupportedError('Access type through getEventType method.'); | 
|  | } |