Update parsing of dumpsys batterystats
[chromium-blink-merge.git] / remoting / client / plugin / chromoting_instance.cc
blob2e40117c907e05be2eee675a680692749c294274
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/client/plugin/chromoting_instance.h"
7 #include <algorithm>
8 #include <string>
9 #include <vector>
11 #if defined(OS_NACL)
12 #include <sys/mount.h>
13 #include <nacl_io/nacl_io.h>
14 #endif
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/libjingle/source/talk/base/helpers.h"
56 #include "third_party/libjingle/source/talk/base/ssladapter.h"
57 #include "url/gurl.h"
59 // Windows defines 'PostMessage', so we have to undef it.
60 #if defined(PostMessage)
61 #undef PostMessage
62 #endif
64 namespace remoting {
66 namespace {
68 // 32-bit BGRA is 4 bytes per pixel.
69 const int kBytesPerPixel = 4;
71 #if defined(ARCH_CPU_LITTLE_ENDIAN)
72 const uint32_t kPixelAlphaMask = 0xff000000;
73 #else // !defined(ARCH_CPU_LITTLE_ENDIAN)
74 const uint32_t kPixelAlphaMask = 0x000000ff;
75 #endif // !defined(ARCH_CPU_LITTLE_ENDIAN)
77 // Default DPI to assume for old clients that use notifyClientResolution.
78 const int kDefaultDPI = 96;
80 // Interval at which to sample performance statistics.
81 const int kPerfStatsIntervalMs = 1000;
83 // URL scheme used by Chrome apps and extensions.
84 const char kChromeExtensionUrlScheme[] = "chrome-extension";
86 // Maximum width and height of a mouse cursor supported by PPAPI.
87 const int kMaxCursorWidth = 32;
88 const int kMaxCursorHeight = 32;
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.
95 const int kRandomSeedSize = 1024;
96 #endif // defined(USE_OPENSSL)
98 std::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";
117 NOTREACHED();
118 return std::string();
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.
125 std::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";
132 case protocol::PEER_IS_OFFLINE:
133 return "HOST_IS_OFFLINE";
135 case protocol::SESSION_REJECTED:
136 case protocol::AUTHENTICATION_FAILED:
137 return "SESSION_REJECTED";
139 case protocol::INCOMPATIBLE_PROTOCOL:
140 return "INCOMPATIBLE_PROTOCOL";
142 case protocol::HOST_OVERLOAD:
143 return "HOST_OVERLOAD";
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";
151 DLOG(FATAL) << "Unknown error code" << error;
152 return std::string();
155 // Returns true if |pixel| is not completely transparent.
156 bool IsVisiblePixel(uint32_t pixel) {
157 return (pixel & kPixelAlphaMask) != 0;
160 // Returns true if there is at least one visible pixel in the given range.
161 bool IsVisibleRow(const uint32_t* begin, const uint32_t* end) {
162 return std::find_if(begin, end, &IsVisiblePixel) != end;
165 bool 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);
177 if (auth_methods->empty()) {
178 LOG(ERROR) << "No valid authentication methods specified.";
179 return false;
182 return true;
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.
188 bool g_logging_to_plugin = false;
189 bool g_has_logging_instance = false;
190 base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
191 g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
192 base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
193 g_logging_instance = LAZY_INSTANCE_INITIALIZER;
194 base::LazyInstance<base::Lock>::Leaky
195 g_logging_lock = LAZY_INSTANCE_INITIALIZER;
196 logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
198 } // namespace
200 // String sent in the "hello" message to the webapp to describe features.
201 const char ChromotingInstance::kApiFeatures[] =
202 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
203 "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
204 "pinlessAuth extensionMessage allowMouseLock mediaSourceRendering "
205 "videoControl";
207 const char ChromotingInstance::kRequestedCapabilities[] = "";
208 const char ChromotingInstance::kSupportedCapabilities[] = "desktopShape";
210 ChromotingInstance::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
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
240 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
241 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
243 // Resister this instance to handle debug log messsages.
244 RegisterLoggingInstance();
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 talk_base::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 talk_base::InitializeSSL();
256 #endif // !defined(USE_OPENSSL)
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);
266 PostLegacyJsonMessage("hello", data.Pass());
269 ChromotingInstance::~ChromotingInstance() {
270 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
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();
276 // PepperView must be destroyed before the client.
277 view_weak_factory_.reset();
278 view_.reset();
280 client_.reset();
282 plugin_task_runner_->Quit();
284 // Ensure that nothing touches the plugin thread delegate after this point.
285 plugin_task_runner_->DetachAndRunShutdownLoop();
287 // Stopping the context shuts down all chromoting threads.
288 context_.Stop();
291 bool ChromotingInstance::Init(uint32_t argc,
292 const char* argn[],
293 const char* argv[]) {
294 CHECK(!initialized_);
295 initialized_ = true;
297 VLOG(1) << "Started ChromotingInstance::Init";
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;
307 #endif
309 // Start all the threads.
310 context_.Start();
312 return true;
315 void ChromotingInstance::HandleMessage(const pp::Var& message) {
316 if (!message.is_string()) {
317 LOG(ERROR) << "Received a message that is not a string.";
318 return;
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;
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();
380 void ChromotingInstance::DidChangeFocus(bool has_focus) {
381 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
383 if (!IsConnected())
384 return;
386 input_handler_.DidChangeFocus(has_focus);
389 void ChromotingInstance::DidChangeView(const pp::View& view) {
390 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
392 plugin_view_ = view;
393 mouse_input_filter_.set_input_size(
394 webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
396 if (view_)
397 view_->SetView(view);
400 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
401 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
403 if (!IsConnected())
404 return false;
406 return input_handler_.HandleInputEvent(event);
409 void ChromotingInstance::SetDesktopSize(const webrtc::DesktopSize& size,
410 const webrtc::DesktopVector& dpi) {
411 mouse_input_filter_.set_output_size(size);
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());
423 void ChromotingInstance::SetDesktopShape(const webrtc::DesktopRegion& shape) {
424 if (desktop_shape_ && shape.Equals(*desktop_shape_))
425 return;
427 desktop_shape_.reset(new webrtc::DesktopRegion(shape));
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());
440 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
441 data->Set("rects", rects_value.release());
442 PostLegacyJsonMessage("onDesktopShape", data.Pass());
445 void 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());
454 void 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());
471 void ChromotingInstance::OnConnectionReady(bool ready) {
472 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
473 data->SetBoolean("ready", ready);
474 PostLegacyJsonMessage("onConnectionReady", data.Pass());
477 void 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());
486 void 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());
492 void 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());
500 void 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());
508 void 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());
521 void 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);
528 protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
529 // TODO(sergeyu): Move clipboard handling to a separate class.
530 // crbug.com/138108
531 return this;
534 protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
535 // TODO(sergeyu): Move cursor shape code to a separate class.
536 // crbug.com/138108
537 return this;
540 void 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());
548 void ChromotingInstance::SetCursorShape(
549 const protocol::CursorShapeInfo& cursor_shape) {
550 COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel, rgba_pixels_are_32bit);
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;
560 int width = cursor_shape.width();
561 int height = cursor_shape.height();
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;
571 scoped_ptr<pp::ImageData> cursor_image;
572 pp::Point cursor_hotspot;
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);
583 src_row_data += src_stride * y;
584 height = kMaxCursorHeight;
585 hotspot_y -= y;
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);
592 src_row_data += x;
593 width = kMaxCursorWidth;
594 bytes_per_row = width * kBytesPerPixel;
595 hotspot_x -= x;
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);
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();
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);
630 input_handler_.SetMouseCursor(cursor_image.Pass(), cursor_hotspot);
634 void ChromotingInstance::OnFirstFrameReceived() {
635 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
636 PostLegacyJsonMessage("onFirstFrameReceived", data.Pass());
639 void 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;
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);
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;
671 fetch_secret_callback =
672 base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
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;
684 VLOG(0) << "Connecting to " << host_jid
685 << ". Local jid: " << local_jid << ".";
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_));
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());
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()));
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());
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);
734 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
735 client_.reset(new ChromotingClient(&context_, this, video_renderer_.get(),
736 audio_player.Pass()));
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()));
745 // Setup the signal strategy.
746 signal_strategy_.reset(new DelegatingSignalStrategy(
747 local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq,
748 weak_factory_.GetWeakPtr())));
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)));
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));
770 // Kick off the connection.
771 client_->Start(signal_strategy_.get(), authenticator.Pass(),
772 transport_factory.Pass(), host_jid, capabilities);
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));
781 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) {
782 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
784 // PepperView must be destroyed before the client.
785 view_weak_factory_.reset();
786 view_.reset();
788 VLOG(0) << "Disconnecting from host.";
790 // Disconnect the input pipeline and teardown the connection.
791 mouse_input_filter_.set_input_stub(NULL);
792 client_.reset();
795 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
796 std::string iq;
797 if (!data.GetString("iq", &iq)) {
798 LOG(ERROR) << "Invalid incomingIq() data.";
799 return;
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);
808 void ChromotingInstance::HandleReleaseAllKeys(
809 const base::DictionaryValue& data) {
810 if (IsConnected())
811 input_tracker_.ReleaseAll();
814 void 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;
824 protocol::KeyEvent event;
825 event.set_usb_keycode(usb_keycode);
826 event.set_pressed(is_pressed);
828 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
829 if (IsConnected())
830 input_tracker_.InjectKeyEvent(event);
833 void 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;
842 key_mapper_.RemapKey(from_keycode, to_keycode);
845 void 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;
854 key_mapper_.TrapKey(keycode, trap);
857 void 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;
866 if (!IsConnected()) {
867 return;
869 protocol::ClipboardEvent event;
870 event.set_mime_type(mime_type);
871 event.set_data(item);
872 client_->clipboard_forwarder()->InjectClipboardEvent(event);
875 void 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;
891 if (!IsConnected()) {
892 return;
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);
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);
905 client_->host_stub()->NotifyClientResolution(client_resolution);
908 void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue& data) {
909 if (!data.HasKey("pause")) {
910 LOG(ERROR) << "Invalid pauseVideo.";
911 return;
913 HandleVideoControl(data);
916 void 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);
922 bool lossless_encode = false;
923 if (data.GetBoolean("losslessEncode", &lossless_encode)) {
924 video_control.set_lossless_encode(lossless_encode);
926 bool lossless_color = false;
927 if (data.GetBoolean("losslessColor", &lossless_color)) {
928 video_control.set_lossless_color(lossless_color);
930 if (!IsConnected()) {
931 return;
933 client_->host_stub()->ControlVideo(video_control);
936 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
937 bool pause = false;
938 if (!data.GetBoolean("pause", &pause)) {
939 LOG(ERROR) << "Invalid pauseAudio.";
940 return;
942 if (!IsConnected()) {
943 return;
945 protocol::AudioControl audio_control;
946 audio_control.set_enable(!pause);
947 client_->host_stub()->ControlAudio(audio_control);
949 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) {
950 std::string pin;
951 if (!data.GetString("pin", &pin)) {
952 LOG(ERROR) << "Invalid onPinFetched.";
953 return;
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.";
963 void 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;
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.";
980 void 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;
987 if (!IsConnected()) {
988 return;
990 protocol::PairingRequest pairing_request;
991 pairing_request.set_client_name(client_name);
992 client_->host_stub()->RequestPairing(pairing_request);
995 void 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;
1004 if (!IsConnected()) {
1005 return;
1007 protocol::ExtensionMessage message;
1008 message.set_type(type);
1009 message.set_data(message_data);
1010 client_->host_stub()->DeliverClientMessage(message);
1013 void ChromotingInstance::HandleAllowMouseLockMessage() {
1014 input_handler_.AllowMouseLock();
1017 void ChromotingInstance::HandleEnableMediaSourceRendering() {
1018 use_media_source_rendering_ = true;
1021 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
1022 input_handler_.set_send_mouse_input_when_unfocused(true);
1025 void ChromotingInstance::HandleDelegateLargeCursors() {
1026 delegate_large_cursors_ = true;
1029 ChromotingStats* ChromotingInstance::GetStats() {
1030 if (!video_renderer_.get())
1031 return NULL;
1032 return video_renderer_->GetStats();
1035 void 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);
1043 void 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());
1050 std::string message_json;
1051 base::JSONWriter::Write(message.get(), &message_json);
1052 PostMessage(pp::Var(message_json));
1055 void 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());
1062 void 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());
1068 void ChromotingInstance::SendPerfStats() {
1069 if (!video_renderer_.get()) {
1070 return;
1073 plugin_task_runner_->PostDelayedTask(
1074 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
1075 weak_factory_.GetWeakPtr()),
1076 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
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());
1090 // static
1091 void ChromotingInstance::RegisterLogMessageHandler() {
1092 base::AutoLock lock(g_logging_lock.Get());
1094 VLOG(1) << "Registering global log handler";
1096 // Record previous handler so we can call it in a chain.
1097 g_logging_old_handler = logging::GetLogMessageHandler();
1099 // Set up log message handler.
1100 // This is not thread-safe so we need it within our lock.
1101 logging::SetLogMessageHandler(&LogToUI);
1104 void ChromotingInstance::RegisterLoggingInstance() {
1105 base::AutoLock lock(g_logging_lock.Get());
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;
1116 void ChromotingInstance::UnregisterLoggingInstance() {
1117 base::AutoLock lock(g_logging_lock.Get());
1119 // Don't unregister unless we're the currently registered instance.
1120 if (this != g_logging_instance.Get().get())
1121 return;
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;
1128 VLOG(1) << "Unregistering global log handler";
1131 // static
1132 bool 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.
1139 // This lockless read is safe because:
1141 // Misreading a false value (when it should be true) means that we'll simply
1142 // skip processing a few log messages.
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;
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();
1163 if (logging_task_runner.get()) {
1164 std::string message = remoting::GetTimestampString();
1165 message += (str.c_str() + message_start);
1167 logging_task_runner->PostTask(
1168 FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
1169 logging_instance, message));
1173 if (g_logging_old_handler)
1174 return (g_logging_old_handler)(severity, file, line, message_start, str);
1175 return false;
1178 void ChromotingInstance::ProcessLogToUI(const std::string& message) {
1179 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
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;
1192 bool ChromotingInstance::IsCallerAppOrExtension() {
1193 const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get();
1194 if (!url_util)
1195 return false;
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;
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;
1208 bool ChromotingInstance::IsConnected() {
1209 return client_ &&
1210 (client_->connection_state() == protocol::ConnectionToHost::CONNECTED);
1213 void ChromotingInstance::OnMediaSourceSize(const webrtc::DesktopSize& size,
1214 const webrtc::DesktopVector& dpi) {
1215 SetDesktopSize(size, dpi);
1218 void ChromotingInstance::OnMediaSourceShape(
1219 const webrtc::DesktopRegion& shape) {
1220 SetDesktopShape(shape);
1223 void 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());
1229 void 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);
1241 } // namespace remoting