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