| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h" |
| |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/web/modules/peerconnection/peer_connection_dependency_factory.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_cross_thread_factory.h" |
| #include "third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_adapter_impl.h" |
| #include "third_party/blink/renderer/modules/peerconnection/adapters/ice_transport_proxy.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_error_util.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_candidate.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_gather_options.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_parameters.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_server.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_event.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_event_init.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread.h" |
| #include "third_party/webrtc/api/ice_transport_factory.h" |
| #include "third_party/webrtc/api/ice_transport_interface.h" |
| #include "third_party/webrtc/api/jsep_ice_candidate.h" |
| #include "third_party/webrtc/api/peer_connection_interface.h" |
| #include "third_party/webrtc/p2p/base/port_allocator.h" |
| #include "third_party/webrtc/p2p/base/transport_description.h" |
| #include "third_party/webrtc/pc/ice_server_parsing.h" |
| #include "third_party/webrtc/pc/webrtc_sdp.h" |
| |
| namespace blink { |
| namespace { |
| |
| const char* kIceRoleControllingStr = "controlling"; |
| const char* kIceRoleControlledStr = "controlled"; |
| |
| base::Optional<cricket::Candidate> ConvertToCricketIceCandidate( |
| const RTCIceCandidate& candidate) { |
| webrtc::JsepIceCandidate jsep_candidate("", 0); |
| webrtc::SdpParseError error; |
| if (!webrtc::SdpDeserializeCandidate(candidate.candidate().Utf8(), |
| &jsep_candidate, &error)) { |
| LOG(WARNING) << "Failed to deserialize candidate: " << error.description; |
| return base::nullopt; |
| } |
| return jsep_candidate.candidate(); |
| } |
| |
| RTCIceCandidate* ConvertToRtcIceCandidate(const cricket::Candidate& candidate) { |
| return RTCIceCandidate::Create(WebRTCICECandidate::Create( |
| String::FromUTF8(webrtc::SdpSerializeCandidate(candidate)), "", 0)); |
| } |
| |
| class DtlsIceTransportAdapterCrossThreadFactory |
| : public IceTransportAdapterCrossThreadFactory { |
| public: |
| explicit DtlsIceTransportAdapterCrossThreadFactory( |
| rtc::scoped_refptr<webrtc::IceTransportInterface> ice_transport) |
| : ice_transport_(ice_transport) {} |
| void InitializeOnMainThread(LocalFrame& frame) override { |
| DCHECK(!worker_thread_rtc_thread_); |
| worker_thread_rtc_thread_ = |
| Platform::Current()->GetWebRtcWorkerThreadRtcThread(); |
| } |
| |
| std::unique_ptr<IceTransportAdapter> ConstructOnWorkerThread( |
| IceTransportAdapter::Delegate* delegate) override { |
| DCHECK(ice_transport_); |
| DCHECK(worker_thread_rtc_thread_); |
| return std::make_unique<IceTransportAdapterImpl>( |
| delegate, std::move(ice_transport_), worker_thread_rtc_thread_); |
| } |
| |
| private: |
| rtc::scoped_refptr<webrtc::IceTransportInterface> ice_transport_; |
| rtc::Thread* worker_thread_rtc_thread_ = nullptr; |
| }; |
| |
| class DefaultIceTransportAdapterCrossThreadFactory |
| : public IceTransportAdapterCrossThreadFactory { |
| public: |
| void InitializeOnMainThread(LocalFrame& frame) override { |
| DCHECK(!port_allocator_); |
| DCHECK(!worker_thread_rtc_thread_); |
| DCHECK(!async_resolver_factory_); |
| |
| auto* rtc_dependency_factory = |
| blink::PeerConnectionDependencyFactory::GetInstance(); |
| rtc_dependency_factory->EnsureInitialized(); |
| port_allocator_ = rtc_dependency_factory->CreatePortAllocator( |
| frame.Client()->GetWebFrame()); |
| |
| async_resolver_factory_ = |
| Platform::Current()->CreateWebRtcAsyncResolverFactory(); |
| worker_thread_rtc_thread_ = |
| Platform::Current()->GetWebRtcWorkerThreadRtcThread(); |
| } |
| |
| std::unique_ptr<IceTransportAdapter> ConstructOnWorkerThread( |
| IceTransportAdapter::Delegate* delegate) override { |
| DCHECK(port_allocator_); |
| DCHECK(worker_thread_rtc_thread_); |
| DCHECK(async_resolver_factory_); |
| return std::make_unique<IceTransportAdapterImpl>( |
| delegate, std::move(port_allocator_), |
| std::move(async_resolver_factory_), worker_thread_rtc_thread_); |
| } |
| |
| private: |
| std::unique_ptr<cricket::PortAllocator> port_allocator_; |
| std::unique_ptr<webrtc::AsyncResolverFactory> async_resolver_factory_; |
| rtc::Thread* worker_thread_rtc_thread_ = nullptr; |
| }; |
| |
| } // namespace |
| |
| RTCIceTransport* RTCIceTransport::Create(ExecutionContext* context) { |
| LocalFrame* frame = To<Document>(context)->GetFrame(); |
| scoped_refptr<base::SingleThreadTaskRunner> proxy_thread = |
| frame->GetTaskRunner(TaskType::kNetworking); |
| scoped_refptr<base::SingleThreadTaskRunner> host_thread = |
| Platform::Current()->GetWebRtcWorkerThread(); |
| return MakeGarbageCollected<RTCIceTransport>( |
| context, std::move(proxy_thread), std::move(host_thread), |
| std::make_unique<DefaultIceTransportAdapterCrossThreadFactory>()); |
| } |
| |
| RTCIceTransport* RTCIceTransport::Create( |
| ExecutionContext* context, |
| rtc::scoped_refptr<webrtc::IceTransportInterface> ice_transport, |
| RTCPeerConnection* peer_connection) { |
| LocalFrame* frame = To<Document>(context)->GetFrame(); |
| scoped_refptr<base::SingleThreadTaskRunner> proxy_thread = |
| frame->GetTaskRunner(TaskType::kNetworking); |
| scoped_refptr<base::SingleThreadTaskRunner> host_thread = |
| Platform::Current()->GetWebRtcWorkerThread(); |
| return MakeGarbageCollected<RTCIceTransport>( |
| context, std::move(proxy_thread), std::move(host_thread), |
| std::make_unique<DtlsIceTransportAdapterCrossThreadFactory>( |
| std::move(ice_transport)), |
| peer_connection); |
| } |
| |
| RTCIceTransport* RTCIceTransport::Create( |
| ExecutionContext* context, |
| scoped_refptr<base::SingleThreadTaskRunner> proxy_thread, |
| scoped_refptr<base::SingleThreadTaskRunner> host_thread, |
| std::unique_ptr<IceTransportAdapterCrossThreadFactory> adapter_factory) { |
| return MakeGarbageCollected<RTCIceTransport>(context, std::move(proxy_thread), |
| std::move(host_thread), |
| std::move(adapter_factory)); |
| } |
| |
| RTCIceTransport::RTCIceTransport( |
| ExecutionContext* context, |
| scoped_refptr<base::SingleThreadTaskRunner> proxy_thread, |
| scoped_refptr<base::SingleThreadTaskRunner> host_thread, |
| std::unique_ptr<IceTransportAdapterCrossThreadFactory> adapter_factory, |
| RTCPeerConnection* peer_connection) |
| : ContextLifecycleObserver(context), peer_connection_(peer_connection) { |
| DCHECK(context); |
| DCHECK(proxy_thread); |
| DCHECK(host_thread); |
| DCHECK(adapter_factory); |
| DCHECK(proxy_thread->BelongsToCurrentThread()); |
| |
| LocalFrame* frame = To<Document>(context)->GetFrame(); |
| DCHECK(frame); |
| proxy_ = std::make_unique<IceTransportProxy>(*frame, std::move(proxy_thread), |
| std::move(host_thread), this, |
| std::move(adapter_factory)); |
| |
| GenerateLocalParameters(); |
| } |
| |
| RTCIceTransport::RTCIceTransport( |
| ExecutionContext* context, |
| scoped_refptr<base::SingleThreadTaskRunner> proxy_thread, |
| scoped_refptr<base::SingleThreadTaskRunner> host_thread, |
| std::unique_ptr<IceTransportAdapterCrossThreadFactory> adapter_factory) |
| : RTCIceTransport(context, |
| std::move(proxy_thread), |
| std::move(host_thread), |
| std::move(adapter_factory), |
| nullptr) {} |
| |
| RTCIceTransport::~RTCIceTransport() { |
| DCHECK(!proxy_); |
| } |
| |
| bool RTCIceTransport::HasConsumer() const { |
| return consumer_; |
| } |
| |
| bool RTCIceTransport::IsFromPeerConnection() const { |
| return peer_connection_; |
| } |
| |
| IceTransportProxy* RTCIceTransport::ConnectConsumer( |
| RTCQuicTransport* consumer) { |
| DCHECK(consumer); |
| DCHECK(proxy_); |
| DCHECK(!peer_connection_); |
| if (!consumer_) { |
| consumer_ = consumer; |
| } else { |
| DCHECK_EQ(consumer_, consumer); |
| } |
| return proxy_.get(); |
| } |
| |
| void RTCIceTransport::DisconnectConsumer(RTCQuicTransport* consumer) { |
| DCHECK(consumer); |
| DCHECK(proxy_); |
| DCHECK_EQ(consumer, consumer_); |
| consumer_ = nullptr; |
| } |
| |
| String RTCIceTransport::role() const { |
| switch (role_) { |
| case cricket::ICEROLE_CONTROLLING: |
| return kIceRoleControllingStr; |
| case cricket::ICEROLE_CONTROLLED: |
| return kIceRoleControlledStr; |
| case cricket::ICEROLE_UNKNOWN: |
| return String(); |
| } |
| NOTREACHED(); |
| return String(); |
| } |
| |
| String RTCIceTransport::state() const { |
| switch (state_) { |
| case webrtc::IceTransportState::kNew: |
| return "new"; |
| case webrtc::IceTransportState::kChecking: |
| return "checking"; |
| case webrtc::IceTransportState::kConnected: |
| return "connected"; |
| case webrtc::IceTransportState::kCompleted: |
| return "completed"; |
| case webrtc::IceTransportState::kDisconnected: |
| return "disconnected"; |
| case webrtc::IceTransportState::kFailed: |
| return "failed"; |
| case webrtc::IceTransportState::kClosed: |
| return "closed"; |
| } |
| NOTREACHED(); |
| return g_empty_string; |
| } |
| |
| String RTCIceTransport::gatheringState() const { |
| switch (gathering_state_) { |
| case cricket::kIceGatheringNew: |
| return "new"; |
| case cricket::kIceGatheringGathering: |
| return "gathering"; |
| case cricket::kIceGatheringComplete: |
| return "complete"; |
| default: |
| NOTREACHED(); |
| return g_empty_string; |
| } |
| } |
| |
| const HeapVector<Member<RTCIceCandidate>>& RTCIceTransport::getLocalCandidates() |
| const { |
| return local_candidates_; |
| } |
| |
| const HeapVector<Member<RTCIceCandidate>>& |
| RTCIceTransport::getRemoteCandidates() const { |
| return remote_candidates_; |
| } |
| |
| RTCIceCandidatePair* RTCIceTransport::getSelectedCandidatePair() const { |
| return selected_candidate_pair_; |
| } |
| |
| RTCIceParameters* RTCIceTransport::getLocalParameters() const { |
| return local_parameters_; |
| } |
| |
| RTCIceParameters* RTCIceTransport::getRemoteParameters() const { |
| return remote_parameters_; |
| } |
| |
| static webrtc::PeerConnectionInterface::IceServer ConvertIceServer( |
| const RTCIceServer* ice_server) { |
| webrtc::PeerConnectionInterface::IceServer converted_ice_server; |
| // Prefer standardized 'urls' field over deprecated 'url' field. |
| Vector<String> url_strings; |
| if (ice_server->hasURLs()) { |
| if (ice_server->urls().IsString()) { |
| url_strings.push_back(ice_server->urls().GetAsString()); |
| } else if (ice_server->urls().IsStringSequence()) { |
| url_strings = ice_server->urls().GetAsStringSequence(); |
| } |
| } else if (ice_server->hasURL()) { |
| url_strings.push_back(ice_server->url()); |
| } |
| for (const String& url_string : url_strings) { |
| converted_ice_server.urls.push_back(url_string.Utf8()); |
| } |
| converted_ice_server.username = ice_server->username().Utf8(); |
| converted_ice_server.password = ice_server->credential().Utf8(); |
| return converted_ice_server; |
| } |
| |
| static cricket::IceParameters ConvertIceParameters( |
| const RTCIceParameters* ice_parameters) { |
| cricket::IceParameters converted_ice_parameters; |
| converted_ice_parameters.ufrag = ice_parameters->usernameFragment().Utf8(); |
| converted_ice_parameters.pwd = ice_parameters->password().Utf8(); |
| return converted_ice_parameters; |
| } |
| |
| static WebVector<webrtc::PeerConnectionInterface::IceServer> ConvertIceServers( |
| const HeapVector<Member<RTCIceServer>>& ice_servers) { |
| Vector<webrtc::PeerConnectionInterface::IceServer> converted_ice_servers; |
| for (const RTCIceServer* ice_server : ice_servers) { |
| converted_ice_servers.emplace_back(ConvertIceServer(ice_server)); |
| } |
| return converted_ice_servers; |
| } |
| |
| static IceTransportPolicy IceTransportPolicyFromString(const String& str) { |
| if (str == "relay") { |
| return IceTransportPolicy::kRelay; |
| } |
| if (str == "all") { |
| return IceTransportPolicy::kAll; |
| } |
| NOTREACHED(); |
| return IceTransportPolicy::kAll; |
| } |
| |
| void RTCIceTransport::gather(RTCIceGatherOptions* options, |
| ExceptionState& exception_state) { |
| if (RaiseExceptionIfClosed(exception_state)) { |
| return; |
| } |
| // TODO(github.com/w3c/webrtc-ice/issues/7): Possibly support calling gather() |
| // more than once. |
| if (gathering_state_ != cricket::kIceGatheringNew) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Can only call gather() once."); |
| return; |
| } |
| WebVector<webrtc::PeerConnectionInterface::IceServer> ice_servers; |
| if (options->hasIceServers()) { |
| ice_servers = ConvertIceServers(options->iceServers()); |
| } |
| cricket::ServerAddresses stun_servers; |
| std::vector<cricket::RelayServerConfig> turn_servers; |
| webrtc::RTCErrorType error_type = webrtc::ParseIceServers( |
| ice_servers.ReleaseVector(), &stun_servers, &turn_servers); |
| if (error_type != webrtc::RTCErrorType::NONE) { |
| ThrowExceptionFromRTCError( |
| webrtc::RTCError(error_type, "Invalid ICE server URL(s)."), |
| exception_state); |
| return; |
| } |
| gathering_state_ = cricket::kIceGatheringGathering; |
| proxy_->StartGathering(ConvertIceParameters(local_parameters_), stun_servers, |
| turn_servers, |
| IceTransportPolicyFromString(options->gatherPolicy())); |
| } |
| |
| static cricket::IceRole IceRoleFromString(const String& role_string) { |
| if (role_string == kIceRoleControllingStr) { |
| return cricket::ICEROLE_CONTROLLING; |
| } |
| if (role_string == kIceRoleControlledStr) { |
| return cricket::ICEROLE_CONTROLLED; |
| } |
| NOTREACHED(); |
| return cricket::ICEROLE_UNKNOWN; |
| } |
| |
| static bool RTCIceParametersAreEqual(const RTCIceParameters* a, |
| const RTCIceParameters* b) { |
| return a->usernameFragment() == b->usernameFragment() && |
| a->password() == b->password(); |
| } |
| |
| void RTCIceTransport::start(RTCIceParameters* remote_parameters, |
| const String& role_string, |
| ExceptionState& exception_state) { |
| if (RaiseExceptionIfClosed(exception_state)) { |
| return; |
| } |
| if (!remote_parameters->hasUsernameFragment() || |
| !remote_parameters->hasPassword()) { |
| exception_state.ThrowTypeError( |
| "remoteParameters must have usernameFragment and password fields set."); |
| return; |
| } |
| cricket::IceRole role = IceRoleFromString(role_string); |
| if (role_ != cricket::ICEROLE_UNKNOWN && role != role_) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "Cannot change role once start() has been called."); |
| return; |
| } |
| if (remote_parameters_ && |
| RTCIceParametersAreEqual(remote_parameters_, remote_parameters)) { |
| // No change to remote parameters: do nothing. |
| return; |
| } |
| if (!remote_parameters_) { |
| // Calling start() for the first time. |
| role_ = role; |
| if (remote_candidates_.size() > 0) { |
| state_ = webrtc::IceTransportState::kChecking; |
| } |
| Vector<cricket::Candidate> initial_remote_candidates; |
| for (RTCIceCandidate* remote_candidate : remote_candidates_) { |
| // This conversion is safe since we throw an exception in |
| // addRemoteCandidate on malformed ICE candidates. |
| initial_remote_candidates.push_back( |
| *ConvertToCricketIceCandidate(*remote_candidate)); |
| } |
| proxy_->Start(ConvertIceParameters(remote_parameters), role, |
| initial_remote_candidates); |
| if (consumer_) { |
| consumer_->OnIceTransportStarted(); |
| } |
| } else { |
| remote_candidates_.clear(); |
| state_ = webrtc::IceTransportState::kNew; |
| proxy_->HandleRemoteRestart(ConvertIceParameters(remote_parameters)); |
| } |
| |
| remote_parameters_ = remote_parameters; |
| } |
| |
| void RTCIceTransport::stop() { |
| Close(CloseReason::kStopped); |
| } |
| |
| void RTCIceTransport::addRemoteCandidate(RTCIceCandidate* remote_candidate, |
| ExceptionState& exception_state) { |
| if (RaiseExceptionIfClosed(exception_state)) { |
| return; |
| } |
| base::Optional<cricket::Candidate> converted_remote_candidate = |
| ConvertToCricketIceCandidate(*remote_candidate); |
| if (!converted_remote_candidate) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kOperationError, |
| "Invalid ICE candidate."); |
| return; |
| } |
| remote_candidates_.push_back(remote_candidate); |
| if (remote_parameters_) { |
| proxy_->AddRemoteCandidate(*converted_remote_candidate); |
| state_ = webrtc::IceTransportState::kChecking; |
| } |
| } |
| |
| void RTCIceTransport::GenerateLocalParameters() { |
| local_parameters_ = RTCIceParameters::Create(); |
| local_parameters_->setUsernameFragment( |
| String::FromUTF8(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH))); |
| local_parameters_->setPassword( |
| String::FromUTF8(rtc::CreateRandomString(cricket::ICE_PWD_LENGTH))); |
| } |
| |
| void RTCIceTransport::OnGatheringStateChanged( |
| cricket::IceGatheringState new_state) { |
| if (new_state == gathering_state_) { |
| return; |
| } |
| if (new_state == cricket::kIceGatheringComplete) { |
| // Generate a null ICE candidate to signal the end of candidates. |
| DispatchEvent(*RTCPeerConnectionIceEvent::Create(nullptr)); |
| } |
| gathering_state_ = new_state; |
| DispatchEvent(*Event::Create(event_type_names::kGatheringstatechange)); |
| } |
| |
| void RTCIceTransport::OnCandidateGathered( |
| const cricket::Candidate& parsed_candidate) { |
| RTCIceCandidate* candidate = ConvertToRtcIceCandidate(parsed_candidate); |
| local_candidates_.push_back(candidate); |
| RTCPeerConnectionIceEventInit* event_init = |
| RTCPeerConnectionIceEventInit::Create(); |
| event_init->setCandidate(candidate); |
| DispatchEvent(*RTCPeerConnectionIceEvent::Create( |
| event_type_names::kIcecandidate, event_init)); |
| } |
| |
| void RTCIceTransport::OnStateChanged(webrtc::IceTransportState new_state) { |
| // MONKEY PATCH: |
| // Due to crbug.com/957487, the lower layers signal kFailed when they |
| // should have been sending kDisconnected. Remap the state. |
| if (new_state == webrtc::IceTransportState::kFailed) { |
| LOG(INFO) << "crbug/957487: Remapping ICE state failed to disconnected"; |
| new_state = webrtc::IceTransportState::kDisconnected; |
| } |
| if (new_state == state_) { |
| return; |
| } |
| state_ = new_state; |
| if (state_ == webrtc::IceTransportState::kFailed) { |
| selected_candidate_pair_ = nullptr; |
| } |
| // Make sure the peerconnection's state is updated before the event fires. |
| if (peer_connection_) { |
| peer_connection_->UpdateIceConnectionState(); |
| } |
| DispatchEvent(*Event::Create(event_type_names::kStatechange)); |
| if (state_ == webrtc::IceTransportState::kClosed || |
| state_ == webrtc::IceTransportState::kFailed) { |
| stop(); |
| } |
| } |
| |
| void RTCIceTransport::OnSelectedCandidatePairChanged( |
| const std::pair<cricket::Candidate, cricket::Candidate>& |
| selected_candidate_pair) { |
| RTCIceCandidate* local = |
| ConvertToRtcIceCandidate(selected_candidate_pair.first); |
| RTCIceCandidate* remote = |
| ConvertToRtcIceCandidate(selected_candidate_pair.second); |
| selected_candidate_pair_ = RTCIceCandidatePair::Create(); |
| selected_candidate_pair_->setLocal(local); |
| selected_candidate_pair_->setRemote(remote); |
| DispatchEvent(*Event::Create(event_type_names::kSelectedcandidatepairchange)); |
| } |
| |
| void RTCIceTransport::Close(CloseReason reason) { |
| if (IsClosed()) { |
| return; |
| } |
| if (HasConsumer()) { |
| consumer_->OnIceTransportClosed(reason); |
| } |
| // Notifying the consumer that we're closing should cause it to disconnect. |
| DCHECK(!HasConsumer()); |
| state_ = webrtc::IceTransportState::kClosed; |
| selected_candidate_pair_ = nullptr; |
| proxy_.reset(); |
| } |
| |
| bool RTCIceTransport::RaiseExceptionIfClosed( |
| ExceptionState& exception_state) const { |
| if (IsClosed()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "The RTCIceTransport's state is 'closed'."); |
| return true; |
| } |
| return false; |
| } |
| |
| const AtomicString& RTCIceTransport::InterfaceName() const { |
| return event_target_names::kRTCIceTransport; |
| } |
| |
| ExecutionContext* RTCIceTransport::GetExecutionContext() const { |
| return ContextLifecycleObserver::GetExecutionContext(); |
| } |
| |
| void RTCIceTransport::ContextDestroyed(ExecutionContext*) { |
| Close(CloseReason::kContextDestroyed); |
| } |
| |
| bool RTCIceTransport::HasPendingActivity() const { |
| // Only allow the RTCIceTransport to be garbage collected if the ICE |
| // implementation is not active. |
| return !!proxy_; |
| } |
| |
| void RTCIceTransport::Trace(blink::Visitor* visitor) { |
| visitor->Trace(local_candidates_); |
| visitor->Trace(remote_candidates_); |
| visitor->Trace(local_parameters_); |
| visitor->Trace(remote_parameters_); |
| visitor->Trace(selected_candidate_pair_); |
| visitor->Trace(consumer_); |
| visitor->Trace(peer_connection_); |
| EventTargetWithInlineData::Trace(visitor); |
| ContextLifecycleObserver::Trace(visitor); |
| } |
| |
| void RTCIceTransport::Dispose() { |
| Close(CloseReason::kDisposed); |
| } |
| |
| } // namespace blink |