chromoting_instance.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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 <algorithm>
8#include <string>
9#include <vector>
10
11#if defined(OS_NACL)
12#include <nacl_io/nacl_io.h>
13#include <sys/mount.h>
14#endif
15
16#include "base/bind.h"
17#include "base/callback.h"
18#include "base/json/json_reader.h"
19#include "base/json/json_writer.h"
20#include "base/lazy_instance.h"
21#include "base/logging.h"
22#include "base/strings/string_split.h"
23#include "base/strings/stringprintf.h"
24#include "base/synchronization/lock.h"
25#include "base/threading/thread.h"
26#include "base/values.h"
27#include "crypto/random.h"
28#include "jingle/glue/thread_wrapper.h"
29#include "media/base/yuv_convert.h"
30#include "net/socket/ssl_server_socket.h"
31#include "ppapi/cpp/completion_callback.h"
32#include "ppapi/cpp/dev/url_util_dev.h"
33#include "ppapi/cpp/image_data.h"
34#include "ppapi/cpp/input_event.h"
35#include "ppapi/cpp/rect.h"
36#include "ppapi/cpp/var_array_buffer.h"
37#include "ppapi/cpp/var_dictionary.h"
38#include "remoting/base/constants.h"
39#include "remoting/base/util.h"
40#include "remoting/client/chromoting_client.h"
41#include "remoting/client/frame_consumer_proxy.h"
42#include "remoting/client/plugin/delegating_signal_strategy.h"
43#include "remoting/client/plugin/media_source_video_renderer.h"
44#include "remoting/client/plugin/normalizing_input_filter_cros.h"
45#include "remoting/client/plugin/normalizing_input_filter_mac.h"
46#include "remoting/client/plugin/pepper_audio_player.h"
47#include "remoting/client/plugin/pepper_input_handler.h"
48#include "remoting/client/plugin/pepper_port_allocator.h"
49#include "remoting/client/plugin/pepper_view.h"
50#include "remoting/client/software_video_renderer.h"
51#include "remoting/client/token_fetcher_proxy.h"
52#include "remoting/protocol/connection_to_host.h"
53#include "remoting/protocol/host_stub.h"
54#include "remoting/protocol/libjingle_transport_factory.h"
55#include "third_party/webrtc/base/helpers.h"
56#include "third_party/webrtc/base/ssladapter.h"
57#include "url/gurl.h"
58
59// Windows defines 'PostMessage', so we have to undef it.
60#if defined(PostMessage)
61#undef PostMessage
62#endif
63
64namespace remoting {
65
66namespace {
67
68// 32-bit BGRA is 4 bytes per pixel.
69const int kBytesPerPixel = 4;
70
71#if defined(ARCH_CPU_LITTLE_ENDIAN)
72const uint32_t kPixelAlphaMask = 0xff000000;
73#else  // !defined(ARCH_CPU_LITTLE_ENDIAN)
74const uint32_t kPixelAlphaMask = 0x000000ff;
75#endif  // !defined(ARCH_CPU_LITTLE_ENDIAN)
76
77// Default DPI to assume for old clients that use notifyClientResolution.
78const int kDefaultDPI = 96;
79
80// Interval at which to sample performance statistics.
81const int kPerfStatsIntervalMs = 1000;
82
83// URL scheme used by Chrome apps and extensions.
84const char kChromeExtensionUrlScheme[] = "chrome-extension";
85
86// Maximum width and height of a mouse cursor supported by PPAPI.
87const int kMaxCursorWidth = 32;
88const int kMaxCursorHeight = 32;
89
90#if defined(USE_OPENSSL)
91// Size of the random seed blob used to initialize RNG in libjingle. Libjingle
92// uses the seed only for OpenSSL builds. OpenSSL needs at least 32 bytes of
93// entropy (see http://wiki.openssl.org/index.php/Random_Numbers), but stores
94// 1039 bytes of state, so we initialize it with 1k or random data.
95const int kRandomSeedSize = 1024;
96#endif  // defined(USE_OPENSSL)
97
98std::string ConnectionStateToString(protocol::ConnectionToHost::State state) {
99  // Values returned by this function must match the
100  // remoting.ClientSession.State enum in JS code.
101  switch (state) {
102    case protocol::ConnectionToHost::INITIALIZING:
103      return "INITIALIZING";
104    case protocol::ConnectionToHost::CONNECTING:
105      return "CONNECTING";
106    case protocol::ConnectionToHost::AUTHENTICATED:
107      // Report the authenticated state as 'CONNECTING' to avoid changing
108      // the interface between the plugin and webapp.
109      return "CONNECTING";
110    case protocol::ConnectionToHost::CONNECTED:
111      return "CONNECTED";
112    case protocol::ConnectionToHost::CLOSED:
113      return "CLOSED";
114    case protocol::ConnectionToHost::FAILED:
115      return "FAILED";
116  }
117  NOTREACHED();
118  return std::string();
119}
120
121// TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
122// and let it handle it, but it would be hard to fix it now because
123// client plugin and webapp versions may not be in sync. It should be
124// easy to do after we are finished moving the client plugin to NaCl.
125std::string ConnectionErrorToString(protocol::ErrorCode error) {
126  // Values returned by this function must match the
127  // remoting.ClientSession.Error enum in JS code.
128  switch (error) {
129    case protocol::OK:
130      return "NONE";
131
132    case protocol::PEER_IS_OFFLINE:
133      return "HOST_IS_OFFLINE";
134
135    case protocol::SESSION_REJECTED:
136    case protocol::AUTHENTICATION_FAILED:
137      return "SESSION_REJECTED";
138
139    case protocol::INCOMPATIBLE_PROTOCOL:
140      return "INCOMPATIBLE_PROTOCOL";
141
142    case protocol::HOST_OVERLOAD:
143      return "HOST_OVERLOAD";
144
145    case protocol::CHANNEL_CONNECTION_ERROR:
146    case protocol::SIGNALING_ERROR:
147    case protocol::SIGNALING_TIMEOUT:
148    case protocol::UNKNOWN_ERROR:
149      return "NETWORK_FAILURE";
150  }
151  DLOG(FATAL) << "Unknown error code" << error;
152  return std::string();
153}
154
155// Returns true if |pixel| is not completely transparent.
156bool IsVisiblePixel(uint32_t pixel) {
157  return (pixel & kPixelAlphaMask) != 0;
158}
159
160// Returns true if there is at least one visible pixel in the given range.
161bool IsVisibleRow(const uint32_t* begin, const uint32_t* end) {
162  return std::find_if(begin, end, &IsVisiblePixel) != end;
163}
164
165bool ParseAuthMethods(
166    const std::string& auth_methods_str,
167    std::vector<protocol::AuthenticationMethod>* auth_methods) {
168  std::vector<std::string> parts;
169  base::SplitString(auth_methods_str, ',', &parts);
170  for (std::vector<std::string>::iterator it = parts.begin();
171       it != parts.end(); ++it) {
172    protocol::AuthenticationMethod authentication_method =
173        protocol::AuthenticationMethod::FromString(*it);
174    if (authentication_method.is_valid())
175      auth_methods->push_back(authentication_method);
176  }
177  if (auth_methods->empty()) {
178    LOG(ERROR) << "No valid authentication methods specified.";
179    return false;
180  }
181
182  return true;
183}
184
185// This flag blocks LOGs to the UI if we're already in the middle of logging
186// to the UI. This prevents a potential infinite loop if we encounter an error
187// while sending the log message to the UI.
188bool g_logging_to_plugin = false;
189bool g_has_logging_instance = false;
190base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
191    g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
192base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
193    g_logging_instance = LAZY_INSTANCE_INITIALIZER;
194base::LazyInstance<base::Lock>::Leaky
195    g_logging_lock = LAZY_INSTANCE_INITIALIZER;
196logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
197
198}  // namespace
199
200// String sent in the "hello" message to the webapp to describe features.
201const char ChromotingInstance::kApiFeatures[] =
202    "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
203    "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
204    "pinlessAuth extensionMessage allowMouseLock mediaSourceRendering "
205    "videoControl";
206
207const char ChromotingInstance::kRequestedCapabilities[] = "";
208const char ChromotingInstance::kSupportedCapabilities[] = "desktopShape";
209
210ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
211    : pp::Instance(pp_instance),
212      initialized_(false),
213      plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)),
214      context_(plugin_task_runner_.get()),
215      input_tracker_(&mouse_input_filter_),
216      key_mapper_(&input_tracker_),
217      input_handler_(this),
218      use_async_pin_dialog_(false),
219      use_media_source_rendering_(false),
220      delegate_large_cursors_(false),
221      weak_factory_(this) {
222#if defined(OS_NACL)
223  // In NaCl global resources need to be initialized differently because they
224  // are not shared with Chrome.
225  thread_task_runner_handle_.reset(
226      new base::ThreadTaskRunnerHandle(plugin_task_runner_));
227  thread_wrapper_.reset(
228      new jingle_glue::JingleThreadWrapper(plugin_task_runner_));
229  media::InitializeCPUSpecificYUVConversions();
230#else
231  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
232#endif
233
234#if defined(OS_NACL)
235  nacl_io_init_ppapi(pp_instance, pp::Module::Get()->get_browser_interface());
236  mount("", "/etc", "memfs", 0, "");
237  mount("", "/usr", "memfs", 0, "");
238#endif
239
240  RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
241  RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
242
243  // Resister this instance to handle debug log messsages.
244  RegisterLoggingInstance();
245
246#if defined(USE_OPENSSL)
247  // Initialize random seed for libjingle. It's necessary only with OpenSSL.
248  char random_seed[kRandomSeedSize];
249  crypto::RandBytes(random_seed, sizeof(random_seed));
250  rtc::InitRandom(random_seed, sizeof(random_seed));
251#else
252  // Libjingle's SSL implementation is not really used, but it has to be
253  // initialized for NSS builds to make sure that RNG is initialized in NSS,
254  // because libjingle uses it.
255  rtc::InitializeSSL();
256#endif  // !defined(USE_OPENSSL)
257
258  // Send hello message.
259  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
260  data->SetInteger("apiVersion", kApiVersion);
261  data->SetString("apiFeatures", kApiFeatures);
262  data->SetInteger("apiMinVersion", kApiMinMessagingVersion);
263  data->SetString("requestedCapabilities", kRequestedCapabilities);
264  data->SetString("supportedCapabilities", kSupportedCapabilities);
265
266  PostLegacyJsonMessage("hello", data.Pass());
267}
268
269ChromotingInstance::~ChromotingInstance() {
270  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
271
272  // Unregister this instance so that debug log messages will no longer be sent
273  // to it. This will stop all logging in all Chromoting instances.
274  UnregisterLoggingInstance();
275
276  // PepperView must be destroyed before the client.
277  view_weak_factory_.reset();
278  view_.reset();
279
280  client_.reset();
281
282  plugin_task_runner_->Quit();
283
284  // Ensure that nothing touches the plugin thread delegate after this point.
285  plugin_task_runner_->DetachAndRunShutdownLoop();
286
287  // Stopping the context shuts down all chromoting threads.
288  context_.Stop();
289}
290
291bool ChromotingInstance::Init(uint32_t argc,
292                              const char* argn[],
293                              const char* argv[]) {
294  CHECK(!initialized_);
295  initialized_ = true;
296
297  VLOG(1) << "Started ChromotingInstance::Init";
298
299  // Check that the calling content is part of an app or extension. This is only
300  // necessary for non-PNaCl version of the plugin. Also PPB_URLUtil_Dev doesn't
301  // work in NaCl at the moment so the check fails in NaCl builds.
302#if !defined(OS_NACL)
303  if (!IsCallerAppOrExtension()) {
304    LOG(ERROR) << "Not an app or extension";
305    return false;
306  }
307#endif
308
309  // Start all the threads.
310  context_.Start();
311
312  return true;
313}
314
315void ChromotingInstance::HandleMessage(const pp::Var& message) {
316  if (!message.is_string()) {
317    LOG(ERROR) << "Received a message that is not a string.";
318    return;
319  }
320
321  scoped_ptr<base::Value> json(
322      base::JSONReader::Read(message.AsString(),
323                             base::JSON_ALLOW_TRAILING_COMMAS));
324  base::DictionaryValue* message_dict = NULL;
325  std::string method;
326  base::DictionaryValue* data = NULL;
327  if (!json.get() ||
328      !json->GetAsDictionary(&message_dict) ||
329      !message_dict->GetString("method", &method) ||
330      !message_dict->GetDictionary("data", &data)) {
331    LOG(ERROR) << "Received invalid message:" << message.AsString();
332    return;
333  }
334
335  if (method == "connect") {
336    HandleConnect(*data);
337  } else if (method == "disconnect") {
338    HandleDisconnect(*data);
339  } else if (method == "incomingIq") {
340    HandleOnIncomingIq(*data);
341  } else if (method == "releaseAllKeys") {
342    HandleReleaseAllKeys(*data);
343  } else if (method == "injectKeyEvent") {
344    HandleInjectKeyEvent(*data);
345  } else if (method == "remapKey") {
346    HandleRemapKey(*data);
347  } else if (method == "trapKey") {
348    HandleTrapKey(*data);
349  } else if (method == "sendClipboardItem") {
350    HandleSendClipboardItem(*data);
351  } else if (method == "notifyClientResolution") {
352    HandleNotifyClientResolution(*data);
353  } else if (method == "pauseVideo") {
354    HandlePauseVideo(*data);
355  } else if (method == "videoControl") {
356    HandleVideoControl(*data);
357  } else if (method == "pauseAudio") {
358    HandlePauseAudio(*data);
359  } else if (method == "useAsyncPinDialog") {
360    use_async_pin_dialog_ = true;
361  } else if (method == "onPinFetched") {
362    HandleOnPinFetched(*data);
363  } else if (method == "onThirdPartyTokenFetched") {
364    HandleOnThirdPartyTokenFetched(*data);
365  } else if (method == "requestPairing") {
366    HandleRequestPairing(*data);
367  } else if (method == "extensionMessage") {
368    HandleExtensionMessage(*data);
369  } else if (method == "allowMouseLock") {
370    HandleAllowMouseLockMessage();
371  } else if (method == "enableMediaSourceRendering") {
372    HandleEnableMediaSourceRendering();
373  } else if (method == "sendMouseInputWhenUnfocused") {
374    HandleSendMouseInputWhenUnfocused();
375  } else if (method == "delegateLargeCursors") {
376    HandleDelegateLargeCursors();
377  }
378}
379
380void ChromotingInstance::DidChangeFocus(bool has_focus) {
381  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
382
383  if (!IsConnected())
384    return;
385
386  input_handler_.DidChangeFocus(has_focus);
387}
388
389void ChromotingInstance::DidChangeView(const pp::View& view) {
390  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
391
392  plugin_view_ = view;
393  mouse_input_filter_.set_input_size(
394      webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
395
396  if (view_)
397    view_->SetView(view);
398}
399
400bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
401  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
402
403  if (!IsConnected())
404    return false;
405
406  return input_handler_.HandleInputEvent(event);
407}
408
409void ChromotingInstance::SetDesktopSize(const webrtc::DesktopSize& size,
410                                        const webrtc::DesktopVector& dpi) {
411  mouse_input_filter_.set_output_size(size);
412
413  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
414  data->SetInteger("width", size.width());
415  data->SetInteger("height", size.height());
416  if (dpi.x())
417    data->SetInteger("x_dpi", dpi.x());
418  if (dpi.y())
419    data->SetInteger("y_dpi", dpi.y());
420  PostLegacyJsonMessage("onDesktopSize", data.Pass());
421}
422
423void ChromotingInstance::SetDesktopShape(const webrtc::DesktopRegion& shape) {
424  if (desktop_shape_ && shape.Equals(*desktop_shape_))
425    return;
426
427  desktop_shape_.reset(new webrtc::DesktopRegion(shape));
428
429  scoped_ptr<base::ListValue> rects_value(new base::ListValue());
430  for (webrtc::DesktopRegion::Iterator i(shape); !i.IsAtEnd(); i.Advance()) {
431    const webrtc::DesktopRect& rect = i.rect();
432    scoped_ptr<base::ListValue> rect_value(new base::ListValue());
433    rect_value->AppendInteger(rect.left());
434    rect_value->AppendInteger(rect.top());
435    rect_value->AppendInteger(rect.width());
436    rect_value->AppendInteger(rect.height());
437    rects_value->Append(rect_value.release());
438  }
439
440  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
441  data->Set("rects", rects_value.release());
442  PostLegacyJsonMessage("onDesktopShape", data.Pass());
443}
444
445void ChromotingInstance::OnConnectionState(
446    protocol::ConnectionToHost::State state,
447    protocol::ErrorCode error) {
448  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
449  data->SetString("state", ConnectionStateToString(state));
450  data->SetString("error", ConnectionErrorToString(error));
451  PostLegacyJsonMessage("onConnectionStatus", data.Pass());
452}
453
454void ChromotingInstance::FetchThirdPartyToken(
455    const GURL& token_url,
456    const std::string& host_public_key,
457    const std::string& scope,
458    base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy) {
459  // Once the Session object calls this function, it won't continue the
460  // authentication until the callback is called (or connection is canceled).
461  // So, it's impossible to reach this with a callback already registered.
462  DCHECK(!token_fetcher_proxy_.get());
463  token_fetcher_proxy_ = token_fetcher_proxy;
464  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
465  data->SetString("tokenUrl", token_url.spec());
466  data->SetString("hostPublicKey", host_public_key);
467  data->SetString("scope", scope);
468  PostLegacyJsonMessage("fetchThirdPartyToken", data.Pass());
469}
470
471void ChromotingInstance::OnConnectionReady(bool ready) {
472  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
473  data->SetBoolean("ready", ready);
474  PostLegacyJsonMessage("onConnectionReady", data.Pass());
475}
476
477void ChromotingInstance::OnRouteChanged(const std::string& channel_name,
478                                        const protocol::TransportRoute& route) {
479  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
480  std::string message = "Channel " + channel_name + " using " +
481      protocol::TransportRoute::GetTypeString(route.type) + " connection.";
482  data->SetString("message", message);
483  PostLegacyJsonMessage("logDebugMessage", data.Pass());
484}
485
486void ChromotingInstance::SetCapabilities(const std::string& capabilities) {
487  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
488  data->SetString("capabilities", capabilities);
489  PostLegacyJsonMessage("setCapabilities", data.Pass());
490}
491
492void ChromotingInstance::SetPairingResponse(
493    const protocol::PairingResponse& pairing_response) {
494  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
495  data->SetString("clientId", pairing_response.client_id());
496  data->SetString("sharedSecret", pairing_response.shared_secret());
497  PostLegacyJsonMessage("pairingResponse", data.Pass());
498}
499
500void ChromotingInstance::DeliverHostMessage(
501    const protocol::ExtensionMessage& message) {
502  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
503  data->SetString("type", message.type());
504  data->SetString("data", message.data());
505  PostLegacyJsonMessage("extensionMessage", data.Pass());
506}
507
508void ChromotingInstance::FetchSecretFromDialog(
509    bool pairing_supported,
510    const protocol::SecretFetchedCallback& secret_fetched_callback) {
511  // Once the Session object calls this function, it won't continue the
512  // authentication until the callback is called (or connection is canceled).
513  // So, it's impossible to reach this with a callback already registered.
514  DCHECK(secret_fetched_callback_.is_null());
515  secret_fetched_callback_ = secret_fetched_callback;
516  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
517  data->SetBoolean("pairingSupported", pairing_supported);
518  PostLegacyJsonMessage("fetchPin", data.Pass());
519}
520
521void ChromotingInstance::FetchSecretFromString(
522    const std::string& shared_secret,
523    bool pairing_supported,
524    const protocol::SecretFetchedCallback& secret_fetched_callback) {
525  secret_fetched_callback.Run(shared_secret);
526}
527
528protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
529  // TODO(sergeyu): Move clipboard handling to a separate class.
530  // crbug.com/138108
531  return this;
532}
533
534protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
535  // TODO(sergeyu): Move cursor shape code to a separate class.
536  // crbug.com/138108
537  return this;
538}
539
540void ChromotingInstance::InjectClipboardEvent(
541    const protocol::ClipboardEvent& event) {
542  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
543  data->SetString("mimeType", event.mime_type());
544  data->SetString("item", event.data());
545  PostLegacyJsonMessage("injectClipboardItem", data.Pass());
546}
547
548void ChromotingInstance::SetCursorShape(
549    const protocol::CursorShapeInfo& cursor_shape) {
550  COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel, rgba_pixels_are_32bit);
551
552  // pp::MouseCursor requires image to be in the native format.
553  if (pp::ImageData::GetNativeImageDataFormat() !=
554      PP_IMAGEDATAFORMAT_BGRA_PREMUL) {
555    LOG(WARNING) << "Unable to set cursor shape - native image format is not"
556                    " premultiplied BGRA";
557    return;
558  }
559
560  int width = cursor_shape.width();
561  int height = cursor_shape.height();
562
563  int hotspot_x = cursor_shape.hotspot_x();
564  int hotspot_y = cursor_shape.hotspot_y();
565  int bytes_per_row = width * kBytesPerPixel;
566  int src_stride = width;
567  const uint32_t* src_row_data = reinterpret_cast<const uint32_t*>(
568      cursor_shape.data().data());
569  const uint32_t* src_row_data_end = src_row_data + src_stride * height;
570
571  scoped_ptr<pp::ImageData> cursor_image;
572  pp::Point cursor_hotspot;
573
574  // Check if the cursor is visible.
575  if (IsVisibleRow(src_row_data, src_row_data_end)) {
576    // If the cursor exceeds the size permitted by PPAPI then crop it, keeping
577    // the hotspot as close to the center of the new cursor shape as possible.
578    if (height > kMaxCursorHeight && !delegate_large_cursors_) {
579      int y = hotspot_y - (kMaxCursorHeight / 2);
580      y = std::max(y, 0);
581      y = std::min(y, height - kMaxCursorHeight);
582
583      src_row_data += src_stride * y;
584      height = kMaxCursorHeight;
585      hotspot_y -= y;
586    }
587    if (width > kMaxCursorWidth && !delegate_large_cursors_) {
588      int x = hotspot_x - (kMaxCursorWidth / 2);
589      x = std::max(x, 0);
590      x = std::min(x, height - kMaxCursorWidth);
591
592      src_row_data += x;
593      width = kMaxCursorWidth;
594      bytes_per_row = width * kBytesPerPixel;
595      hotspot_x -= x;
596    }
597
598    cursor_image.reset(new pp::ImageData(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
599                                          pp::Size(width, height), false));
600    cursor_hotspot = pp::Point(hotspot_x, hotspot_y);
601
602    uint8* dst_row_data = reinterpret_cast<uint8*>(cursor_image->data());
603    for (int row = 0; row < height; row++) {
604      memcpy(dst_row_data, src_row_data, bytes_per_row);
605      src_row_data += src_stride;
606      dst_row_data += cursor_image->stride();
607    }
608  }
609
610  if (height > kMaxCursorHeight || width > kMaxCursorWidth) {
611    DCHECK(delegate_large_cursors_);
612    size_t buffer_size = height * bytes_per_row;
613    pp::VarArrayBuffer array_buffer(buffer_size);
614    void* dst = array_buffer.Map();
615    memcpy(dst, cursor_image->data(), buffer_size);
616    array_buffer.Unmap();
617    pp::VarDictionary dictionary;
618    dictionary.Set(pp::Var("width"), width);
619    dictionary.Set(pp::Var("height"), height);
620    dictionary.Set(pp::Var("hotspotX"), cursor_hotspot.x());
621    dictionary.Set(pp::Var("hotspotY"), cursor_hotspot.y());
622    dictionary.Set(pp::Var("data"), array_buffer);
623    PostChromotingMessage("setCursorShape", dictionary);
624    input_handler_.SetMouseCursor(scoped_ptr<pp::ImageData>(), cursor_hotspot);
625  } else {
626    if (delegate_large_cursors_) {
627      pp::VarDictionary dictionary;
628      PostChromotingMessage("unsetCursorShape", dictionary);
629    }
630    input_handler_.SetMouseCursor(cursor_image.Pass(), cursor_hotspot);
631  }
632}
633
634void ChromotingInstance::OnFirstFrameReceived() {
635  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
636  PostLegacyJsonMessage("onFirstFrameReceived", data.Pass());
637}
638
639void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) {
640  std::string local_jid;
641  std::string host_jid;
642  std::string host_public_key;
643  std::string auth_methods_str;
644  std::string authentication_tag;
645  std::vector<protocol::AuthenticationMethod> auth_methods;
646  if (!data.GetString("hostJid", &host_jid) ||
647      !data.GetString("hostPublicKey", &host_public_key) ||
648      !data.GetString("localJid", &local_jid) ||
649      !data.GetString("authenticationMethods", &auth_methods_str) ||
650      !ParseAuthMethods(auth_methods_str, &auth_methods) ||
651      !data.GetString("authenticationTag", &authentication_tag)) {
652    LOG(ERROR) << "Invalid connect() data.";
653    return;
654  }
655
656  std::string client_pairing_id;
657  data.GetString("clientPairingId", &client_pairing_id);
658  std::string client_paired_secret;
659  data.GetString("clientPairedSecret", &client_paired_secret);
660
661  protocol::FetchSecretCallback fetch_secret_callback;
662  if (use_async_pin_dialog_) {
663    fetch_secret_callback = base::Bind(
664        &ChromotingInstance::FetchSecretFromDialog, weak_factory_.GetWeakPtr());
665  } else {
666    std::string shared_secret;
667    if (!data.GetString("sharedSecret", &shared_secret)) {
668      LOG(ERROR) << "sharedSecret not specified in connect().";
669      return;
670    }
671    fetch_secret_callback =
672        base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
673  }
674
675  // Read the list of capabilities, if any.
676  std::string capabilities;
677  if (data.HasKey("capabilities")) {
678    if (!data.GetString("capabilities", &capabilities)) {
679      LOG(ERROR) << "Invalid connect() data.";
680      return;
681    }
682  }
683
684  VLOG(0) << "Connecting to " << host_jid
685          << ". Local jid: " << local_jid << ".";
686
687#if defined(OS_NACL)
688  std::string key_filter;
689  if (!data.GetString("keyFilter", &key_filter)) {
690    NOTREACHED();
691    normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
692  } else if (key_filter == "mac") {
693    normalizing_input_filter_.reset(
694        new NormalizingInputFilterMac(&key_mapper_));
695  } else if (key_filter == "cros") {
696    normalizing_input_filter_.reset(
697        new NormalizingInputFilterCros(&key_mapper_));
698  } else {
699    DCHECK(key_filter.empty());
700    normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
701  }
702#elif defined(OS_MACOSX)
703  normalizing_input_filter_.reset(new NormalizingInputFilterMac(&key_mapper_));
704#elif defined(OS_CHROMEOS)
705  normalizing_input_filter_.reset(new NormalizingInputFilterCros(&key_mapper_));
706#else
707  normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
708#endif
709  input_handler_.set_input_stub(normalizing_input_filter_.get());
710
711  if (use_media_source_rendering_) {
712    video_renderer_.reset(new MediaSourceVideoRenderer(this));
713  } else {
714    view_.reset(new PepperView(this, &context_));
715    view_weak_factory_.reset(
716        new base::WeakPtrFactory<FrameConsumer>(view_.get()));
717
718    // SoftwareVideoRenderer runs on a separate thread so for now we wrap
719    // PepperView with a ref-counted proxy object.
720    scoped_refptr<FrameConsumerProxy> consumer_proxy =
721        new FrameConsumerProxy(plugin_task_runner_,
722                               view_weak_factory_->GetWeakPtr());
723
724    SoftwareVideoRenderer* renderer =
725        new SoftwareVideoRenderer(context_.main_task_runner(),
726                                  context_.decode_task_runner(),
727                                  consumer_proxy);
728    view_->Initialize(renderer);
729    if (!plugin_view_.is_null())
730      view_->SetView(plugin_view_);
731    video_renderer_.reset(renderer);
732  }
733
734  scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
735  client_.reset(new ChromotingClient(&context_, this, video_renderer_.get(),
736                                     audio_player.Pass()));
737
738  // Connect the input pipeline to the protocol stub & initialize components.
739  mouse_input_filter_.set_input_stub(client_->input_stub());
740  if (!plugin_view_.is_null()) {
741    mouse_input_filter_.set_input_size(webrtc::DesktopSize(
742        plugin_view_.GetRect().width(), plugin_view_.GetRect().height()));
743  }
744
745  // Setup the signal strategy.
746  signal_strategy_.reset(new DelegatingSignalStrategy(
747      local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq,
748                            weak_factory_.GetWeakPtr())));
749
750  // Create TransportFactory.
751  scoped_ptr<protocol::TransportFactory> transport_factory(
752      new protocol::LibjingleTransportFactory(
753          signal_strategy_.get(),
754          PepperPortAllocator::Create(this)
755              .PassAs<cricket::HttpPortAllocatorBase>(),
756          protocol::NetworkSettings(
757              protocol::NetworkSettings::NAT_TRAVERSAL_FULL)));
758
759  // Create Authenticator.
760  scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
761      token_fetcher(new TokenFetcherProxy(
762          base::Bind(&ChromotingInstance::FetchThirdPartyToken,
763                     weak_factory_.GetWeakPtr()),
764          host_public_key));
765  scoped_ptr<protocol::Authenticator> authenticator(
766      new protocol::NegotiatingClientAuthenticator(
767          client_pairing_id, client_paired_secret, authentication_tag,
768          fetch_secret_callback, token_fetcher.Pass(), auth_methods));
769
770  // Kick off the connection.
771  client_->Start(signal_strategy_.get(), authenticator.Pass(),
772                 transport_factory.Pass(), host_jid, capabilities);
773
774  // Start timer that periodically sends perf stats.
775  plugin_task_runner_->PostDelayedTask(
776      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
777                            weak_factory_.GetWeakPtr()),
778      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
779}
780
781void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) {
782  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
783
784  // PepperView must be destroyed before the client.
785  view_weak_factory_.reset();
786  view_.reset();
787
788  VLOG(0) << "Disconnecting from host.";
789
790  // Disconnect the input pipeline and teardown the connection.
791  mouse_input_filter_.set_input_stub(NULL);
792  client_.reset();
793}
794
795void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
796  std::string iq;
797  if (!data.GetString("iq", &iq)) {
798    LOG(ERROR) << "Invalid incomingIq() data.";
799    return;
800  }
801
802  // Just ignore the message if it's received before Connect() is called. It's
803  // likely to be a leftover from a previous session, so it's safe to ignore it.
804  if (signal_strategy_)
805    signal_strategy_->OnIncomingMessage(iq);
806}
807
808void ChromotingInstance::HandleReleaseAllKeys(
809    const base::DictionaryValue& data) {
810  if (IsConnected())
811    input_tracker_.ReleaseAll();
812}
813
814void ChromotingInstance::HandleInjectKeyEvent(
815      const base::DictionaryValue& data) {
816  int usb_keycode = 0;
817  bool is_pressed = false;
818  if (!data.GetInteger("usbKeycode", &usb_keycode) ||
819      !data.GetBoolean("pressed", &is_pressed)) {
820    LOG(ERROR) << "Invalid injectKeyEvent.";
821    return;
822  }
823
824  protocol::KeyEvent event;
825  event.set_usb_keycode(usb_keycode);
826  event.set_pressed(is_pressed);
827
828  // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
829  if (IsConnected())
830    input_tracker_.InjectKeyEvent(event);
831}
832
833void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) {
834  int from_keycode = 0;
835  int to_keycode = 0;
836  if (!data.GetInteger("fromKeycode", &from_keycode) ||
837      !data.GetInteger("toKeycode", &to_keycode)) {
838    LOG(ERROR) << "Invalid remapKey.";
839    return;
840  }
841
842  key_mapper_.RemapKey(from_keycode, to_keycode);
843}
844
845void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) {
846  int keycode = 0;
847  bool trap = false;
848  if (!data.GetInteger("keycode", &keycode) ||
849      !data.GetBoolean("trap", &trap)) {
850    LOG(ERROR) << "Invalid trapKey.";
851    return;
852  }
853
854  key_mapper_.TrapKey(keycode, trap);
855}
856
857void ChromotingInstance::HandleSendClipboardItem(
858    const base::DictionaryValue& data) {
859  std::string mime_type;
860  std::string item;
861  if (!data.GetString("mimeType", &mime_type) ||
862      !data.GetString("item", &item)) {
863    LOG(ERROR) << "Invalid sendClipboardItem data.";
864    return;
865  }
866  if (!IsConnected()) {
867    return;
868  }
869  protocol::ClipboardEvent event;
870  event.set_mime_type(mime_type);
871  event.set_data(item);
872  client_->clipboard_forwarder()->InjectClipboardEvent(event);
873}
874
875void ChromotingInstance::HandleNotifyClientResolution(
876    const base::DictionaryValue& data) {
877  int width = 0;
878  int height = 0;
879  int x_dpi = kDefaultDPI;
880  int y_dpi = kDefaultDPI;
881  if (!data.GetInteger("width", &width) ||
882      !data.GetInteger("height", &height) ||
883      !data.GetInteger("x_dpi", &x_dpi) ||
884      !data.GetInteger("y_dpi", &y_dpi) ||
885      width <= 0 || height <= 0 ||
886      x_dpi <= 0 || y_dpi <= 0) {
887    LOG(ERROR) << "Invalid notifyClientResolution.";
888    return;
889  }
890
891  if (!IsConnected()) {
892    return;
893  }
894
895  protocol::ClientResolution client_resolution;
896  client_resolution.set_width(width);
897  client_resolution.set_height(height);
898  client_resolution.set_x_dpi(x_dpi);
899  client_resolution.set_y_dpi(y_dpi);
900
901  // Include the legacy width & height in DIPs for use by older hosts.
902  client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi);
903  client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi);
904
905  client_->host_stub()->NotifyClientResolution(client_resolution);
906}
907
908void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue& data) {
909  if (!data.HasKey("pause")) {
910    LOG(ERROR) << "Invalid pauseVideo.";
911    return;
912  }
913  HandleVideoControl(data);
914}
915
916void ChromotingInstance::HandleVideoControl(const base::DictionaryValue& data) {
917  protocol::VideoControl video_control;
918  bool pause_video = false;
919  if (data.GetBoolean("pause", &pause_video)) {
920    video_control.set_enable(!pause_video);
921  }
922  bool lossless_encode = false;
923  if (data.GetBoolean("losslessEncode", &lossless_encode)) {
924    video_control.set_lossless_encode(lossless_encode);
925  }
926  bool lossless_color = false;
927  if (data.GetBoolean("losslessColor", &lossless_color)) {
928    video_control.set_lossless_color(lossless_color);
929  }
930  if (!IsConnected()) {
931    return;
932  }
933  client_->host_stub()->ControlVideo(video_control);
934}
935
936void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
937  bool pause = false;
938  if (!data.GetBoolean("pause", &pause)) {
939    LOG(ERROR) << "Invalid pauseAudio.";
940    return;
941  }
942  if (!IsConnected()) {
943    return;
944  }
945  protocol::AudioControl audio_control;
946  audio_control.set_enable(!pause);
947  client_->host_stub()->ControlAudio(audio_control);
948}
949void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) {
950  std::string pin;
951  if (!data.GetString("pin", &pin)) {
952    LOG(ERROR) << "Invalid onPinFetched.";
953    return;
954  }
955  if (!secret_fetched_callback_.is_null()) {
956    secret_fetched_callback_.Run(pin);
957    secret_fetched_callback_.Reset();
958  } else {
959    LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch.";
960  }
961}
962
963void ChromotingInstance::HandleOnThirdPartyTokenFetched(
964    const base::DictionaryValue& data) {
965  std::string token;
966  std::string shared_secret;
967  if (!data.GetString("token", &token) ||
968      !data.GetString("sharedSecret", &shared_secret)) {
969    LOG(ERROR) << "Invalid onThirdPartyTokenFetched data.";
970    return;
971  }
972  if (token_fetcher_proxy_.get()) {
973    token_fetcher_proxy_->OnTokenFetched(token, shared_secret);
974    token_fetcher_proxy_.reset();
975  } else {
976    LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
977  }
978}
979
980void ChromotingInstance::HandleRequestPairing(
981    const base::DictionaryValue& data) {
982  std::string client_name;
983  if (!data.GetString("clientName", &client_name)) {
984    LOG(ERROR) << "Invalid requestPairing";
985    return;
986  }
987  if (!IsConnected()) {
988    return;
989  }
990  protocol::PairingRequest pairing_request;
991  pairing_request.set_client_name(client_name);
992  client_->host_stub()->RequestPairing(pairing_request);
993}
994
995void ChromotingInstance::HandleExtensionMessage(
996    const base::DictionaryValue& data) {
997  std::string type;
998  std::string message_data;
999  if (!data.GetString("type", &type) ||
1000      !data.GetString("data", &message_data)) {
1001    LOG(ERROR) << "Invalid extensionMessage.";
1002    return;
1003  }
1004  if (!IsConnected()) {
1005    return;
1006  }
1007  protocol::ExtensionMessage message;
1008  message.set_type(type);
1009  message.set_data(message_data);
1010  client_->host_stub()->DeliverClientMessage(message);
1011}
1012
1013void ChromotingInstance::HandleAllowMouseLockMessage() {
1014  input_handler_.AllowMouseLock();
1015}
1016
1017void ChromotingInstance::HandleEnableMediaSourceRendering() {
1018  use_media_source_rendering_ = true;
1019}
1020
1021void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
1022  input_handler_.set_send_mouse_input_when_unfocused(true);
1023}
1024
1025void ChromotingInstance::HandleDelegateLargeCursors() {
1026  delegate_large_cursors_ = true;
1027}
1028
1029ChromotingStats* ChromotingInstance::GetStats() {
1030  if (!video_renderer_.get())
1031    return NULL;
1032  return video_renderer_->GetStats();
1033}
1034
1035void ChromotingInstance::PostChromotingMessage(const std::string& method,
1036                                               const pp::VarDictionary& data) {
1037  pp::VarDictionary message;
1038  message.Set(pp::Var("method"), pp::Var(method));
1039  message.Set(pp::Var("data"), data);
1040  PostMessage(message);
1041}
1042
1043void ChromotingInstance::PostLegacyJsonMessage(
1044    const std::string& method,
1045    scoped_ptr<base::DictionaryValue> data) {
1046  scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
1047  message->SetString("method", method);
1048  message->Set("data", data.release());
1049
1050  std::string message_json;
1051  base::JSONWriter::Write(message.get(), &message_json);
1052  PostMessage(pp::Var(message_json));
1053}
1054
1055void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
1056  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1057  data->SetInteger("usbKeycode", usb_keycode);
1058  data->SetBoolean("pressed", pressed);
1059  PostLegacyJsonMessage("trappedKeyEvent", data.Pass());
1060}
1061
1062void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
1063  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1064  data->SetString("iq", iq);
1065  PostLegacyJsonMessage("sendOutgoingIq", data.Pass());
1066}
1067
1068void ChromotingInstance::SendPerfStats() {
1069  if (!video_renderer_.get()) {
1070    return;
1071  }
1072
1073  plugin_task_runner_->PostDelayedTask(
1074      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
1075                            weak_factory_.GetWeakPtr()),
1076      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
1077
1078  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1079  ChromotingStats* stats = video_renderer_->GetStats();
1080  data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate());
1081  data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate());
1082  data->SetDouble("captureLatency", stats->video_capture_ms()->Average());
1083  data->SetDouble("encodeLatency", stats->video_encode_ms()->Average());
1084  data->SetDouble("decodeLatency", stats->video_decode_ms()->Average());
1085  data->SetDouble("renderLatency", stats->video_paint_ms()->Average());
1086  data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average());
1087  PostLegacyJsonMessage("onPerfStats", data.Pass());
1088}
1089
1090// static
1091void ChromotingInstance::RegisterLogMessageHandler() {
1092  base::AutoLock lock(g_logging_lock.Get());
1093
1094  VLOG(1) << "Registering global log handler";
1095
1096  // Record previous handler so we can call it in a chain.
1097  g_logging_old_handler = logging::GetLogMessageHandler();
1098
1099  // Set up log message handler.
1100  // This is not thread-safe so we need it within our lock.
1101  logging::SetLogMessageHandler(&LogToUI);
1102}
1103
1104void ChromotingInstance::RegisterLoggingInstance() {
1105  base::AutoLock lock(g_logging_lock.Get());
1106
1107  // Register this instance as the one that will handle all logging calls
1108  // and display them to the user.
1109  // If multiple plugins are run, then the last one registered will handle all
1110  // logging for all instances.
1111  g_logging_instance.Get() = weak_factory_.GetWeakPtr();
1112  g_logging_task_runner.Get() = plugin_task_runner_;
1113  g_has_logging_instance = true;
1114}
1115
1116void ChromotingInstance::UnregisterLoggingInstance() {
1117  base::AutoLock lock(g_logging_lock.Get());
1118
1119  // Don't unregister unless we're the currently registered instance.
1120  if (this != g_logging_instance.Get().get())
1121    return;
1122
1123  // Unregister this instance for logging.
1124  g_has_logging_instance = false;
1125  g_logging_instance.Get().reset();
1126  g_logging_task_runner.Get() = NULL;
1127
1128  VLOG(1) << "Unregistering global log handler";
1129}
1130
1131// static
1132bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
1133                                 size_t message_start,
1134                                 const std::string& str) {
1135  // Note that we're reading |g_has_logging_instance| outside of a lock.
1136  // This lockless read is done so that we don't needlessly slow down global
1137  // logging with a lock for each log message.
1138  //
1139  // This lockless read is safe because:
1140  //
1141  // Misreading a false value (when it should be true) means that we'll simply
1142  // skip processing a few log messages.
1143  //
1144  // Misreading a true value (when it should be false) means that we'll take
1145  // the lock and check |g_logging_instance| unnecessarily. This is not
1146  // problematic because we always set |g_logging_instance| inside a lock.
1147  if (g_has_logging_instance) {
1148    scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner;
1149    base::WeakPtr<ChromotingInstance> logging_instance;
1150
1151    {
1152      base::AutoLock lock(g_logging_lock.Get());
1153      // If we're on the logging thread and |g_logging_to_plugin| is set then
1154      // this LOG message came from handling a previous LOG message and we
1155      // should skip it to avoid an infinite loop of LOG messages.
1156      if (!g_logging_task_runner.Get()->BelongsToCurrentThread() ||
1157          !g_logging_to_plugin) {
1158        logging_task_runner = g_logging_task_runner.Get();
1159        logging_instance = g_logging_instance.Get();
1160      }
1161    }
1162
1163    if (logging_task_runner.get()) {
1164      std::string message = remoting::GetTimestampString();
1165      message += (str.c_str() + message_start);
1166
1167      logging_task_runner->PostTask(
1168          FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
1169                                logging_instance, message));
1170    }
1171  }
1172
1173  if (g_logging_old_handler)
1174    return (g_logging_old_handler)(severity, file, line, message_start, str);
1175  return false;
1176}
1177
1178void ChromotingInstance::ProcessLogToUI(const std::string& message) {
1179  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
1180
1181  // This flag (which is set only here) is used to prevent LogToUI from posting
1182  // new tasks while we're in the middle of servicing a LOG call. This can
1183  // happen if the call to LogDebugInfo tries to LOG anything.
1184  // Since it is read on the plugin thread, we don't need to lock to set it.
1185  g_logging_to_plugin = true;
1186  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1187  data->SetString("message", message);
1188  PostLegacyJsonMessage("logDebugMessage", data.Pass());
1189  g_logging_to_plugin = false;
1190}
1191
1192bool ChromotingInstance::IsCallerAppOrExtension() {
1193  const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get();
1194  if (!url_util)
1195    return false;
1196
1197  PP_URLComponents_Dev url_components;
1198  pp::Var url_var = url_util->GetDocumentURL(this, &url_components);
1199  if (!url_var.is_string())
1200    return false;
1201
1202  std::string url = url_var.AsString();
1203  std::string url_scheme = url.substr(url_components.scheme.begin,
1204                                      url_components.scheme.len);
1205  return url_scheme == kChromeExtensionUrlScheme;
1206}
1207
1208bool ChromotingInstance::IsConnected() {
1209  return client_ &&
1210         (client_->connection_state() == protocol::ConnectionToHost::CONNECTED);
1211}
1212
1213void ChromotingInstance::OnMediaSourceSize(const webrtc::DesktopSize& size,
1214                                           const webrtc::DesktopVector& dpi) {
1215  SetDesktopSize(size, dpi);
1216}
1217
1218void ChromotingInstance::OnMediaSourceShape(
1219    const webrtc::DesktopRegion& shape) {
1220  SetDesktopShape(shape);
1221}
1222
1223void ChromotingInstance::OnMediaSourceReset(const std::string& format) {
1224  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1225  data->SetString("format", format);
1226  PostLegacyJsonMessage("mediaSourceReset", data.Pass());
1227}
1228
1229void ChromotingInstance::OnMediaSourceData(uint8_t* buffer, size_t buffer_size,
1230                                           bool keyframe) {
1231  pp::VarArrayBuffer array_buffer(buffer_size);
1232  void* data_ptr = array_buffer.Map();
1233  memcpy(data_ptr, buffer, buffer_size);
1234  array_buffer.Unmap();
1235  pp::VarDictionary data_dictionary;
1236  data_dictionary.Set(pp::Var("buffer"), array_buffer);
1237  data_dictionary.Set(pp::Var("keyframe"), keyframe);
1238  PostChromotingMessage("mediaSourceData", data_dictionary);
1239}
1240
1241}  // namespace remoting
1242