1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer
12 *    in the documentation and/or other materials provided with the
13 *    distribution.
14 * 3. Neither the name of Google Inc. nor the names of its contributors
15 *    may be used to endorse or promote products derived from this
16 *    software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#include "modules/mediastream/RTCPeerConnection.h"
34
35#include "bindings/v8/ArrayValue.h"
36#include "bindings/v8/ExceptionState.h"
37#include "core/dom/Document.h"
38#include "core/dom/Event.h"
39#include "core/dom/ExceptionCode.h"
40#include "core/dom/ScriptExecutionContext.h"
41#include "core/html/VoidCallback.h"
42#include "core/loader/FrameLoader.h"
43#include "core/loader/FrameLoaderClient.h"
44#include "core/page/Frame.h"
45#include "core/platform/mediastream/RTCConfiguration.h"
46#include "core/platform/mediastream/RTCDataChannelHandler.h"
47#include "modules/mediastream/MediaConstraintsImpl.h"
48#include "modules/mediastream/MediaStreamEvent.h"
49#include "modules/mediastream/RTCDTMFSender.h"
50#include "modules/mediastream/RTCDataChannel.h"
51#include "modules/mediastream/RTCDataChannelEvent.h"
52#include "modules/mediastream/RTCErrorCallback.h"
53#include "modules/mediastream/RTCIceCandidate.h"
54#include "modules/mediastream/RTCIceCandidateEvent.h"
55#include "modules/mediastream/RTCSessionDescription.h"
56#include "modules/mediastream/RTCSessionDescriptionCallback.h"
57#include "modules/mediastream/RTCSessionDescriptionRequestImpl.h"
58#include "modules/mediastream/RTCStatsCallback.h"
59#include "modules/mediastream/RTCStatsRequestImpl.h"
60#include "modules/mediastream/RTCVoidRequestImpl.h"
61#include "public/platform/WebRTCDataChannelInit.h"
62#include "public/platform/WebRTCICECandidate.h"
63#include "public/platform/WebRTCSessionDescription.h"
64
65namespace WebCore {
66
67PassRefPtr<RTCConfiguration> RTCPeerConnection::parseConfiguration(const Dictionary& configuration, ExceptionState& es)
68{
69    if (configuration.isUndefinedOrNull())
70        return 0;
71
72    ArrayValue iceServers;
73    bool ok = configuration.get("iceServers", iceServers);
74    if (!ok || iceServers.isUndefinedOrNull()) {
75        es.throwDOMException(TypeMismatchError);
76        return 0;
77    }
78
79    size_t numberOfServers;
80    ok = iceServers.length(numberOfServers);
81    if (!ok) {
82        es.throwDOMException(TypeMismatchError);
83        return 0;
84    }
85
86    RefPtr<RTCConfiguration> rtcConfiguration = RTCConfiguration::create();
87
88    for (size_t i = 0; i < numberOfServers; ++i) {
89        Dictionary iceServer;
90        ok = iceServers.get(i, iceServer);
91        if (!ok) {
92            es.throwDOMException(TypeMismatchError);
93            return 0;
94        }
95
96        String urlString, username, credential;
97        ok = iceServer.get("url", urlString);
98        if (!ok) {
99            es.throwDOMException(TypeMismatchError);
100            return 0;
101        }
102        KURL url(KURL(), urlString);
103        if (!url.isValid() || !(url.protocolIs("turn") || url.protocolIs("turns") || url.protocolIs("stun"))) {
104            es.throwDOMException(TypeMismatchError);
105            return 0;
106        }
107
108        iceServer.get("username", username);
109        iceServer.get("credential", credential);
110
111        rtcConfiguration->appendServer(RTCIceServer::create(url, username, credential));
112    }
113
114    return rtcConfiguration.release();
115}
116
117PassRefPtr<RTCPeerConnection> RTCPeerConnection::create(ScriptExecutionContext* context, const Dictionary& rtcConfiguration, const Dictionary& mediaConstraints, ExceptionState& es)
118{
119    RefPtr<RTCConfiguration> configuration = parseConfiguration(rtcConfiguration, es);
120    if (es.hadException())
121        return 0;
122
123    RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, es);
124    if (es.hadException())
125        return 0;
126
127    RefPtr<RTCPeerConnection> peerConnection = adoptRef(new RTCPeerConnection(context, configuration.release(), constraints.release(), es));
128    peerConnection->suspendIfNeeded();
129    if (es.hadException())
130        return 0;
131
132    return peerConnection.release();
133}
134
135RTCPeerConnection::RTCPeerConnection(ScriptExecutionContext* context, PassRefPtr<RTCConfiguration> configuration, PassRefPtr<MediaConstraints> constraints, ExceptionState& es)
136    : ActiveDOMObject(context)
137    , m_signalingState(SignalingStateStable)
138    , m_iceGatheringState(IceGatheringStateNew)
139    , m_iceConnectionState(IceConnectionStateNew)
140    , m_scheduledEventTimer(this, &RTCPeerConnection::scheduledEventTimerFired)
141    , m_stopped(false)
142{
143    ScriptWrappable::init(this);
144    Document* document = toDocument(scriptExecutionContext());
145
146    if (!document->frame()) {
147        es.throwDOMException(NotSupportedError);
148        return;
149    }
150
151    m_peerHandler = RTCPeerConnectionHandler::create(this);
152    if (!m_peerHandler) {
153        es.throwDOMException(NotSupportedError);
154        return;
155    }
156
157    document->frame()->loader()->client()->dispatchWillStartUsingPeerConnectionHandler(m_peerHandler.get());
158
159    if (!m_peerHandler->initialize(configuration, constraints)) {
160        es.throwDOMException(NotSupportedError);
161        return;
162    }
163}
164
165RTCPeerConnection::~RTCPeerConnection()
166{
167    stop();
168}
169
170void RTCPeerConnection::createOffer(PassRefPtr<RTCSessionDescriptionCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, const Dictionary& mediaConstraints, ExceptionState& es)
171{
172    if (m_signalingState == SignalingStateClosed) {
173        es.throwDOMException(InvalidStateError);
174        return;
175    }
176
177    if (!successCallback) {
178        es.throwDOMException(TypeMismatchError);
179        return;
180    }
181
182    RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, es);
183    if (es.hadException())
184        return;
185
186    RefPtr<RTCSessionDescriptionRequestImpl> request = RTCSessionDescriptionRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback);
187    m_peerHandler->createOffer(request.release(), constraints);
188}
189
190void RTCPeerConnection::createAnswer(PassRefPtr<RTCSessionDescriptionCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, const Dictionary& mediaConstraints, ExceptionState& es)
191{
192    if (m_signalingState == SignalingStateClosed) {
193        es.throwDOMException(InvalidStateError);
194        return;
195    }
196
197    if (!successCallback) {
198        es.throwDOMException(TypeMismatchError);
199        return;
200    }
201
202    RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, es);
203    if (es.hadException())
204        return;
205
206    RefPtr<RTCSessionDescriptionRequestImpl> request = RTCSessionDescriptionRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback);
207    m_peerHandler->createAnswer(request.release(), constraints.release());
208}
209
210void RTCPeerConnection::setLocalDescription(PassRefPtr<RTCSessionDescription> prpSessionDescription, PassRefPtr<VoidCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, ExceptionState& es)
211{
212    if (m_signalingState == SignalingStateClosed) {
213        es.throwDOMException(InvalidStateError);
214        return;
215    }
216
217    RefPtr<RTCSessionDescription> sessionDescription = prpSessionDescription;
218    if (!sessionDescription) {
219        es.throwDOMException(TypeMismatchError);
220        return;
221    }
222
223    RefPtr<RTCVoidRequestImpl> request = RTCVoidRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback);
224    m_peerHandler->setLocalDescription(request.release(), sessionDescription->webSessionDescription());
225}
226
227PassRefPtr<RTCSessionDescription> RTCPeerConnection::localDescription(ExceptionState& es)
228{
229    WebKit::WebRTCSessionDescription webSessionDescription = m_peerHandler->localDescription();
230    if (webSessionDescription.isNull())
231        return 0;
232
233    RefPtr<RTCSessionDescription> sessionDescription = RTCSessionDescription::create(webSessionDescription);
234    return sessionDescription.release();
235}
236
237void RTCPeerConnection::setRemoteDescription(PassRefPtr<RTCSessionDescription> prpSessionDescription, PassRefPtr<VoidCallback> successCallback, PassRefPtr<RTCErrorCallback> errorCallback, ExceptionState& es)
238{
239    if (m_signalingState == SignalingStateClosed) {
240        es.throwDOMException(InvalidStateError);
241        return;
242    }
243
244    RefPtr<RTCSessionDescription> sessionDescription = prpSessionDescription;
245    if (!sessionDescription) {
246        es.throwDOMException(TypeMismatchError);
247        return;
248    }
249
250    RefPtr<RTCVoidRequestImpl> request = RTCVoidRequestImpl::create(scriptExecutionContext(), successCallback, errorCallback);
251    m_peerHandler->setRemoteDescription(request.release(), sessionDescription->webSessionDescription());
252}
253
254PassRefPtr<RTCSessionDescription> RTCPeerConnection::remoteDescription(ExceptionState& es)
255{
256    WebKit::WebRTCSessionDescription webSessionDescription = m_peerHandler->remoteDescription();
257    if (webSessionDescription.isNull())
258        return 0;
259
260    RefPtr<RTCSessionDescription> desc = RTCSessionDescription::create(webSessionDescription);
261    return desc.release();
262}
263
264void RTCPeerConnection::updateIce(const Dictionary& rtcConfiguration, const Dictionary& mediaConstraints, ExceptionState& es)
265{
266    if (m_signalingState == SignalingStateClosed) {
267        es.throwDOMException(InvalidStateError);
268        return;
269    }
270
271    RefPtr<RTCConfiguration> configuration = parseConfiguration(rtcConfiguration, es);
272    if (es.hadException())
273        return;
274
275    RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, es);
276    if (es.hadException())
277        return;
278
279    bool valid = m_peerHandler->updateIce(configuration, constraints);
280    if (!valid)
281        es.throwDOMException(SyntaxError);
282}
283
284void RTCPeerConnection::addIceCandidate(RTCIceCandidate* iceCandidate, ExceptionState& es)
285{
286    if (m_signalingState == SignalingStateClosed) {
287        es.throwDOMException(InvalidStateError);
288        return;
289    }
290
291    if (!iceCandidate) {
292        es.throwDOMException(TypeMismatchError);
293        return;
294    }
295
296    bool valid = m_peerHandler->addIceCandidate(iceCandidate->webCandidate());
297    if (!valid)
298        es.throwDOMException(SyntaxError);
299}
300
301String RTCPeerConnection::signalingState() const
302{
303    switch (m_signalingState) {
304    case SignalingStateStable:
305        return "stable";
306    case SignalingStateHaveLocalOffer:
307        return "have-local-offer";
308    case SignalingStateHaveRemoteOffer:
309        return "have-remote-offer";
310    case SignalingStateHaveLocalPrAnswer:
311        return "have-local-pranswer";
312    case SignalingStateHaveRemotePrAnswer:
313        return "have-remote-pranswer";
314    case SignalingStateClosed:
315        return "closed";
316    }
317
318    ASSERT_NOT_REACHED();
319    return String();
320}
321
322String RTCPeerConnection::iceGatheringState() const
323{
324    switch (m_iceGatheringState) {
325    case IceGatheringStateNew:
326        return "new";
327    case IceGatheringStateGathering:
328        return "gathering";
329    case IceGatheringStateComplete:
330        return "complete";
331    }
332
333    ASSERT_NOT_REACHED();
334    return String();
335}
336
337String RTCPeerConnection::iceConnectionState() const
338{
339    switch (m_iceConnectionState) {
340    case IceConnectionStateNew:
341        return "new";
342    case IceConnectionStateChecking:
343        return "checking";
344    case IceConnectionStateConnected:
345        return "connected";
346    case IceConnectionStateCompleted:
347        return "completed";
348    case IceConnectionStateFailed:
349        return "failed";
350    case IceConnectionStateDisconnected:
351        return "disconnected";
352    case IceConnectionStateClosed:
353        return "closed";
354    }
355
356    ASSERT_NOT_REACHED();
357    return String();
358}
359
360void RTCPeerConnection::addStream(PassRefPtr<MediaStream> prpStream, const Dictionary& mediaConstraints, ExceptionState& es)
361{
362    if (m_signalingState == SignalingStateClosed) {
363        es.throwDOMException(InvalidStateError);
364        return;
365    }
366
367    RefPtr<MediaStream> stream = prpStream;
368    if (!stream) {
369        es.throwDOMException(TypeMismatchError);
370        return;
371    }
372
373    if (m_localStreams.contains(stream))
374        return;
375
376    RefPtr<MediaConstraints> constraints = MediaConstraintsImpl::create(mediaConstraints, es);
377    if (es.hadException())
378        return;
379
380    m_localStreams.append(stream);
381
382    bool valid = m_peerHandler->addStream(stream->descriptor(), constraints);
383    if (!valid)
384        es.throwDOMException(SyntaxError);
385}
386
387void RTCPeerConnection::removeStream(PassRefPtr<MediaStream> prpStream, ExceptionState& es)
388{
389    if (m_signalingState == SignalingStateClosed) {
390        es.throwDOMException(InvalidStateError);
391        return;
392    }
393
394    if (!prpStream) {
395        es.throwDOMException(TypeMismatchError);
396        return;
397    }
398
399    RefPtr<MediaStream> stream = prpStream;
400
401    size_t pos = m_localStreams.find(stream);
402    if (pos == notFound)
403        return;
404
405    m_localStreams.remove(pos);
406
407    m_peerHandler->removeStream(stream->descriptor());
408}
409
410MediaStreamVector RTCPeerConnection::getLocalStreams() const
411{
412    return m_localStreams;
413}
414
415MediaStreamVector RTCPeerConnection::getRemoteStreams() const
416{
417    return m_remoteStreams;
418}
419
420MediaStream* RTCPeerConnection::getStreamById(const String& streamId)
421{
422    for (MediaStreamVector::iterator iter = m_localStreams.begin(); iter != m_localStreams.end(); ++iter) {
423        if ((*iter)->id() == streamId)
424            return iter->get();
425    }
426
427    for (MediaStreamVector::iterator iter = m_remoteStreams.begin(); iter != m_remoteStreams.end(); ++iter) {
428        if ((*iter)->id() == streamId)
429            return iter->get();
430    }
431
432    return 0;
433}
434
435void RTCPeerConnection::getStats(PassRefPtr<RTCStatsCallback> successCallback, PassRefPtr<MediaStreamTrack> selector)
436{
437    RefPtr<RTCStatsRequestImpl> statsRequest = RTCStatsRequestImpl::create(scriptExecutionContext(), successCallback, selector);
438    // FIXME: Add passing selector as part of the statsRequest.
439    m_peerHandler->getStats(statsRequest.release());
440}
441
442PassRefPtr<RTCDataChannel> RTCPeerConnection::createDataChannel(String label, const Dictionary& options, ExceptionState& es)
443{
444    if (m_signalingState == SignalingStateClosed) {
445        es.throwDOMException(InvalidStateError);
446        return 0;
447    }
448
449    WebKit::WebRTCDataChannelInit init;
450    options.get("ordered", init.ordered);
451    options.get("negotiated", init.negotiated);
452
453    unsigned short value = 0;
454    if (options.get("id", value))
455        init.id = value;
456    if (options.get("maxRetransmits", value))
457        init.maxRetransmits = value;
458    if (options.get("maxRetransmitTime", value))
459        init.maxRetransmitTime = value;
460
461    String protocolString;
462    options.get("protocol", protocolString);
463    init.protocol = protocolString;
464
465    RefPtr<RTCDataChannel> channel = RTCDataChannel::create(scriptExecutionContext(), m_peerHandler.get(), label, init, es);
466    if (es.hadException())
467        return 0;
468    m_dataChannels.append(channel);
469    return channel.release();
470}
471
472bool RTCPeerConnection::hasLocalStreamWithTrackId(const String& trackId)
473{
474    for (MediaStreamVector::iterator iter = m_localStreams.begin(); iter != m_localStreams.end(); ++iter) {
475        if ((*iter)->getTrackById(trackId))
476            return true;
477    }
478    return false;
479}
480
481PassRefPtr<RTCDTMFSender> RTCPeerConnection::createDTMFSender(PassRefPtr<MediaStreamTrack> prpTrack, ExceptionState& es)
482{
483    if (m_signalingState == SignalingStateClosed) {
484        es.throwDOMException(InvalidStateError);
485        return 0;
486    }
487
488    if (!prpTrack) {
489        es.throwTypeError();
490        return 0;
491    }
492
493    RefPtr<MediaStreamTrack> track = prpTrack;
494
495    if (!hasLocalStreamWithTrackId(track->id())) {
496        es.throwDOMException(SyntaxError);
497        return 0;
498    }
499
500    RefPtr<RTCDTMFSender> dtmfSender = RTCDTMFSender::create(scriptExecutionContext(), m_peerHandler.get(), track.release(), es);
501    if (es.hadException())
502        return 0;
503    return dtmfSender.release();
504}
505
506void RTCPeerConnection::close(ExceptionState& es)
507{
508    if (m_signalingState == SignalingStateClosed) {
509        es.throwDOMException(InvalidStateError);
510        return;
511    }
512
513    m_peerHandler->stop();
514
515    changeIceConnectionState(IceConnectionStateClosed);
516    changeIceGatheringState(IceGatheringStateComplete);
517    changeSignalingState(SignalingStateClosed);
518}
519
520void RTCPeerConnection::negotiationNeeded()
521{
522    scheduleDispatchEvent(Event::create(eventNames().negotiationneededEvent, false, false));
523}
524
525void RTCPeerConnection::didGenerateIceCandidate(WebKit::WebRTCICECandidate webCandidate)
526{
527    ASSERT(scriptExecutionContext()->isContextThread());
528    if (webCandidate.isNull())
529        scheduleDispatchEvent(RTCIceCandidateEvent::create(false, false, 0));
530    else {
531        RefPtr<RTCIceCandidate> iceCandidate = RTCIceCandidate::create(webCandidate);
532        scheduleDispatchEvent(RTCIceCandidateEvent::create(false, false, iceCandidate.release()));
533    }
534}
535
536void RTCPeerConnection::didChangeSignalingState(SignalingState newState)
537{
538    ASSERT(scriptExecutionContext()->isContextThread());
539    changeSignalingState(newState);
540}
541
542void RTCPeerConnection::didChangeIceGatheringState(IceGatheringState newState)
543{
544    ASSERT(scriptExecutionContext()->isContextThread());
545    changeIceGatheringState(newState);
546}
547
548void RTCPeerConnection::didChangeIceConnectionState(IceConnectionState newState)
549{
550    ASSERT(scriptExecutionContext()->isContextThread());
551    changeIceConnectionState(newState);
552}
553
554void RTCPeerConnection::didAddRemoteStream(PassRefPtr<MediaStreamDescriptor> streamDescriptor)
555{
556    ASSERT(scriptExecutionContext()->isContextThread());
557
558    if (m_signalingState == SignalingStateClosed)
559        return;
560
561    RefPtr<MediaStream> stream = MediaStream::create(scriptExecutionContext(), streamDescriptor);
562    m_remoteStreams.append(stream);
563
564    scheduleDispatchEvent(MediaStreamEvent::create(eventNames().addstreamEvent, false, false, stream.release()));
565}
566
567void RTCPeerConnection::didRemoveRemoteStream(MediaStreamDescriptor* streamDescriptor)
568{
569    ASSERT(scriptExecutionContext()->isContextThread());
570    ASSERT(streamDescriptor->client());
571
572    RefPtr<MediaStream> stream = static_cast<MediaStream*>(streamDescriptor->client());
573    stream->streamEnded();
574
575    if (m_signalingState == SignalingStateClosed)
576        return;
577
578    size_t pos = m_remoteStreams.find(stream);
579    ASSERT(pos != notFound);
580    m_remoteStreams.remove(pos);
581
582    scheduleDispatchEvent(MediaStreamEvent::create(eventNames().removestreamEvent, false, false, stream.release()));
583}
584
585void RTCPeerConnection::didAddRemoteDataChannel(PassOwnPtr<RTCDataChannelHandler> handler)
586{
587    ASSERT(scriptExecutionContext()->isContextThread());
588
589    if (m_signalingState == SignalingStateClosed)
590        return;
591
592    RefPtr<RTCDataChannel> channel = RTCDataChannel::create(scriptExecutionContext(), handler);
593    m_dataChannels.append(channel);
594
595    scheduleDispatchEvent(RTCDataChannelEvent::create(eventNames().datachannelEvent, false, false, channel.release()));
596}
597
598const AtomicString& RTCPeerConnection::interfaceName() const
599{
600    return eventNames().interfaceForRTCPeerConnection;
601}
602
603ScriptExecutionContext* RTCPeerConnection::scriptExecutionContext() const
604{
605    return ActiveDOMObject::scriptExecutionContext();
606}
607
608void RTCPeerConnection::stop()
609{
610    if (m_stopped)
611        return;
612
613    m_stopped = true;
614    m_iceConnectionState = IceConnectionStateClosed;
615    m_signalingState = SignalingStateClosed;
616
617    Vector<RefPtr<RTCDataChannel> >::iterator i = m_dataChannels.begin();
618    for (; i != m_dataChannels.end(); ++i)
619        (*i)->stop();
620}
621
622EventTargetData* RTCPeerConnection::eventTargetData()
623{
624    return &m_eventTargetData;
625}
626
627EventTargetData* RTCPeerConnection::ensureEventTargetData()
628{
629    return &m_eventTargetData;
630}
631
632void RTCPeerConnection::changeSignalingState(SignalingState signalingState)
633{
634    if (m_signalingState != SignalingStateClosed && m_signalingState != signalingState) {
635        m_signalingState = signalingState;
636        scheduleDispatchEvent(Event::create(eventNames().signalingstatechangeEvent, false, false));
637    }
638}
639
640void RTCPeerConnection::changeIceGatheringState(IceGatheringState iceGatheringState)
641{
642    m_iceGatheringState = iceGatheringState;
643}
644
645void RTCPeerConnection::changeIceConnectionState(IceConnectionState iceConnectionState)
646{
647    if (m_iceConnectionState != IceConnectionStateClosed && m_iceConnectionState != iceConnectionState) {
648        m_iceConnectionState = iceConnectionState;
649        scheduleDispatchEvent(Event::create(eventNames().iceconnectionstatechangeEvent, false, false));
650    }
651}
652
653void RTCPeerConnection::scheduleDispatchEvent(PassRefPtr<Event> event)
654{
655    m_scheduledEvents.append(event);
656
657    if (!m_scheduledEventTimer.isActive())
658        m_scheduledEventTimer.startOneShot(0);
659}
660
661void RTCPeerConnection::scheduledEventTimerFired(Timer<RTCPeerConnection>*)
662{
663    if (m_stopped)
664        return;
665
666    Vector<RefPtr<Event> > events;
667    events.swap(m_scheduledEvents);
668
669    Vector<RefPtr<Event> >::iterator it = events.begin();
670    for (; it != events.end(); ++it)
671        dispatchEvent((*it).release());
672
673    events.clear();
674}
675
676} // namespace WebCore
677