Revert 168224 - Update V8 to version 3.15.4.
[chromium-blink-merge.git] / remoting / client / plugin / chromoting_instance.cc
blobf3d006cbe2807974b3998ede23121c13365b470c
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
3 // found in the LICENSE file.
5 #include "remoting/client/plugin/chromoting_instance.h"
7 #include <string>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/json/json_reader.h"
13 #include "base/json/json_writer.h"
14 #include "base/lazy_instance.h"
15 #include "base/logging.h"
16 #include "base/stringprintf.h"
17 #include "base/string_split.h"
18 #include "base/synchronization/lock.h"
19 #include "base/synchronization/waitable_event.h"
20 #include "base/threading/thread.h"
21 #include "base/values.h"
22 #include "jingle/glue/thread_wrapper.h"
23 #include "media/base/media.h"
24 #include "net/socket/ssl_server_socket.h"
25 #include "ppapi/cpp/completion_callback.h"
26 #include "ppapi/cpp/input_event.h"
27 #include "ppapi/cpp/mouse_cursor.h"
28 #include "ppapi/cpp/rect.h"
29 #include "remoting/base/constants.h"
30 #include "remoting/base/util.h"
31 #include "remoting/client/client_config.h"
32 #include "remoting/client/chromoting_client.h"
33 #include "remoting/client/frame_consumer_proxy.h"
34 #include "remoting/client/plugin/pepper_audio_player.h"
35 #include "remoting/client/plugin/pepper_input_handler.h"
36 #include "remoting/client/plugin/pepper_port_allocator.h"
37 #include "remoting/client/plugin/pepper_view.h"
38 #include "remoting/client/plugin/pepper_xmpp_proxy.h"
39 #include "remoting/client/rectangle_update_decoder.h"
40 #include "remoting/protocol/connection_to_host.h"
41 #include "remoting/protocol/host_stub.h"
42 #include "remoting/protocol/libjingle_transport_factory.h"
44 // Windows defines 'PostMessage', so we have to undef it.
45 #if defined(PostMessage)
46 #undef PostMessage
47 #endif
49 namespace remoting {
51 namespace {
53 // 32-bit BGRA is 4 bytes per pixel.
54 const int kBytesPerPixel = 4;
56 const int kPerfStatsIntervalMs = 1000;
58 std::string ConnectionStateToString(protocol::ConnectionToHost::State state) {
59 // Values returned by this function must match the
60 // remoting.ClientSession.State enum in JS code.
61 switch (state) {
62 case protocol::ConnectionToHost::INITIALIZING:
63 return "INITIALIZING";
64 case protocol::ConnectionToHost::CONNECTING:
65 return "CONNECTING";
66 case protocol::ConnectionToHost::CONNECTED:
67 return "CONNECTED";
68 case protocol::ConnectionToHost::CLOSED:
69 return "CLOSED";
70 case protocol::ConnectionToHost::FAILED:
71 return "FAILED";
73 NOTREACHED();
74 return "";
77 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
78 // and let it handle it, but it would be hard to fix it now because
79 // client plugin and webapp versions may not be in sync. It should be
80 // easy to do after we are finished moving the client plugin to NaCl.
81 std::string ConnectionErrorToString(protocol::ErrorCode error) {
82 // Values returned by this function must match the
83 // remoting.ClientSession.Error enum in JS code.
84 switch (error) {
85 case protocol::OK:
86 return "NONE";
88 case protocol::PEER_IS_OFFLINE:
89 return "HOST_IS_OFFLINE";
91 case protocol::SESSION_REJECTED:
92 case protocol::AUTHENTICATION_FAILED:
93 return "SESSION_REJECTED";
95 case protocol::INCOMPATIBLE_PROTOCOL:
96 return "INCOMPATIBLE_PROTOCOL";
98 case protocol::HOST_OVERLOAD:
99 return "HOST_OVERLOAD";
101 case protocol::CHANNEL_CONNECTION_ERROR:
102 case protocol::SIGNALING_ERROR:
103 case protocol::SIGNALING_TIMEOUT:
104 case protocol::UNKNOWN_ERROR:
105 return "NETWORK_FAILURE";
107 DLOG(FATAL) << "Unknown error code" << error;
108 return "";
111 // This flag blocks LOGs to the UI if we're already in the middle of logging
112 // to the UI. This prevents a potential infinite loop if we encounter an error
113 // while sending the log message to the UI.
114 bool g_logging_to_plugin = false;
115 bool g_has_logging_instance = false;
116 base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
117 g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
118 base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
119 g_logging_instance = LAZY_INSTANCE_INITIALIZER;
120 base::LazyInstance<base::Lock>::Leaky
121 g_logging_lock = LAZY_INSTANCE_INITIALIZER;
122 logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
124 } // namespace
126 // String sent in the "hello" message to the plugin to describe features.
127 const char ChromotingInstance::kApiFeatures[] =
128 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
129 "notifyClientDimensions pauseVideo pauseAudio";
131 bool ChromotingInstance::ParseAuthMethods(const std::string& auth_methods_str,
132 ClientConfig* config) {
133 std::vector<std::string> auth_methods;
134 base::SplitString(auth_methods_str, ',', &auth_methods);
135 for (std::vector<std::string>::iterator it = auth_methods.begin();
136 it != auth_methods.end(); ++it) {
137 protocol::AuthenticationMethod authentication_method =
138 protocol::AuthenticationMethod::FromString(*it);
139 if (authentication_method.is_valid())
140 config->authentication_methods.push_back(authentication_method);
142 if (config->authentication_methods.empty()) {
143 LOG(ERROR) << "No valid authentication methods specified.";
144 return false;
147 return true;
150 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
151 : pp::Instance(pp_instance),
152 initialized_(false),
153 plugin_task_runner_(
154 new PluginThreadTaskRunner(&plugin_thread_delegate_)),
155 context_(plugin_task_runner_),
156 input_tracker_(&mouse_input_filter_),
157 #if defined(OS_MACOSX)
158 // On Mac we need an extra filter to inject missing keyup events.
159 // See remoting/client/plugin/mac_key_event_processor.h for more details.
160 mac_key_event_processor_(&input_tracker_),
161 key_mapper_(&mac_key_event_processor_),
162 #else
163 key_mapper_(&input_tracker_),
164 #endif
165 input_handler_(&key_mapper_),
166 weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
167 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
168 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
170 // Resister this instance to handle debug log messsages.
171 RegisterLoggingInstance();
173 // Send hello message.
174 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
175 data->SetInteger("apiVersion", kApiVersion);
176 data->SetString("apiFeatures", kApiFeatures);
177 data->SetInteger("apiMinVersion", kApiMinMessagingVersion);
178 PostChromotingMessage("hello", data.Pass());
181 ChromotingInstance::~ChromotingInstance() {
182 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
184 // Unregister this instance so that debug log messages will no longer be sent
185 // to it. This will stop all logging in all Chromoting instances.
186 UnregisterLoggingInstance();
188 // PepperView must be destroyed before the client.
189 view_.reset();
191 if (client_.get()) {
192 client_->Stop(base::Bind(&PluginThreadTaskRunner::Quit,
193 plugin_task_runner_));
194 } else {
195 plugin_task_runner_->Quit();
198 // Ensure that nothing touches the plugin thread delegate after this point.
199 plugin_task_runner_->DetachAndRunShutdownLoop();
201 // Stopping the context shuts down all chromoting threads.
202 context_.Stop();
205 bool ChromotingInstance::Init(uint32_t argc,
206 const char* argn[],
207 const char* argv[]) {
208 CHECK(!initialized_);
209 initialized_ = true;
211 VLOG(1) << "Started ChromotingInstance::Init";
213 // Check to make sure the media library is initialized.
214 // http://crbug.com/91521.
215 if (!media::IsMediaLibraryInitialized()) {
216 LOG(ERROR) << "Media library not initialized.";
217 return false;
220 // Enable support for SSL server sockets, which must be done as early as
221 // possible, preferably before any NSS SSL sockets (client or server) have
222 // been created.
223 // It's possible that the hosting process has already made use of SSL, in
224 // which case, there may be a slight race.
225 net::EnableSSLServerSockets();
227 // Start all the threads.
228 context_.Start();
230 // Create the chromoting objects that don't depend on the network connection.
231 // RectangleUpdateDecoder runs on a separate thread so for now we wrap
232 // PepperView with a ref-counted proxy object.
233 scoped_refptr<FrameConsumerProxy> consumer_proxy =
234 new FrameConsumerProxy(plugin_task_runner_);
235 rectangle_decoder_ = new RectangleUpdateDecoder(context_.main_task_runner(),
236 context_.decode_task_runner(),
237 consumer_proxy);
238 view_.reset(new PepperView(this, &context_, rectangle_decoder_.get()));
239 consumer_proxy->Attach(view_->AsWeakPtr());
241 return true;
244 void ChromotingInstance::HandleMessage(const pp::Var& message) {
245 if (!message.is_string()) {
246 LOG(ERROR) << "Received a message that is not a string.";
247 return;
250 scoped_ptr<base::Value> json(
251 base::JSONReader::Read(message.AsString(),
252 base::JSON_ALLOW_TRAILING_COMMAS));
253 base::DictionaryValue* message_dict = NULL;
254 std::string method;
255 base::DictionaryValue* data = NULL;
256 if (!json.get() ||
257 !json->GetAsDictionary(&message_dict) ||
258 !message_dict->GetString("method", &method) ||
259 !message_dict->GetDictionary("data", &data)) {
260 LOG(ERROR) << "Received invalid message:" << message.AsString();
261 return;
264 if (method == "connect") {
265 ClientConfig config;
266 std::string auth_methods;
267 if (!data->GetString("hostJid", &config.host_jid) ||
268 !data->GetString("hostPublicKey", &config.host_public_key) ||
269 !data->GetString("localJid", &config.local_jid) ||
270 !data->GetString("sharedSecret", &config.shared_secret) ||
271 !data->GetString("authenticationMethods", &auth_methods) ||
272 !ParseAuthMethods(auth_methods, &config) ||
273 !data->GetString("authenticationTag", &config.authentication_tag)) {
274 LOG(ERROR) << "Invalid connect() data.";
275 return;
278 Connect(config);
279 } else if (method == "disconnect") {
280 Disconnect();
281 } else if (method == "incomingIq") {
282 std::string iq;
283 if (!data->GetString("iq", &iq)) {
284 LOG(ERROR) << "Invalid onIq() data.";
285 return;
287 OnIncomingIq(iq);
288 } else if (method == "releaseAllKeys") {
289 ReleaseAllKeys();
290 } else if (method == "injectKeyEvent") {
291 int usb_keycode = 0;
292 bool is_pressed = false;
293 if (!data->GetInteger("usbKeycode", &usb_keycode) ||
294 !data->GetBoolean("pressed", &is_pressed)) {
295 LOG(ERROR) << "Invalid injectKeyEvent.";
296 return;
299 protocol::KeyEvent event;
300 event.set_usb_keycode(usb_keycode);
301 event.set_pressed(is_pressed);
302 InjectKeyEvent(event);
303 } else if (method == "remapKey") {
304 int from_keycode = 0;
305 int to_keycode = 0;
306 if (!data->GetInteger("fromKeycode", &from_keycode) ||
307 !data->GetInteger("toKeycode", &to_keycode)) {
308 LOG(ERROR) << "Invalid remapKey.";
309 return;
312 RemapKey(from_keycode, to_keycode);
313 } else if (method == "trapKey") {
314 int keycode = 0;
315 bool trap = false;
316 if (!data->GetInteger("keycode", &keycode) ||
317 !data->GetBoolean("trap", &trap)) {
318 LOG(ERROR) << "Invalid trapKey.";
319 return;
322 TrapKey(keycode, trap);
323 } else if (method == "sendClipboardItem") {
324 std::string mime_type;
325 std::string item;
326 if (!data->GetString("mimeType", &mime_type) ||
327 !data->GetString("item", &item)) {
328 LOG(ERROR) << "Invalid sendClipboardItem() data.";
329 return;
331 SendClipboardItem(mime_type, item);
332 } else if (method == "notifyClientDimensions") {
333 int width = 0;
334 int height = 0;
335 if (!data->GetInteger("width", &width) ||
336 !data->GetInteger("height", &height)) {
337 LOG(ERROR) << "Invalid notifyClientDimensions.";
338 return;
340 NotifyClientDimensions(width, height);
341 } else if (method == "pauseVideo") {
342 bool pause = false;
343 if (!data->GetBoolean("pause", &pause)) {
344 LOG(ERROR) << "Invalid pauseVideo.";
345 return;
347 PauseVideo(pause);
348 } else if (method == "pauseAudio") {
349 bool pause = false;
350 if (!data->GetBoolean("pause", &pause)) {
351 LOG(ERROR) << "Invalid pauseAudio.";
352 return;
354 PauseAudio(pause);
358 void ChromotingInstance::DidChangeView(const pp::View& view) {
359 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
361 view_->SetView(view);
363 mouse_input_filter_.set_input_size(view_->get_view_size_dips());
366 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
367 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
369 if (!IsConnected())
370 return false;
372 // TODO(wez): When we have a good hook into Host dimensions changes, move
373 // this there.
374 mouse_input_filter_.set_output_size(view_->get_screen_size());
376 return input_handler_.HandleInputEvent(event);
379 void ChromotingInstance::SetDesktopSize(const SkISize& size,
380 const SkIPoint& dpi) {
381 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
382 data->SetInteger("width", size.width());
383 data->SetInteger("height", size.height());
384 if (dpi.x())
385 data->SetInteger("x_dpi", dpi.x());
386 if (dpi.y())
387 data->SetInteger("y_dpi", dpi.y());
388 PostChromotingMessage("onDesktopSize", data.Pass());
391 void ChromotingInstance::OnConnectionState(
392 protocol::ConnectionToHost::State state,
393 protocol::ErrorCode error) {
394 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
395 data->SetString("state", ConnectionStateToString(state));
396 data->SetString("error", ConnectionErrorToString(error));
397 PostChromotingMessage("onConnectionStatus", data.Pass());
400 void ChromotingInstance::OnConnectionReady(bool ready) {
401 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
402 data->SetBoolean("ready", ready);
403 PostChromotingMessage("onConnectionReady", data.Pass());
406 protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
407 // TODO(sergeyu): Move clipboard handling to a separate class.
408 // crbug.com/138108
409 return this;
412 protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
413 // TODO(sergeyu): Move cursor shape code to a separate class.
414 // crbug.com/138108
415 return this;
418 void ChromotingInstance::InjectClipboardEvent(
419 const protocol::ClipboardEvent& event) {
420 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
421 data->SetString("mimeType", event.mime_type());
422 data->SetString("item", event.data());
423 PostChromotingMessage("injectClipboardItem", data.Pass());
426 void ChromotingInstance::SetCursorShape(
427 const protocol::CursorShapeInfo& cursor_shape) {
428 if (!cursor_shape.has_data() ||
429 !cursor_shape.has_width() ||
430 !cursor_shape.has_height() ||
431 !cursor_shape.has_hotspot_x() ||
432 !cursor_shape.has_hotspot_y()) {
433 return;
436 int width = cursor_shape.width();
437 int height = cursor_shape.height();
439 if (width < 0 || height < 0) {
440 return;
443 if (width > 32 || height > 32) {
444 VLOG(2) << "Cursor too large for SetCursor: "
445 << width << "x" << height << " > 32x32";
446 return;
449 if (pp::ImageData::GetNativeImageDataFormat() !=
450 PP_IMAGEDATAFORMAT_BGRA_PREMUL) {
451 VLOG(2) << "Unable to set cursor shape - non-native image format";
452 return;
455 int hotspot_x = cursor_shape.hotspot_x();
456 int hotspot_y = cursor_shape.hotspot_y();
458 pp::ImageData cursor_image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
459 pp::Size(width, height), false);
461 int bytes_per_row = width * kBytesPerPixel;
462 const uint8* src_row_data = reinterpret_cast<const uint8*>(
463 cursor_shape.data().data());
464 uint8* dst_row_data = reinterpret_cast<uint8*>(cursor_image.data());
465 for (int row = 0; row < height; row++) {
466 memcpy(dst_row_data, src_row_data, bytes_per_row);
467 src_row_data += bytes_per_row;
468 dst_row_data += cursor_image.stride();
471 pp::MouseCursor::SetCursor(this, PP_MOUSECURSOR_TYPE_CUSTOM,
472 cursor_image,
473 pp::Point(hotspot_x, hotspot_y));
476 void ChromotingInstance::OnFirstFrameReceived() {
477 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
478 PostChromotingMessage("onFirstFrameReceived", data.Pass());
481 void ChromotingInstance::Connect(const ClientConfig& config) {
482 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
484 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
486 host_connection_.reset(new protocol::ConnectionToHost(true));
487 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
488 client_.reset(new ChromotingClient(config, &context_,
489 host_connection_.get(), this,
490 rectangle_decoder_.get(),
491 audio_player.Pass()));
493 // Connect the input pipeline to the protocol stub & initialize components.
494 mouse_input_filter_.set_input_stub(host_connection_->input_stub());
495 mouse_input_filter_.set_input_size(view_->get_view_size_dips());
497 LOG(INFO) << "Connecting to " << config.host_jid
498 << ". Local jid: " << config.local_jid << ".";
500 // Setup the XMPP Proxy.
501 xmpp_proxy_ = new PepperXmppProxy(
502 base::Bind(&ChromotingInstance::SendOutgoingIq, AsWeakPtr()),
503 plugin_task_runner_, context_.main_task_runner());
505 scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator(
506 PepperPortAllocator::Create(this));
507 scoped_ptr<protocol::TransportFactory> transport_factory(
508 new protocol::LibjingleTransportFactory(port_allocator.Pass(), false));
510 // Kick off the connection.
511 client_->Start(xmpp_proxy_, transport_factory.Pass());
513 // Start timer that periodically sends perf stats.
514 plugin_task_runner_->PostDelayedTask(
515 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()),
516 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
519 void ChromotingInstance::Disconnect() {
520 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
522 // PepperView must be destroyed before the client.
523 view_.reset();
525 LOG(INFO) << "Disconnecting from host.";
526 if (client_.get()) {
527 // TODO(sergeyu): Should we disconnect asynchronously?
528 base::WaitableEvent done_event(true, false);
529 client_->Stop(base::Bind(&base::WaitableEvent::Signal,
530 base::Unretained(&done_event)));
531 done_event.Wait();
532 client_.reset();
535 // Disconnect the input pipeline and teardown the connection.
536 mouse_input_filter_.set_input_stub(NULL);
537 host_connection_.reset();
540 void ChromotingInstance::OnIncomingIq(const std::string& iq) {
541 xmpp_proxy_->OnIq(iq);
544 void ChromotingInstance::ReleaseAllKeys() {
545 if (IsConnected())
546 input_tracker_.ReleaseAll();
549 void ChromotingInstance::InjectKeyEvent(const protocol::KeyEvent& event) {
550 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
551 if (IsConnected())
552 input_tracker_.InjectKeyEvent(event);
555 void ChromotingInstance::RemapKey(uint32 in_usb_keycode,
556 uint32 out_usb_keycode) {
557 key_mapper_.RemapKey(in_usb_keycode, out_usb_keycode);
560 void ChromotingInstance::TrapKey(uint32 usb_keycode, bool trap) {
561 key_mapper_.TrapKey(usb_keycode, trap);
564 void ChromotingInstance::SendClipboardItem(const std::string& mime_type,
565 const std::string& item) {
566 if (!IsConnected()) {
567 return;
569 protocol::ClipboardEvent event;
570 event.set_mime_type(mime_type);
571 event.set_data(item);
572 host_connection_->clipboard_stub()->InjectClipboardEvent(event);
575 void ChromotingInstance::NotifyClientDimensions(int width, int height) {
576 if (!IsConnected()) {
577 return;
579 protocol::ClientDimensions client_dimensions;
580 client_dimensions.set_width(width);
581 client_dimensions.set_height(height);
582 host_connection_->host_stub()->NotifyClientDimensions(client_dimensions);
585 void ChromotingInstance::PauseVideo(bool pause) {
586 if (!IsConnected()) {
587 return;
589 protocol::VideoControl video_control;
590 video_control.set_enable(!pause);
591 host_connection_->host_stub()->ControlVideo(video_control);
594 void ChromotingInstance::PauseAudio(bool pause) {
595 if (!IsConnected()) {
596 return;
598 protocol::AudioControl audio_control;
599 audio_control.set_enable(!pause);
600 host_connection_->host_stub()->ControlAudio(audio_control);
603 ChromotingStats* ChromotingInstance::GetStats() {
604 if (!client_.get())
605 return NULL;
606 return client_->GetStats();
609 void ChromotingInstance::PostChromotingMessage(
610 const std::string& method,
611 scoped_ptr<base::DictionaryValue> data) {
612 scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
613 message->SetString("method", method);
614 message->Set("data", data.release());
616 std::string message_json;
617 base::JSONWriter::Write(message.get(), &message_json);
618 PostMessage(pp::Var(message_json));
621 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
622 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
623 data->SetInteger("usbKeycode", usb_keycode);
624 data->SetBoolean("pressed", pressed);
625 PostChromotingMessage("trappedKeyEvent", data.Pass());
628 void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
629 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
630 data->SetString("iq", iq);
631 PostChromotingMessage("sendOutgoingIq", data.Pass());
634 void ChromotingInstance::SendPerfStats() {
635 if (!client_.get()) {
636 return;
639 plugin_task_runner_->PostDelayedTask(
640 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()),
641 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
643 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
644 ChromotingStats* stats = client_->GetStats();
645 data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate());
646 data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate());
647 data->SetDouble("captureLatency", stats->video_capture_ms()->Average());
648 data->SetDouble("encodeLatency", stats->video_encode_ms()->Average());
649 data->SetDouble("decodeLatency", stats->video_decode_ms()->Average());
650 data->SetDouble("renderLatency", stats->video_paint_ms()->Average());
651 data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average());
652 PostChromotingMessage("onPerfStats", data.Pass());
655 // static
656 void ChromotingInstance::RegisterLogMessageHandler() {
657 base::AutoLock lock(g_logging_lock.Get());
659 VLOG(1) << "Registering global log handler";
661 // Record previous handler so we can call it in a chain.
662 g_logging_old_handler = logging::GetLogMessageHandler();
664 // Set up log message handler.
665 // This is not thread-safe so we need it within our lock.
666 logging::SetLogMessageHandler(&LogToUI);
669 void ChromotingInstance::RegisterLoggingInstance() {
670 base::AutoLock lock(g_logging_lock.Get());
672 // Register this instance as the one that will handle all logging calls
673 // and display them to the user.
674 // If multiple plugins are run, then the last one registered will handle all
675 // logging for all instances.
676 g_logging_instance.Get() = weak_factory_.GetWeakPtr();
677 g_logging_task_runner.Get() = plugin_task_runner_;
678 g_has_logging_instance = true;
681 void ChromotingInstance::UnregisterLoggingInstance() {
682 base::AutoLock lock(g_logging_lock.Get());
684 // Don't unregister unless we're the currently registered instance.
685 if (this != g_logging_instance.Get().get())
686 return;
688 // Unregister this instance for logging.
689 g_has_logging_instance = false;
690 g_logging_instance.Get().reset();
691 g_logging_task_runner.Get() = NULL;
693 VLOG(1) << "Unregistering global log handler";
696 // static
697 bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
698 size_t message_start,
699 const std::string& str) {
700 // Note that we're reading |g_has_logging_instance| outside of a lock.
701 // This lockless read is done so that we don't needlessly slow down global
702 // logging with a lock for each log message.
704 // This lockless read is safe because:
706 // Misreading a false value (when it should be true) means that we'll simply
707 // skip processing a few log messages.
709 // Misreading a true value (when it should be false) means that we'll take
710 // the lock and check |g_logging_instance| unnecessarily. This is not
711 // problematic because we always set |g_logging_instance| inside a lock.
712 if (g_has_logging_instance) {
713 scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner;
714 base::WeakPtr<ChromotingInstance> logging_instance;
717 base::AutoLock lock(g_logging_lock.Get());
718 // If we're on the logging thread and |g_logging_to_plugin| is set then
719 // this LOG message came from handling a previous LOG message and we
720 // should skip it to avoid an infinite loop of LOG messages.
721 if (!g_logging_task_runner.Get()->BelongsToCurrentThread() ||
722 !g_logging_to_plugin) {
723 logging_task_runner = g_logging_task_runner.Get();
724 logging_instance = g_logging_instance.Get();
728 if (logging_task_runner.get()) {
729 std::string message = remoting::GetTimestampString();
730 message += (str.c_str() + message_start);
732 logging_task_runner->PostTask(
733 FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
734 logging_instance, message));
738 if (g_logging_old_handler)
739 return (g_logging_old_handler)(severity, file, line, message_start, str);
740 return false;
743 void ChromotingInstance::ProcessLogToUI(const std::string& message) {
744 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
746 // This flag (which is set only here) is used to prevent LogToUI from posting
747 // new tasks while we're in the middle of servicing a LOG call. This can
748 // happen if the call to LogDebugInfo tries to LOG anything.
749 // Since it is read on the plugin thread, we don't need to lock to set it.
750 g_logging_to_plugin = true;
751 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
752 data->SetString("message", message);
753 PostChromotingMessage("logDebugMessage", data.Pass());
754 g_logging_to_plugin = false;
757 bool ChromotingInstance::IsConnected() {
758 return host_connection_.get() &&
759 (host_connection_->state() == protocol::ConnectionToHost::CONNECTED);
762 } // namespace remoting