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