chromoting_instance.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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, 1200 size_t buffer_size) { 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 PostChromotingMessage("mediaSourceData", data_dictionary); 1208} 1209 1210} // namespace remoting 1211