OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "remoting/host/chromoting_host.h" | 5 #include "remoting/host/chromoting_host.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/callback.h" | 8 #include "base/callback.h" |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/message_loop_proxy.h" | 10 #include "base/message_loop_proxy.h" |
11 #include "build/build_config.h" | 11 #include "build/build_config.h" |
12 #include "remoting/base/constants.h" | 12 #include "remoting/base/constants.h" |
13 #include "remoting/codec/audio_encoder.h" | |
14 #include "remoting/codec/audio_encoder_speex.h" | |
15 #include "remoting/codec/audio_encoder_verbatim.h" | |
16 #include "remoting/codec/video_encoder.h" | |
17 #include "remoting/codec/video_encoder_row_based.h" | |
18 #include "remoting/codec/video_encoder_vp8.h" | |
19 #include "remoting/host/audio_capturer.h" | |
20 #include "remoting/host/audio_scheduler.h" | |
21 #include "remoting/host/chromoting_host_context.h" | 13 #include "remoting/host/chromoting_host_context.h" |
22 #include "remoting/host/desktop_environment.h" | 14 #include "remoting/host/desktop_environment.h" |
| 15 #include "remoting/host/desktop_environment_factory.h" |
23 #include "remoting/host/event_executor.h" | 16 #include "remoting/host/event_executor.h" |
24 #include "remoting/host/host_config.h" | 17 #include "remoting/host/host_config.h" |
25 #include "remoting/host/screen_recorder.h" | |
26 #include "remoting/protocol/connection_to_client.h" | 18 #include "remoting/protocol/connection_to_client.h" |
27 #include "remoting/protocol/client_stub.h" | 19 #include "remoting/protocol/client_stub.h" |
28 #include "remoting/protocol/host_stub.h" | 20 #include "remoting/protocol/host_stub.h" |
29 #include "remoting/protocol/input_stub.h" | 21 #include "remoting/protocol/input_stub.h" |
30 #include "remoting/protocol/session_config.h" | 22 #include "remoting/protocol/session_config.h" |
31 | 23 |
32 using remoting::protocol::ConnectionToClient; | 24 using remoting::protocol::ConnectionToClient; |
33 using remoting::protocol::InputStub; | 25 using remoting::protocol::InputStub; |
34 | 26 |
35 namespace remoting { | 27 namespace remoting { |
(...skipping 24 matching lines...) Expand all Loading... |
60 | 52 |
61 // Don't use initial delay unless the last request was an error. | 53 // Don't use initial delay unless the last request was an error. |
62 false, | 54 false, |
63 }; | 55 }; |
64 | 56 |
65 } // namespace | 57 } // namespace |
66 | 58 |
67 ChromotingHost::ChromotingHost( | 59 ChromotingHost::ChromotingHost( |
68 ChromotingHostContext* context, | 60 ChromotingHostContext* context, |
69 SignalStrategy* signal_strategy, | 61 SignalStrategy* signal_strategy, |
70 DesktopEnvironment* environment, | 62 DesktopEnvironmentFactory* desktop_environment_factory, |
71 scoped_ptr<protocol::SessionManager> session_manager) | 63 scoped_ptr<protocol::SessionManager> session_manager) |
72 : context_(context), | 64 : context_(context), |
73 desktop_environment_(environment), | 65 desktop_environment_factory_(desktop_environment_factory), |
74 session_manager_(session_manager.Pass()), | 66 session_manager_(session_manager.Pass()), |
75 signal_strategy_(signal_strategy), | 67 signal_strategy_(signal_strategy), |
76 stopping_recorders_(0), | 68 clients_count_(0), |
77 state_(kInitial), | 69 state_(kInitial), |
78 protocol_config_(protocol::CandidateSessionConfig::CreateDefault()), | 70 protocol_config_(protocol::CandidateSessionConfig::CreateDefault()), |
79 login_backoff_(&kDefaultBackoffPolicy), | 71 login_backoff_(&kDefaultBackoffPolicy), |
80 authenticating_client_(false), | 72 authenticating_client_(false), |
81 reject_authenticating_client_(false) { | 73 reject_authenticating_client_(false) { |
82 DCHECK(context_); | 74 DCHECK(context_); |
83 DCHECK(signal_strategy); | 75 DCHECK(signal_strategy); |
84 DCHECK(desktop_environment_); | |
85 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 76 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
86 | 77 |
87 if (!AudioCapturer::IsSupported()) { | 78 if (!desktop_environment_factory_->SupportsAudioCapture()) { |
88 // Disable audio by replacing our list of supported audio configurations | 79 // Disable audio by replacing our list of supported audio configurations |
89 // with the NONE config. | 80 // with the NONE config. |
90 protocol_config_->mutable_audio_configs()->clear(); | 81 protocol_config_->mutable_audio_configs()->clear(); |
91 protocol_config_->mutable_audio_configs()->push_back( | 82 protocol_config_->mutable_audio_configs()->push_back( |
92 protocol::ChannelConfig()); | 83 protocol::ChannelConfig()); |
93 } | 84 } |
94 } | 85 } |
95 | 86 |
96 ChromotingHost::~ChromotingHost() { | 87 ChromotingHost::~ChromotingHost() { |
97 DCHECK(clients_.empty()); | 88 DCHECK(clients_.empty()); |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
135 // We are already stopping. Just save the task. | 126 // We are already stopping. Just save the task. |
136 if (!shutdown_task.is_null()) | 127 if (!shutdown_task.is_null()) |
137 shutdown_tasks_.push_back(shutdown_task); | 128 shutdown_tasks_.push_back(shutdown_task); |
138 break; | 129 break; |
139 | 130 |
140 case kStarted: | 131 case kStarted: |
141 if (!shutdown_task.is_null()) | 132 if (!shutdown_task.is_null()) |
142 shutdown_tasks_.push_back(shutdown_task); | 133 shutdown_tasks_.push_back(shutdown_task); |
143 state_ = kStopping; | 134 state_ = kStopping; |
144 | 135 |
145 // Disconnect all of the clients, implicitly stopping the ScreenRecorder. | 136 // Disconnect all of the clients. |
146 while (!clients_.empty()) { | 137 while (!clients_.empty()) { |
147 clients_.front()->Disconnect(); | 138 clients_.front()->Disconnect(); |
148 } | 139 } |
149 DCHECK(!recorder_.get()); | |
150 DCHECK(!audio_scheduler_.get()); | |
151 | 140 |
152 // Destroy session manager. | 141 // Run the remaining shutdown tasks. |
153 session_manager_.reset(); | 142 if (state_ == kStopping && !clients_count_) |
| 143 ShutdownFinish(); |
154 | 144 |
155 if (!stopping_recorders_) | |
156 ShutdownFinish(); | |
157 break; | 145 break; |
158 } | 146 } |
159 } | 147 } |
160 | 148 |
161 void ChromotingHost::AddStatusObserver(HostStatusObserver* observer) { | 149 void ChromotingHost::AddStatusObserver(HostStatusObserver* observer) { |
162 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 150 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
163 status_observers_.AddObserver(observer); | 151 status_observers_.AddObserver(observer); |
164 } | 152 } |
165 | 153 |
166 void ChromotingHost::RemoveStatusObserver(HostStatusObserver* observer) { | 154 void ChromotingHost::RemoveStatusObserver(HostStatusObserver* observer) { |
(...skipping 23 matching lines...) Expand all Loading... |
190 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 178 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
191 | 179 |
192 login_backoff_.Reset(); | 180 login_backoff_.Reset(); |
193 | 181 |
194 // Disconnect all other clients. | 182 // Disconnect all other clients. |
195 // Iterate over a copy of the list of clients, to avoid mutating the list | 183 // Iterate over a copy of the list of clients, to avoid mutating the list |
196 // while iterating over it. | 184 // while iterating over it. |
197 ClientList clients_copy(clients_); | 185 ClientList clients_copy(clients_); |
198 for (ClientList::const_iterator other_client = clients_copy.begin(); | 186 for (ClientList::const_iterator other_client = clients_copy.begin(); |
199 other_client != clients_copy.end(); ++other_client) { | 187 other_client != clients_copy.end(); ++other_client) { |
200 if ((*other_client) != client) { | 188 if (other_client->get() != client) { |
201 (*other_client)->Disconnect(); | 189 (*other_client)->Disconnect(); |
202 } | 190 } |
203 } | 191 } |
204 | 192 |
205 // Disconnects above must have destroyed all other clients and |recorder_|. | 193 // Disconnects above must have destroyed all other clients and |recorder_|. |
206 DCHECK_EQ(clients_.size(), 1U); | 194 DCHECK_EQ(clients_.size(), 1U); |
207 DCHECK(!recorder_.get()); | |
208 DCHECK(!audio_scheduler_.get()); | |
209 | 195 |
210 // Notify observers that there is at least one authenticated client. | 196 // Notify observers that there is at least one authenticated client. |
211 const std::string& jid = client->client_jid(); | 197 const std::string& jid = client->client_jid(); |
212 | 198 |
213 reject_authenticating_client_ = false; | 199 reject_authenticating_client_ = false; |
214 | 200 |
215 authenticating_client_ = true; | 201 authenticating_client_ = true; |
216 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 202 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
217 OnClientAuthenticated(jid)); | 203 OnClientAuthenticated(jid)); |
218 authenticating_client_ = false; | 204 authenticating_client_ = false; |
219 | 205 |
220 if (reject_authenticating_client_) { | 206 if (reject_authenticating_client_) { |
221 client->Disconnect(); | 207 client->Disconnect(); |
222 } | 208 } |
223 } | 209 } |
224 | 210 |
225 void ChromotingHost::OnSessionChannelsConnected(ClientSession* client) { | 211 void ChromotingHost::OnSessionChannelsConnected(ClientSession* client) { |
226 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 212 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
227 | 213 |
228 // Then we create a ScreenRecorder passing the message loops that | 214 // Notify observers. |
229 // it should run on. | |
230 VideoEncoder* video_encoder = | |
231 CreateVideoEncoder(client->connection()->session()->config()); | |
232 | |
233 recorder_ = new ScreenRecorder(context_->capture_task_runner(), | |
234 context_->encode_task_runner(), | |
235 context_->network_task_runner(), | |
236 desktop_environment_->capturer(), | |
237 video_encoder); | |
238 if (client->connection()->session()->config().is_audio_enabled()) { | |
239 scoped_ptr<AudioEncoder> audio_encoder = | |
240 CreateAudioEncoder(client->connection()->session()->config()); | |
241 audio_scheduler_ = new AudioScheduler( | |
242 context_->audio_task_runner(), | |
243 context_->network_task_runner(), | |
244 desktop_environment_->audio_capturer(), | |
245 audio_encoder.Pass(), | |
246 client->connection()->audio_stub()); | |
247 } | |
248 | |
249 // Immediately add the connection and start the session. | |
250 recorder_->AddConnection(client->connection()); | |
251 recorder_->Start(); | |
252 desktop_environment_->OnSessionStarted(client->CreateClipboardProxy()); | |
253 | |
254 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 215 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
255 OnClientConnected(client->client_jid())); | 216 OnClientConnected(client->client_jid())); |
256 } | 217 } |
257 | 218 |
258 void ChromotingHost::OnSessionAuthenticationFailed(ClientSession* client) { | 219 void ChromotingHost::OnSessionAuthenticationFailed(ClientSession* client) { |
259 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 220 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
260 | 221 |
261 // Notify observers. | 222 // Notify observers. |
262 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 223 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
263 OnAccessDenied(client->client_jid())); | 224 OnAccessDenied(client->client_jid())); |
264 } | 225 } |
265 | 226 |
266 void ChromotingHost::OnSessionClosed(ClientSession* client) { | 227 void ChromotingHost::OnSessionClosed(ClientSession* client) { |
267 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 228 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
268 | 229 |
269 scoped_ptr<ClientSession> client_destroyer(client); | 230 ClientList::iterator it = clients_.begin(); |
270 | 231 for (; it != clients_.end(); ++it) { |
271 ClientList::iterator it = std::find(clients_.begin(), clients_.end(), client); | 232 if (it->get() == client) { |
| 233 break; |
| 234 } |
| 235 } |
272 CHECK(it != clients_.end()); | 236 CHECK(it != clients_.end()); |
273 clients_.erase(it); | |
274 | |
275 if (recorder_.get()) { | |
276 recorder_->RemoveConnection(client->connection()); | |
277 } | |
278 | |
279 if (audio_scheduler_.get()) { | |
280 audio_scheduler_->OnClientDisconnected(); | |
281 StopAudioScheduler(); | |
282 } | |
283 | 237 |
284 if (client->is_authenticated()) { | 238 if (client->is_authenticated()) { |
285 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 239 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
286 OnClientDisconnected(client->client_jid())); | 240 OnClientDisconnected(client->client_jid())); |
| 241 } |
287 | 242 |
288 // TODO(sergeyu): This teardown logic belongs to ClientSession | 243 client->Stop(base::Bind(&ChromotingHost::OnClientStopped, this)); |
289 // class. It should start/stop screen recorder or tell the host | 244 clients_.erase(it); |
290 // when to do it. | |
291 if (recorder_.get()) { | |
292 // Currently we don't allow more than one simultaneous connection, | |
293 // so we need to shutdown recorder when a client disconnects. | |
294 StopScreenRecorder(); | |
295 } | |
296 desktop_environment_->OnSessionFinished(); | |
297 } | |
298 } | 245 } |
299 | 246 |
300 void ChromotingHost::OnSessionSequenceNumber(ClientSession* session, | 247 void ChromotingHost::OnSessionSequenceNumber(ClientSession* session, |
301 int64 sequence_number) { | 248 int64 sequence_number) { |
302 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 249 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
303 if (recorder_.get()) | |
304 recorder_->UpdateSequenceNumber(sequence_number); | |
305 } | 250 } |
306 | 251 |
307 void ChromotingHost::OnSessionRouteChange( | 252 void ChromotingHost::OnSessionRouteChange( |
308 ClientSession* session, | 253 ClientSession* session, |
309 const std::string& channel_name, | 254 const std::string& channel_name, |
310 const protocol::TransportRoute& route) { | 255 const protocol::TransportRoute& route) { |
311 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 256 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
312 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 257 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
313 OnClientRouteChange(session->client_jid(), channel_name, | 258 OnClientRouteChange(session->client_jid(), channel_name, |
314 route)); | 259 route)); |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
348 *response = protocol::SessionManager::INCOMPATIBLE; | 293 *response = protocol::SessionManager::INCOMPATIBLE; |
349 return; | 294 return; |
350 } | 295 } |
351 | 296 |
352 session->set_config(config); | 297 session->set_config(config); |
353 | 298 |
354 *response = protocol::SessionManager::ACCEPT; | 299 *response = protocol::SessionManager::ACCEPT; |
355 | 300 |
356 LOG(INFO) << "Client connected: " << session->jid(); | 301 LOG(INFO) << "Client connected: " << session->jid(); |
357 | 302 |
| 303 // Create the desktop integration implementation for the client to use. |
| 304 scoped_ptr<DesktopEnvironment> desktop_environment = |
| 305 desktop_environment_factory_->Create(context_); |
| 306 |
358 // Create a client object. | 307 // Create a client object. |
359 scoped_ptr<protocol::ConnectionToClient> connection( | 308 scoped_ptr<protocol::ConnectionToClient> connection( |
360 new protocol::ConnectionToClient(session)); | 309 new protocol::ConnectionToClient(session)); |
361 ClientSession* client = new ClientSession( | 310 scoped_refptr<ClientSession> client = new ClientSession( |
362 this, | 311 this, |
| 312 context_->capture_task_runner(), |
| 313 context_->encode_task_runner(), |
| 314 context_->network_task_runner(), |
363 connection.Pass(), | 315 connection.Pass(), |
364 desktop_environment_->event_executor(), | 316 desktop_environment.Pass(), |
365 desktop_environment_->event_executor(), | |
366 desktop_environment_->capturer(), | |
367 max_session_duration_); | 317 max_session_duration_); |
368 clients_.push_back(client); | 318 clients_.push_back(client); |
| 319 clients_count_++; |
369 } | 320 } |
370 | 321 |
371 void ChromotingHost::set_protocol_config( | 322 void ChromotingHost::set_protocol_config( |
372 protocol::CandidateSessionConfig* config) { | 323 protocol::CandidateSessionConfig* config) { |
373 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 324 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
374 DCHECK(config); | 325 DCHECK(config); |
375 DCHECK_EQ(state_, kInitial); | 326 DCHECK_EQ(state_, kInitial); |
376 protocol_config_.reset(config); | 327 protocol_config_.reset(config); |
377 } | 328 } |
378 | 329 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
417 } | 368 } |
418 } | 369 } |
419 | 370 |
420 void ChromotingHost::SetUiStrings(const UiStrings& ui_strings) { | 371 void ChromotingHost::SetUiStrings(const UiStrings& ui_strings) { |
421 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 372 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
422 DCHECK_EQ(state_, kInitial); | 373 DCHECK_EQ(state_, kInitial); |
423 | 374 |
424 ui_strings_ = ui_strings; | 375 ui_strings_ = ui_strings; |
425 } | 376 } |
426 | 377 |
427 // TODO(sergeyu): Move this to SessionManager? | 378 void ChromotingHost::OnClientStopped() { |
428 // static | 379 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
429 VideoEncoder* ChromotingHost::CreateVideoEncoder( | |
430 const protocol::SessionConfig& config) { | |
431 const protocol::ChannelConfig& video_config = config.video_config(); | |
432 | 380 |
433 if (video_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) { | 381 --clients_count_; |
434 return VideoEncoderRowBased::CreateVerbatimEncoder(); | 382 if (state_ == kStopping && !clients_count_) |
435 } else if (video_config.codec == protocol::ChannelConfig::CODEC_ZIP) { | |
436 return VideoEncoderRowBased::CreateZlibEncoder(); | |
437 } else if (video_config.codec == protocol::ChannelConfig::CODEC_VP8) { | |
438 return new remoting::VideoEncoderVp8(); | |
439 } | |
440 | |
441 return NULL; | |
442 } | |
443 | |
444 // static | |
445 scoped_ptr<AudioEncoder> ChromotingHost::CreateAudioEncoder( | |
446 const protocol::SessionConfig& config) { | |
447 const protocol::ChannelConfig& audio_config = config.audio_config(); | |
448 | |
449 if (audio_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) { | |
450 return scoped_ptr<AudioEncoder>(new AudioEncoderVerbatim()); | |
451 } else if (audio_config.codec == protocol::ChannelConfig::CODEC_SPEEX) { | |
452 return scoped_ptr<AudioEncoder>(new AudioEncoderSpeex()); | |
453 } | |
454 | |
455 NOTIMPLEMENTED(); | |
456 return scoped_ptr<AudioEncoder>(NULL); | |
457 } | |
458 | |
459 void ChromotingHost::StopScreenRecorder() { | |
460 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | |
461 DCHECK(recorder_.get()); | |
462 | |
463 ++stopping_recorders_; | |
464 scoped_refptr<ScreenRecorder> recorder = recorder_; | |
465 recorder_ = NULL; | |
466 recorder->Stop(base::Bind(&ChromotingHost::OnRecorderStopped, this)); | |
467 } | |
468 | |
469 void ChromotingHost::StopAudioScheduler() { | |
470 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | |
471 DCHECK(audio_scheduler_.get()); | |
472 | |
473 ++stopping_recorders_; | |
474 scoped_refptr<AudioScheduler> recorder = audio_scheduler_; | |
475 audio_scheduler_ = NULL; | |
476 recorder->Stop(base::Bind(&ChromotingHost::OnRecorderStopped, this)); | |
477 } | |
478 | |
479 void ChromotingHost::OnRecorderStopped() { | |
480 if (!context_->network_task_runner()->BelongsToCurrentThread()) { | |
481 context_->network_task_runner()->PostTask( | |
482 FROM_HERE, base::Bind(&ChromotingHost::OnRecorderStopped, this)); | |
483 return; | |
484 } | |
485 | |
486 --stopping_recorders_; | |
487 DCHECK_GE(stopping_recorders_, 0); | |
488 | |
489 if (!stopping_recorders_ && state_ == kStopping) | |
490 ShutdownFinish(); | 383 ShutdownFinish(); |
491 } | 384 } |
492 | 385 |
493 void ChromotingHost::ShutdownFinish() { | 386 void ChromotingHost::ShutdownFinish() { |
494 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); | 387 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
495 DCHECK(!stopping_recorders_); | 388 DCHECK_EQ(state_, kStopping); |
| 389 |
| 390 // Destroy session manager. |
| 391 session_manager_.reset(); |
496 | 392 |
497 state_ = kStopped; | 393 state_ = kStopped; |
498 | 394 |
499 // Keep reference to |this|, so that we don't get destroyed while | 395 // Keep reference to |this|, so that we don't get destroyed while |
500 // sending notifications. | 396 // sending notifications. |
501 scoped_refptr<ChromotingHost> self(this); | 397 scoped_refptr<ChromotingHost> self(this); |
502 | 398 |
503 // Notify observers. | 399 // Notify observers. |
504 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, | 400 FOR_EACH_OBSERVER(HostStatusObserver, status_observers_, |
505 OnShutdown()); | 401 OnShutdown()); |
506 | 402 |
507 for (std::vector<base::Closure>::iterator it = shutdown_tasks_.begin(); | 403 for (std::vector<base::Closure>::iterator it = shutdown_tasks_.begin(); |
508 it != shutdown_tasks_.end(); ++it) { | 404 it != shutdown_tasks_.end(); ++it) { |
509 it->Run(); | 405 it->Run(); |
510 } | 406 } |
511 shutdown_tasks_.clear(); | 407 shutdown_tasks_.clear(); |
512 } | 408 } |
513 | 409 |
514 } // namespace remoting | 410 } // namespace remoting |
OLD | NEW |