chromoting_instance.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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.
4
5#include "remoting/client/plugin/chromoting_instance.h"
6
7#include <string>
8#include <vector>
9
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"
43
44// Windows defines 'PostMessage', so we have to undef it.
45#if defined(PostMessage)
46#undef PostMessage
47#endif
48
49namespace remoting {
50
51namespace {
52
53// 32-bit BGRA is 4 bytes per pixel.
54const int kBytesPerPixel = 4;
55
56const int kPerfStatsIntervalMs = 1000;
57
58std::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";
72  }
73  NOTREACHED();
74  return "";
75}
76
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.
81std::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";
87
88    case protocol::PEER_IS_OFFLINE:
89      return "HOST_IS_OFFLINE";
90
91    case protocol::SESSION_REJECTED:
92    case protocol::AUTHENTICATION_FAILED:
93      return "SESSION_REJECTED";
94
95    case protocol::INCOMPATIBLE_PROTOCOL:
96      return "INCOMPATIBLE_PROTOCOL";
97
98    case protocol::HOST_OVERLOAD:
99      return "HOST_OVERLOAD";
100
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";
106  }
107  DLOG(FATAL) << "Unknown error code" << error;
108  return  "";
109}
110
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.
114bool g_logging_to_plugin = false;
115bool g_has_logging_instance = false;
116base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
117    g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
118base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
119    g_logging_instance = LAZY_INSTANCE_INITIALIZER;
120base::LazyInstance<base::Lock>::Leaky
121    g_logging_lock = LAZY_INSTANCE_INITIALIZER;
122logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
123
124}  // namespace
125
126// String sent in the "hello" message to the plugin to describe features.
127const char ChromotingInstance::kApiFeatures[] =
128    "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
129    "notifyClientDimensions pauseVideo pauseAudio";
130
131bool 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);
141  }
142  if (config->authentication_methods.empty()) {
143    LOG(ERROR) << "No valid authentication methods specified.";
144    return false;
145  }
146
147  return true;
148}
149
150ChromotingInstance::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);
169
170  // Resister this instance to handle debug log messsages.
171  RegisterLoggingInstance();
172
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());
179}
180
181ChromotingInstance::~ChromotingInstance() {
182  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
183
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();
187
188  // PepperView must be destroyed before the client.
189  view_.reset();
190
191  if (client_.get()) {
192    client_->Stop(base::Bind(&PluginThreadTaskRunner::Quit,
193                  plugin_task_runner_));
194  } else {
195    plugin_task_runner_->Quit();
196  }
197
198  // Ensure that nothing touches the plugin thread delegate after this point.
199  plugin_task_runner_->DetachAndRunShutdownLoop();
200
201  // Stopping the context shuts down all chromoting threads.
202  context_.Stop();
203}
204
205bool ChromotingInstance::Init(uint32_t argc,
206                              const char* argn[],
207                              const char* argv[]) {
208  CHECK(!initialized_);
209  initialized_ = true;
210
211  VLOG(1) << "Started ChromotingInstance::Init";
212
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;
218  }
219
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();
226
227  // Start all the threads.
228  context_.Start();
229
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());
240
241  return true;
242}
243
244void ChromotingInstance::HandleMessage(const pp::Var& message) {
245  if (!message.is_string()) {
246    LOG(ERROR) << "Received a message that is not a string.";
247    return;
248  }
249
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;
262  }
263
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;
276    }
277
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;
286    }
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;
297    }
298
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;
310    }
311
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;
320    }
321
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;
330    }
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;
339    }
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;
346    }
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;
353    }
354    PauseAudio(pause);
355  }
356}
357
358void ChromotingInstance::DidChangeView(const pp::View& view) {
359  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
360
361  view_->SetView(view);
362
363  mouse_input_filter_.set_input_size(view_->get_view_size_dips());
364}
365
366bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
367  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
368
369  if (!IsConnected())
370    return false;
371
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());
375
376  return input_handler_.HandleInputEvent(event);
377}
378
379void 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());
389}
390
391void 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());
398}
399
400void ChromotingInstance::OnConnectionReady(bool ready) {
401  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
402  data->SetBoolean("ready", ready);
403  PostChromotingMessage("onConnectionReady", data.Pass());
404}
405
406protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
407  // TODO(sergeyu): Move clipboard handling to a separate class.
408  // crbug.com/138108
409  return this;
410}
411
412protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
413  // TODO(sergeyu): Move cursor shape code to a separate class.
414  // crbug.com/138108
415  return this;
416}
417
418void 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());
424}
425
426void 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;
434  }
435
436  if (pp::ImageData::GetNativeImageDataFormat() !=
437      PP_IMAGEDATAFORMAT_BGRA_PREMUL) {
438    VLOG(2) << "Unable to set cursor shape - non-native image format";
439    return;
440  }
441
442  int width = cursor_shape.width();
443  int height = cursor_shape.height();
444
445  if (width > 32 || height > 32) {
446    VLOG(2) << "Cursor too large for SetCursor: "
447            << width << "x" << height << " > 32x32";
448    return;
449  }
450
451  int hotspot_x = cursor_shape.hotspot_x();
452  int hotspot_y = cursor_shape.hotspot_y();
453
454  pp::ImageData cursor_image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
455                             pp::Size(width, height), false);
456
457  int bytes_per_row = width * kBytesPerPixel;
458  const uint8* src_row_data = reinterpret_cast<const uint8*>(
459      cursor_shape.data().data());
460  uint8* dst_row_data = reinterpret_cast<uint8*>(cursor_image.data());
461  for (int row = 0; row < height; row++) {
462    memcpy(dst_row_data, src_row_data, bytes_per_row);
463    src_row_data += bytes_per_row;
464    dst_row_data += cursor_image.stride();
465  }
466
467  pp::MouseCursor::SetCursor(this, PP_MOUSECURSOR_TYPE_CUSTOM,
468                             cursor_image,
469                             pp::Point(hotspot_x, hotspot_y));
470}
471
472void ChromotingInstance::OnFirstFrameReceived() {
473  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
474  PostChromotingMessage("onFirstFrameReceived", data.Pass());
475}
476
477void ChromotingInstance::Connect(const ClientConfig& config) {
478  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
479
480  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
481
482  host_connection_.reset(new protocol::ConnectionToHost(true));
483  scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
484  client_.reset(new ChromotingClient(config, &context_,
485                                     host_connection_.get(), this,
486                                     rectangle_decoder_.get(),
487                                     audio_player.Pass()));
488
489  // Connect the input pipeline to the protocol stub & initialize components.
490  mouse_input_filter_.set_input_stub(host_connection_->input_stub());
491  mouse_input_filter_.set_input_size(view_->get_view_size_dips());
492
493  LOG(INFO) << "Connecting to " << config.host_jid
494            << ". Local jid: " << config.local_jid << ".";
495
496  // Setup the XMPP Proxy.
497  xmpp_proxy_ = new PepperXmppProxy(
498      base::Bind(&ChromotingInstance::SendOutgoingIq, AsWeakPtr()),
499      plugin_task_runner_, context_.main_task_runner());
500
501  scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator(
502      PepperPortAllocator::Create(this));
503  scoped_ptr<protocol::TransportFactory> transport_factory(
504      new protocol::LibjingleTransportFactory(port_allocator.Pass(), false));
505
506  // Kick off the connection.
507  client_->Start(xmpp_proxy_, transport_factory.Pass());
508
509  // Start timer that periodically sends perf stats.
510  plugin_task_runner_->PostDelayedTask(
511      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()),
512      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
513}
514
515void ChromotingInstance::Disconnect() {
516  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
517
518  // PepperView must be destroyed before the client.
519  view_.reset();
520
521  LOG(INFO) << "Disconnecting from host.";
522  if (client_.get()) {
523    // TODO(sergeyu): Should we disconnect asynchronously?
524    base::WaitableEvent done_event(true, false);
525    client_->Stop(base::Bind(&base::WaitableEvent::Signal,
526                             base::Unretained(&done_event)));
527    done_event.Wait();
528    client_.reset();
529  }
530
531  // Disconnect the input pipeline and teardown the connection.
532  mouse_input_filter_.set_input_stub(NULL);
533  host_connection_.reset();
534}
535
536void ChromotingInstance::OnIncomingIq(const std::string& iq) {
537  xmpp_proxy_->OnIq(iq);
538}
539
540void ChromotingInstance::ReleaseAllKeys() {
541  if (IsConnected())
542    input_tracker_.ReleaseAll();
543}
544
545void ChromotingInstance::InjectKeyEvent(const protocol::KeyEvent& event) {
546  // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
547  if (IsConnected())
548    input_tracker_.InjectKeyEvent(event);
549}
550
551void ChromotingInstance::RemapKey(uint32 in_usb_keycode,
552                                  uint32 out_usb_keycode) {
553  key_mapper_.RemapKey(in_usb_keycode, out_usb_keycode);
554}
555
556void ChromotingInstance::TrapKey(uint32 usb_keycode, bool trap) {
557  key_mapper_.TrapKey(usb_keycode, trap);
558}
559
560void ChromotingInstance::SendClipboardItem(const std::string& mime_type,
561                                           const std::string& item) {
562  if (!IsConnected()) {
563    return;
564  }
565  protocol::ClipboardEvent event;
566  event.set_mime_type(mime_type);
567  event.set_data(item);
568  host_connection_->clipboard_stub()->InjectClipboardEvent(event);
569}
570
571void ChromotingInstance::NotifyClientDimensions(int width, int height) {
572  if (!IsConnected()) {
573    return;
574  }
575  protocol::ClientDimensions client_dimensions;
576  client_dimensions.set_width(width);
577  client_dimensions.set_height(height);
578  host_connection_->host_stub()->NotifyClientDimensions(client_dimensions);
579}
580
581void ChromotingInstance::PauseVideo(bool pause) {
582  if (!IsConnected()) {
583    return;
584  }
585  protocol::VideoControl video_control;
586  video_control.set_enable(!pause);
587  host_connection_->host_stub()->ControlVideo(video_control);
588}
589
590void ChromotingInstance::PauseAudio(bool pause) {
591  if (!IsConnected()) {
592    return;
593  }
594  protocol::AudioControl audio_control;
595  audio_control.set_enable(!pause);
596  host_connection_->host_stub()->ControlAudio(audio_control);
597}
598
599ChromotingStats* ChromotingInstance::GetStats() {
600  if (!client_.get())
601    return NULL;
602  return client_->GetStats();
603}
604
605void ChromotingInstance::PostChromotingMessage(
606    const std::string& method,
607    scoped_ptr<base::DictionaryValue> data) {
608  scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
609  message->SetString("method", method);
610  message->Set("data", data.release());
611
612  std::string message_json;
613  base::JSONWriter::Write(message.get(), &message_json);
614  PostMessage(pp::Var(message_json));
615}
616
617void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
618  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
619  data->SetInteger("usbKeycode", usb_keycode);
620  data->SetBoolean("pressed", pressed);
621  PostChromotingMessage("trappedKeyEvent", data.Pass());
622}
623
624void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
625  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
626  data->SetString("iq", iq);
627  PostChromotingMessage("sendOutgoingIq", data.Pass());
628}
629
630void ChromotingInstance::SendPerfStats() {
631  if (!client_.get()) {
632    return;
633  }
634
635  plugin_task_runner_->PostDelayedTask(
636      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()),
637      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
638
639  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
640  ChromotingStats* stats = client_->GetStats();
641  data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate());
642  data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate());
643  data->SetDouble("captureLatency", stats->video_capture_ms()->Average());
644  data->SetDouble("encodeLatency", stats->video_encode_ms()->Average());
645  data->SetDouble("decodeLatency", stats->video_decode_ms()->Average());
646  data->SetDouble("renderLatency", stats->video_paint_ms()->Average());
647  data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average());
648  PostChromotingMessage("onPerfStats", data.Pass());
649}
650
651// static
652void ChromotingInstance::RegisterLogMessageHandler() {
653  base::AutoLock lock(g_logging_lock.Get());
654
655  VLOG(1) << "Registering global log handler";
656
657  // Record previous handler so we can call it in a chain.
658  g_logging_old_handler = logging::GetLogMessageHandler();
659
660  // Set up log message handler.
661  // This is not thread-safe so we need it within our lock.
662  logging::SetLogMessageHandler(&LogToUI);
663}
664
665void ChromotingInstance::RegisterLoggingInstance() {
666  base::AutoLock lock(g_logging_lock.Get());
667
668  // Register this instance as the one that will handle all logging calls
669  // and display them to the user.
670  // If multiple plugins are run, then the last one registered will handle all
671  // logging for all instances.
672  g_logging_instance.Get() = weak_factory_.GetWeakPtr();
673  g_logging_task_runner.Get() = plugin_task_runner_;
674  g_has_logging_instance = true;
675}
676
677void ChromotingInstance::UnregisterLoggingInstance() {
678  base::AutoLock lock(g_logging_lock.Get());
679
680  // Don't unregister unless we're the currently registered instance.
681  if (this != g_logging_instance.Get().get())
682    return;
683
684  // Unregister this instance for logging.
685  g_has_logging_instance = false;
686  g_logging_instance.Get().reset();
687  g_logging_task_runner.Get() = NULL;
688
689  VLOG(1) << "Unregistering global log handler";
690}
691
692// static
693bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
694                                 size_t message_start,
695                                 const std::string& str) {
696  // Note that we're reading |g_has_logging_instance| outside of a lock.
697  // This lockless read is done so that we don't needlessly slow down global
698  // logging with a lock for each log message.
699  //
700  // This lockless read is safe because:
701  //
702  // Misreading a false value (when it should be true) means that we'll simply
703  // skip processing a few log messages.
704  //
705  // Misreading a true value (when it should be false) means that we'll take
706  // the lock and check |g_logging_instance| unnecessarily. This is not
707  // problematic because we always set |g_logging_instance| inside a lock.
708  if (g_has_logging_instance) {
709    scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner;
710    base::WeakPtr<ChromotingInstance> logging_instance;
711
712    {
713      base::AutoLock lock(g_logging_lock.Get());
714      // If we're on the logging thread and |g_logging_to_plugin| is set then
715      // this LOG message came from handling a previous LOG message and we
716      // should skip it to avoid an infinite loop of LOG messages.
717      if (!g_logging_task_runner.Get()->BelongsToCurrentThread() ||
718          !g_logging_to_plugin) {
719        logging_task_runner = g_logging_task_runner.Get();
720        logging_instance = g_logging_instance.Get();
721      }
722    }
723
724    if (logging_task_runner.get()) {
725      std::string message = remoting::GetTimestampString();
726      message += (str.c_str() + message_start);
727
728      logging_task_runner->PostTask(
729          FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
730                                logging_instance, message));
731    }
732  }
733
734  if (g_logging_old_handler)
735    return (g_logging_old_handler)(severity, file, line, message_start, str);
736  return false;
737}
738
739void ChromotingInstance::ProcessLogToUI(const std::string& message) {
740  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
741
742  // This flag (which is set only here) is used to prevent LogToUI from posting
743  // new tasks while we're in the middle of servicing a LOG call. This can
744  // happen if the call to LogDebugInfo tries to LOG anything.
745  // Since it is read on the plugin thread, we don't need to lock to set it.
746  g_logging_to_plugin = true;
747  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
748  data->SetString("message", message);
749  PostChromotingMessage("logDebugMessage", data.Pass());
750  g_logging_to_plugin = false;
751}
752
753bool ChromotingInstance::IsConnected() {
754  return host_connection_.get() &&
755    (host_connection_->state() == protocol::ConnectionToHost::CONNECTED);
756}
757
758}  // namespace remoting
759