Clean up some includes to make the build a bit faster: DOMPromise
[WebKit-https.git] / Source / WebCore / Modules / mediastream / PeerConnectionBackend.cpp
1 /*
2  * Copyright (C) 2015 Ericsson AB. All rights reserved.
3  * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer
13  *    in the documentation and/or other materials provided with the
14  *    distribution.
15  * 3. Neither the name of Ericsson nor the names of its contributors
16  *    may be used to endorse or promote products derived from this
17  *    software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "PeerConnectionBackend.h"
34
35 #if ENABLE(WEB_RTC)
36
37 #include "EventNames.h"
38 #include "JSDOMPromiseDeferred.h"
39 #include "JSRTCSessionDescription.h"
40 #include "LibWebRTCCertificateGenerator.h"
41 #include "Logging.h"
42 #include "Page.h"
43 #include "RTCIceCandidate.h"
44 #include "RTCPeerConnection.h"
45 #include "RTCPeerConnectionIceEvent.h"
46 #include "RTCRtpCapabilities.h"
47 #include "RTCTrackEvent.h"
48 #include "RuntimeEnabledFeatures.h"
49 #include <wtf/text/StringBuilder.h>
50 #include <wtf/text/StringConcatenateNumbers.h>
51
52 namespace WebCore {
53
54 using namespace PAL;
55
56 #if !USE(LIBWEBRTC)
57 static std::unique_ptr<PeerConnectionBackend> createNoPeerConnectionBackend(RTCPeerConnection&)
58 {
59     return nullptr;
60 }
61
62 CreatePeerConnectionBackend PeerConnectionBackend::create = createNoPeerConnectionBackend;
63
64 Optional<RTCRtpCapabilities> PeerConnectionBackend::receiverCapabilities(ScriptExecutionContext&, const String&)
65 {
66     ASSERT_NOT_REACHED();
67     return { };
68 }
69
70 Optional<RTCRtpCapabilities> PeerConnectionBackend::senderCapabilities(ScriptExecutionContext&, const String&)
71 {
72     ASSERT_NOT_REACHED();
73     return { };
74 }
75 #endif
76
77 PeerConnectionBackend::PeerConnectionBackend(RTCPeerConnection& peerConnection)
78     : m_peerConnection(peerConnection)
79 #if !RELEASE_LOG_DISABLED
80     , m_logger(peerConnection.logger())
81     , m_logIdentifier(peerConnection.logIdentifier())
82 #endif
83 {
84 }
85
86 PeerConnectionBackend::~PeerConnectionBackend() = default;
87
88 void PeerConnectionBackend::createOffer(RTCOfferOptions&& options, PeerConnection::SessionDescriptionPromise&& promise)
89 {
90     ASSERT(!m_offerAnswerPromise);
91     ASSERT(!m_peerConnection.isClosed());
92
93     m_offerAnswerPromise = WTF::makeUnique<PeerConnection::SessionDescriptionPromise>(WTFMove(promise));
94     doCreateOffer(WTFMove(options));
95 }
96
97 void PeerConnectionBackend::createOfferSucceeded(String&& sdp)
98 {
99     ASSERT(isMainThread());
100     ALWAYS_LOG(LOGIDENTIFIER, "Create offer succeeded:\n", sdp);
101
102     if (m_peerConnection.isClosed())
103         return;
104
105     ASSERT(m_offerAnswerPromise);
106     m_peerConnection.doTask([promise = WTFMove(m_offerAnswerPromise), sdp = filterSDP(WTFMove(sdp))]() mutable {
107         promise->resolve(RTCSessionDescription::Init { RTCSdpType::Offer, sdp });
108     });
109 }
110
111 void PeerConnectionBackend::createOfferFailed(Exception&& exception)
112 {
113     ASSERT(isMainThread());
114     ALWAYS_LOG(LOGIDENTIFIER, "Create offer failed:", exception.message());
115
116     if (m_peerConnection.isClosed())
117         return;
118
119     ASSERT(m_offerAnswerPromise);
120     m_peerConnection.doTask([promise = WTFMove(m_offerAnswerPromise), exception = WTFMove(exception)]() mutable {
121         promise->reject(WTFMove(exception));
122     });
123 }
124
125 void PeerConnectionBackend::createAnswer(RTCAnswerOptions&& options, PeerConnection::SessionDescriptionPromise&& promise)
126 {
127     ASSERT(!m_offerAnswerPromise);
128     ASSERT(!m_peerConnection.isClosed());
129
130     m_offerAnswerPromise = WTF::makeUnique<PeerConnection::SessionDescriptionPromise>(WTFMove(promise));
131     doCreateAnswer(WTFMove(options));
132 }
133
134 void PeerConnectionBackend::createAnswerSucceeded(String&& sdp)
135 {
136     ASSERT(isMainThread());
137     ALWAYS_LOG(LOGIDENTIFIER, "Create answer succeeded:\n", sdp);
138
139     if (m_peerConnection.isClosed())
140         return;
141
142     ASSERT(m_offerAnswerPromise);
143     m_peerConnection.doTask([promise = WTFMove(m_offerAnswerPromise), sdp = WTFMove(sdp)]() mutable {
144         promise->resolve(RTCSessionDescription::Init { RTCSdpType::Answer, sdp });
145     });
146 }
147
148 void PeerConnectionBackend::createAnswerFailed(Exception&& exception)
149 {
150     ASSERT(isMainThread());
151     ALWAYS_LOG(LOGIDENTIFIER, "Create answer failed:", exception.message());
152
153     if (m_peerConnection.isClosed())
154         return;
155
156     ASSERT(m_offerAnswerPromise);
157     m_peerConnection.doTask([promise = WTFMove(m_offerAnswerPromise), exception = WTFMove(exception)]() mutable {
158         promise->reject(WTFMove(exception));
159     });
160 }
161
162 static inline bool isLocalDescriptionTypeValidForState(RTCSdpType type, RTCSignalingState state)
163 {
164     switch (state) {
165     case RTCSignalingState::Stable:
166         return type == RTCSdpType::Offer;
167     case RTCSignalingState::HaveLocalOffer:
168         return type == RTCSdpType::Offer;
169     case RTCSignalingState::HaveRemoteOffer:
170         return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
171     case RTCSignalingState::HaveLocalPranswer:
172         return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
173     default:
174         return false;
175     };
176
177     ASSERT_NOT_REACHED();
178     return false;
179 }
180
181 void PeerConnectionBackend::setLocalDescription(RTCSessionDescription& sessionDescription, DOMPromiseDeferred<void>&& promise)
182 {
183     ASSERT(!m_peerConnection.isClosed());
184
185     if (!isLocalDescriptionTypeValidForState(sessionDescription.type(), m_peerConnection.signalingState())) {
186         promise.reject(InvalidStateError, "Description type incompatible with current signaling state");
187         return;
188     }
189
190     m_setDescriptionPromise = WTF::makeUnique<DOMPromiseDeferred<void>>(WTFMove(promise));
191     doSetLocalDescription(sessionDescription);
192 }
193
194 void PeerConnectionBackend::setLocalDescriptionSucceeded()
195 {
196     ASSERT(isMainThread());
197     ALWAYS_LOG(LOGIDENTIFIER);
198
199     if (m_peerConnection.isClosed())
200         return;
201
202
203     ASSERT(m_setDescriptionPromise);
204     m_peerConnection.doTask([promise = WTFMove(m_setDescriptionPromise)]() mutable {
205         promise->resolve();
206     });
207 }
208
209 void PeerConnectionBackend::setLocalDescriptionFailed(Exception&& exception)
210 {
211     ASSERT(isMainThread());
212     ALWAYS_LOG(LOGIDENTIFIER, "Set local description failed:", exception.message());
213
214     if (m_peerConnection.isClosed())
215         return;
216
217     ASSERT(m_setDescriptionPromise);
218     m_peerConnection.doTask([promise = WTFMove(m_setDescriptionPromise), exception = WTFMove(exception)]() mutable {
219         promise->reject(WTFMove(exception));
220     });
221 }
222
223 static inline bool isRemoteDescriptionTypeValidForState(RTCSdpType type, RTCSignalingState state)
224 {
225     switch (state) {
226     case RTCSignalingState::Stable:
227         return type == RTCSdpType::Offer;
228     case RTCSignalingState::HaveLocalOffer:
229         return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
230     case RTCSignalingState::HaveRemoteOffer:
231         return type == RTCSdpType::Offer;
232     case RTCSignalingState::HaveRemotePranswer:
233         return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
234     default:
235         return false;
236     };
237
238     ASSERT_NOT_REACHED();
239     return false;
240 }
241
242 void PeerConnectionBackend::setRemoteDescription(RTCSessionDescription& sessionDescription, DOMPromiseDeferred<void>&& promise)
243 {
244     ASSERT(!m_peerConnection.isClosed());
245
246     if (!isRemoteDescriptionTypeValidForState(sessionDescription.type(), m_peerConnection.signalingState())) {
247         promise.reject(InvalidStateError, "Description type incompatible with current signaling state");
248         return;
249     }
250
251     m_setDescriptionPromise = WTF::makeUnique<DOMPromiseDeferred<void>>(WTFMove(promise));
252     doSetRemoteDescription(sessionDescription);
253 }
254
255 void PeerConnectionBackend::setRemoteDescriptionSucceeded()
256 {
257     ASSERT(isMainThread());
258     ALWAYS_LOG(LOGIDENTIFIER, "Set remote description succeeded");
259
260     ASSERT(!m_peerConnection.isClosed());
261
262     auto events = WTFMove(m_pendingTrackEvents);
263     for (auto& event : events) {
264         auto& track = event.track.get();
265
266         m_peerConnection.dispatchEventWhenFeasible(RTCTrackEvent::create(eventNames().trackEvent, Event::CanBubble::No, Event::IsCancelable::No, WTFMove(event.receiver), WTFMove(event.track), WTFMove(event.streams), WTFMove(event.transceiver)));
267
268         if (m_peerConnection.isClosed())
269             return;
270
271         // FIXME: As per spec, we should set muted to 'false' when starting to receive the content from network.
272         track.source().setMuted(false);
273     }
274
275     if (m_peerConnection.isClosed())
276         return;
277
278
279     ASSERT(m_setDescriptionPromise);
280     m_peerConnection.doTask([promise = WTFMove(m_setDescriptionPromise)]() mutable {
281         promise->resolve();
282     });
283 }
284
285 void PeerConnectionBackend::setRemoteDescriptionFailed(Exception&& exception)
286 {
287     ASSERT(isMainThread());
288     ALWAYS_LOG(LOGIDENTIFIER, "Set remote description failed:", exception.message());
289
290     ASSERT(m_pendingTrackEvents.isEmpty());
291     m_pendingTrackEvents.clear();
292
293     ASSERT(!m_peerConnection.isClosed());
294     ASSERT(m_setDescriptionPromise);
295     m_peerConnection.doTask([promise = WTFMove(m_setDescriptionPromise), exception = WTFMove(exception)]() mutable {
296         promise->reject(WTFMove(exception));
297     });
298 }
299
300 void PeerConnectionBackend::addPendingTrackEvent(PendingTrackEvent&& event)
301 {
302     ASSERT(!m_peerConnection.isClosed());
303     m_pendingTrackEvents.append(WTFMove(event));
304 }
305
306 static String extractIPAddres(const String& sdp)
307 {
308     ASSERT(sdp.contains(" host "));
309     unsigned counter = 0;
310     for (auto item : StringView { sdp }.split(' ')) {
311         if (++counter == 5)
312             return item.toString();
313     }
314     return { };
315 }
316
317 void PeerConnectionBackend::addIceCandidate(RTCIceCandidate* iceCandidate, DOMPromiseDeferred<void>&& promise)
318 {
319     ASSERT(!m_peerConnection.isClosed());
320
321     if (!iceCandidate) {
322         endOfIceCandidates(WTFMove(promise));
323         return;
324     }
325
326     // FIXME: As per https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addicecandidate(), this check should be done before enqueuing the task.
327     if (iceCandidate->sdpMid().isNull() && !iceCandidate->sdpMLineIndex()) {
328         promise.reject(Exception { TypeError, "Trying to add a candidate that is missing both sdpMid and sdpMLineIndex"_s });
329         return;
330     }
331     m_addIceCandidatePromise = WTF::makeUnique<DOMPromiseDeferred<void>>(WTFMove(promise));
332     doAddIceCandidate(*iceCandidate);
333 }
334
335 void PeerConnectionBackend::addIceCandidateSucceeded()
336 {
337     ASSERT(isMainThread());
338     ALWAYS_LOG(LOGIDENTIFIER, "Adding ice candidate succeeded");
339
340     if (m_peerConnection.isClosed())
341         return;
342
343     ASSERT(m_addIceCandidatePromise);
344     m_peerConnection.doTask([promise = WTFMove(m_addIceCandidatePromise)]() mutable {
345         promise->resolve();
346     });
347 }
348
349 void PeerConnectionBackend::addIceCandidateFailed(Exception&& exception)
350 {
351     ASSERT(isMainThread());
352     ALWAYS_LOG(LOGIDENTIFIER, "Adding ice candidate failed:", exception.message());
353
354     if (m_peerConnection.isClosed())
355         return;
356
357     ASSERT(m_addIceCandidatePromise);
358     m_peerConnection.doTask([promise = WTFMove(m_addIceCandidatePromise), exception = WTFMove(exception)]() mutable {
359         promise->reject(WTFMove(exception));
360     });
361 }
362
363 void PeerConnectionBackend::fireICECandidateEvent(RefPtr<RTCIceCandidate>&& candidate, String&& serverURL)
364 {
365     ASSERT(isMainThread());
366
367     m_peerConnection.dispatchEventWhenFeasible(RTCPeerConnectionIceEvent::create(Event::CanBubble::No, Event::IsCancelable::No, WTFMove(candidate), WTFMove(serverURL)));
368 }
369
370 void PeerConnectionBackend::enableICECandidateFiltering()
371 {
372     m_shouldFilterICECandidates = true;
373 }
374
375 void PeerConnectionBackend::disableICECandidateFiltering()
376 {
377     m_shouldFilterICECandidates = false;
378     for (auto& pendingICECandidate : m_pendingICECandidates)
379         fireICECandidateEvent(RTCIceCandidate::create(WTFMove(pendingICECandidate.sdp), WTFMove(pendingICECandidate.mid), pendingICECandidate.sdpMLineIndex), WTFMove(pendingICECandidate.serverURL));
380     m_pendingICECandidates.clear();
381 }
382
383 static String filterICECandidate(String&& sdp)
384 {
385     ASSERT(!sdp.contains(" host "));
386
387     if (!sdp.contains(" raddr "))
388         return WTFMove(sdp);
389
390     bool skipNextItem = false;
391     bool isFirst = true;
392     StringBuilder filteredSDP;
393     sdp.split(' ', [&](StringView item) {
394         if (skipNextItem) {
395             skipNextItem = false;
396             return;
397         }
398         if (item == "raddr") {
399             filteredSDP.append(" raddr 0.0.0.0");
400             skipNextItem = true;
401             return;
402         }
403         if (item == "rport") {
404             filteredSDP.append(" rport 0");
405             skipNextItem = true;
406             return;
407         }
408         if (isFirst)
409             isFirst = false;
410         else
411             filteredSDP.append(' ');
412         filteredSDP.append(item);
413     });
414     return filteredSDP.toString();
415 }
416
417 String PeerConnectionBackend::filterSDP(String&& sdp) const
418 {
419     if (!m_shouldFilterICECandidates)
420         return WTFMove(sdp);
421
422     StringBuilder filteredSDP;
423     sdp.split('\n', [&filteredSDP](StringView line) {
424         if (line.startsWith("c=IN IP4"))
425             filteredSDP.append("c=IN IP4 0.0.0.0");
426         else if (line.startsWith("c=IN IP6"))
427             filteredSDP.append("c=IN IP6 ::");
428         else if (!line.startsWith("a=candidate"))
429             filteredSDP.append(line);
430         else if (line.find(" host ", 11) == notFound)
431             filteredSDP.append(filterICECandidate(line.toString()));
432         else
433             return;
434         filteredSDP.append('\n');
435     });
436     return filteredSDP.toString();
437 }
438
439 void PeerConnectionBackend::newICECandidate(String&& sdp, String&& mid, unsigned short sdpMLineIndex, String&& serverURL)
440 {
441     ALWAYS_LOG(LOGIDENTIFIER, "Gathered ice candidate:", sdp);
442     m_finishedGatheringCandidates = false;
443
444     if (!m_shouldFilterICECandidates) {
445         fireICECandidateEvent(RTCIceCandidate::create(WTFMove(sdp), WTFMove(mid), sdpMLineIndex), WTFMove(serverURL));
446         return;
447     }
448     if (sdp.find(" host ", 0) != notFound) {
449         // FIXME: We might need to clear all pending candidates when setting again local description.
450         m_pendingICECandidates.append(PendingICECandidate { String { sdp }, WTFMove(mid), sdpMLineIndex, WTFMove(serverURL) });
451         if (RuntimeEnabledFeatures::sharedFeatures().webRTCMDNSICECandidatesEnabled()) {
452             auto ipAddress = extractIPAddres(sdp);
453             // We restrict to IPv4 candidates for now.
454             if (ipAddress.contains('.'))
455                 registerMDNSName(ipAddress);
456         }
457         return;
458     }
459     fireICECandidateEvent(RTCIceCandidate::create(filterICECandidate(WTFMove(sdp)), WTFMove(mid), sdpMLineIndex), WTFMove(serverURL));
460 }
461
462 void PeerConnectionBackend::doneGatheringCandidates()
463 {
464     ASSERT(isMainThread());
465     ALWAYS_LOG(LOGIDENTIFIER, "Finished ice candidate gathering");
466     m_finishedGatheringCandidates = true;
467
468     if (m_waitingForMDNSRegistration)
469         return;
470
471     m_peerConnection.dispatchEventWhenFeasible(RTCPeerConnectionIceEvent::create(Event::CanBubble::No, Event::IsCancelable::No, nullptr, { }));
472     m_peerConnection.updateIceGatheringState(RTCIceGatheringState::Complete);
473     m_pendingICECandidates.clear();
474 }
475
476 void PeerConnectionBackend::endOfIceCandidates(DOMPromiseDeferred<void>&& promise)
477 {
478     promise.resolve();
479 }
480
481 void PeerConnectionBackend::registerMDNSName(const String& ipAddress)
482 {
483     ++m_waitingForMDNSRegistration;
484     auto& document = downcast<Document>(*m_peerConnection.scriptExecutionContext());
485     auto& provider = document.page()->libWebRTCProvider();
486     provider.registerMDNSName(document.identifier().toUInt64(), ipAddress, [peerConnection = makeRef(m_peerConnection), this, ipAddress] (LibWebRTCProvider::MDNSNameOrError&& result) {
487         if (peerConnection->isStopped())
488             return;
489
490         --m_waitingForMDNSRegistration;
491         if (!result.has_value()) {
492             m_peerConnection.scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, makeString("MDNS registration of a host candidate failed with error", (unsigned)result.error()));
493             return;
494         }
495
496         this->finishedRegisteringMDNSName(ipAddress, result.value());
497     });
498 }
499
500 void PeerConnectionBackend::finishedRegisteringMDNSName(const String& ipAddress, const String& name)
501 {
502     Vector<PendingICECandidate*> candidates;
503     for (auto& candidate : m_pendingICECandidates) {
504         if (candidate.sdp.find(ipAddress) != notFound) {
505             auto sdp = candidate.sdp;
506             sdp.replace(ipAddress, name);
507             fireICECandidateEvent(RTCIceCandidate::create(WTFMove(sdp), WTFMove(candidate.mid), candidate.sdpMLineIndex), WTFMove(candidate.serverURL));
508             candidates.append(&candidate);
509         }
510     }
511     m_pendingICECandidates.removeAllMatching([&] (const auto& candidate) {
512         return candidates.contains(&candidate);
513     });
514
515     if (!m_waitingForMDNSRegistration && m_finishedGatheringCandidates)
516         doneGatheringCandidates();
517 }
518
519 void PeerConnectionBackend::updateSignalingState(RTCSignalingState newSignalingState)
520 {
521     ASSERT(isMainThread());
522
523     if (newSignalingState != m_peerConnection.signalingState()) {
524         m_peerConnection.setSignalingState(newSignalingState);
525         m_peerConnection.dispatchEventWhenFeasible(Event::create(eventNames().signalingstatechangeEvent, Event::CanBubble::No, Event::IsCancelable::No));
526     }
527 }
528
529 void PeerConnectionBackend::stop()
530 {
531     m_offerAnswerPromise = nullptr;
532     m_setDescriptionPromise = nullptr;
533     m_addIceCandidatePromise = nullptr;
534
535     m_pendingTrackEvents.clear();
536
537     doStop();
538 }
539
540 void PeerConnectionBackend::markAsNeedingNegotiation()
541 {
542     if (m_negotiationNeeded)
543         return;
544
545     m_negotiationNeeded = true;
546
547     if (m_peerConnection.signalingState() == RTCSignalingState::Stable)
548         m_peerConnection.scheduleNegotiationNeededEvent();
549 }
550
551 ExceptionOr<Ref<RTCRtpSender>> PeerConnectionBackend::addTrack(MediaStreamTrack&, Vector<String>&&)
552 {
553     return Exception { NotSupportedError, "Not implemented"_s };
554 }
555
556 ExceptionOr<Ref<RTCRtpTransceiver>> PeerConnectionBackend::addTransceiver(const String&, const RTCRtpTransceiverInit&)
557 {
558     return Exception { NotSupportedError, "Not implemented"_s };
559 }
560
561 ExceptionOr<Ref<RTCRtpTransceiver>> PeerConnectionBackend::addTransceiver(Ref<MediaStreamTrack>&&, const RTCRtpTransceiverInit&)
562 {
563     return Exception { NotSupportedError, "Not implemented"_s };
564 }
565
566 void PeerConnectionBackend::generateCertificate(Document& document, const CertificateInformation& info, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&& promise)
567 {
568 #if USE(LIBWEBRTC)
569     LibWebRTCCertificateGenerator::generateCertificate(document.securityOrigin(), document.page()->libWebRTCProvider(), info, WTFMove(promise));
570 #else
571     UNUSED_PARAM(document);
572     UNUSED_PARAM(expires);
573     UNUSED_PARAM(type);
574     promise.reject(NotSupportedError);
575 #endif
576 }
577
578 ScriptExecutionContext* PeerConnectionBackend::context() const
579 {
580     return m_peerConnection.scriptExecutionContext();
581 }
582
583 RTCRtpTransceiver* PeerConnectionBackend::transceiverFromSender(const RTCRtpSender& sender)
584 {
585     for (auto& transceiver : m_peerConnection.currentTransceivers()) {
586         if (&transceiver->sender() == &sender)
587             return transceiver.get();
588     }
589     return nullptr;
590 }
591
592 #if !RELEASE_LOG_DISABLED
593 WTFLogChannel& PeerConnectionBackend::logChannel() const
594 {
595     return LogWebRTC;
596 }
597 #endif
598
599 } // namespace WebCore
600
601 #endif // ENABLE(WEB_RTC)