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