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 "ppapi/proxy/ppp_content_decryptor_private_proxy.h" 6 7#include "base/files/file.h" 8#include "ppapi/c/pp_bool.h" 9#include "ppapi/c/ppb_core.h" 10#include "ppapi/proxy/content_decryptor_private_serializer.h" 11#include "ppapi/proxy/host_dispatcher.h" 12#include "ppapi/proxy/plugin_globals.h" 13#include "ppapi/proxy/plugin_resource_tracker.h" 14#include "ppapi/proxy/ppapi_messages.h" 15#include "ppapi/proxy/ppb_buffer_proxy.h" 16#include "ppapi/proxy/serialized_var.h" 17#include "ppapi/shared_impl/scoped_pp_resource.h" 18#include "ppapi/shared_impl/var_tracker.h" 19#include "ppapi/thunk/enter.h" 20#include "ppapi/thunk/ppb_buffer_api.h" 21#include "ppapi/thunk/ppb_instance_api.h" 22#include "ppapi/thunk/thunk.h" 23 24using ppapi::thunk::EnterResourceNoLock; 25using ppapi::thunk::PPB_Buffer_API; 26using ppapi::thunk::PPB_Instance_API; 27 28namespace ppapi { 29namespace proxy { 30 31namespace { 32 33PP_Bool DescribeHostBufferResource(PP_Resource resource, uint32_t* size) { 34 EnterResourceNoLock<PPB_Buffer_API> enter(resource, true); 35 if (enter.failed()) 36 return PP_FALSE; 37 return enter.object()->Describe(size); 38} 39 40// TODO(dmichael): Refactor so this handle sharing code is in one place. 41PP_Bool ShareHostBufferResourceToPlugin( 42 HostDispatcher* dispatcher, 43 PP_Resource resource, 44 base::SharedMemoryHandle* shared_mem_handle) { 45 if (!dispatcher || resource == 0 || !shared_mem_handle) 46 return PP_FALSE; 47 EnterResourceNoLock<PPB_Buffer_API> enter(resource, true); 48 if (enter.failed()) 49 return PP_FALSE; 50 int handle; 51 int32_t result = enter.object()->GetSharedMemory(&handle); 52 if (result != PP_OK) 53 return PP_FALSE; 54 base::PlatformFile platform_file = 55 #if defined(OS_WIN) 56 reinterpret_cast<HANDLE>(static_cast<intptr_t>(handle)); 57 #elif defined(OS_POSIX) 58 handle; 59 #else 60 #error Not implemented. 61 #endif 62 63 *shared_mem_handle = dispatcher->ShareHandleWithRemote(platform_file, false); 64 return PP_TRUE; 65} 66 67// SerializedVarReceiveInput will decrement the reference count, but we want 68// to give the recipient a reference. This utility function takes care of that 69// work for the message handlers defined below. 70PP_Var ExtractReceivedVarAndAddRef(Dispatcher* dispatcher, 71 SerializedVarReceiveInput* serialized_var) { 72 PP_Var var = serialized_var->Get(dispatcher); 73 PpapiGlobals::Get()->GetVarTracker()->AddRefVar(var); 74 return var; 75} 76 77bool InitializePppDecryptorBuffer(PP_Instance instance, 78 HostDispatcher* dispatcher, 79 PP_Resource resource, 80 PPPDecryptor_Buffer* buffer) { 81 if (!buffer) { 82 NOTREACHED(); 83 return false; 84 } 85 86 if (resource == 0) { 87 buffer->resource = HostResource(); 88 buffer->handle = base::SharedMemoryHandle(); 89 buffer->size = 0; 90 return true; 91 } 92 93 HostResource host_resource; 94 host_resource.SetHostResource(instance, resource); 95 96 uint32_t size = 0; 97 if (DescribeHostBufferResource(resource, &size) == PP_FALSE) 98 return false; 99 100 base::SharedMemoryHandle handle; 101 if (ShareHostBufferResourceToPlugin(dispatcher, 102 resource, 103 &handle) == PP_FALSE) 104 return false; 105 106 buffer->resource = host_resource; 107 buffer->handle = handle; 108 buffer->size = size; 109 return true; 110} 111 112void Initialize(PP_Instance instance, 113 PP_Var key_system) { 114 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 115 if (!dispatcher) { 116 NOTREACHED(); 117 return; 118 } 119 120 dispatcher->Send( 121 new PpapiMsg_PPPContentDecryptor_Initialize( 122 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 123 instance, 124 SerializedVarSendInput(dispatcher, key_system))); 125} 126 127void CreateSession(PP_Instance instance, 128 uint32_t promise_id, 129 PP_Var init_data_type, 130 PP_Var init_data, 131 PP_SessionType session_type) { 132 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 133 if (!dispatcher) { 134 NOTREACHED(); 135 return; 136 } 137 138 dispatcher->Send(new PpapiMsg_PPPContentDecryptor_CreateSession( 139 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 140 instance, 141 promise_id, 142 SerializedVarSendInput(dispatcher, init_data_type), 143 SerializedVarSendInput(dispatcher, init_data), 144 session_type)); 145} 146 147void LoadSession(PP_Instance instance, 148 uint32_t promise_id, 149 PP_Var web_session_id) { 150 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 151 if (!dispatcher) { 152 NOTREACHED(); 153 return; 154 } 155 156 dispatcher->Send(new PpapiMsg_PPPContentDecryptor_LoadSession( 157 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 158 instance, 159 promise_id, 160 SerializedVarSendInput(dispatcher, web_session_id))); 161} 162 163void UpdateSession(PP_Instance instance, 164 uint32_t promise_id, 165 PP_Var web_session_id, 166 PP_Var response) { 167 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 168 if (!dispatcher) { 169 NOTREACHED(); 170 return; 171 } 172 173 dispatcher->Send(new PpapiMsg_PPPContentDecryptor_UpdateSession( 174 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 175 instance, 176 promise_id, 177 SerializedVarSendInput(dispatcher, web_session_id), 178 SerializedVarSendInput(dispatcher, response))); 179} 180 181void ReleaseSession(PP_Instance instance, 182 uint32_t promise_id, 183 PP_Var web_session_id) { 184 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 185 if (!dispatcher) { 186 NOTREACHED(); 187 return; 188 } 189 190 dispatcher->Send(new PpapiMsg_PPPContentDecryptor_ReleaseSession( 191 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 192 instance, 193 promise_id, 194 SerializedVarSendInput(dispatcher, web_session_id))); 195} 196 197void Decrypt(PP_Instance instance, 198 PP_Resource encrypted_block, 199 const PP_EncryptedBlockInfo* encrypted_block_info) { 200 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 201 if (!dispatcher) { 202 NOTREACHED(); 203 return; 204 } 205 206 PPPDecryptor_Buffer buffer; 207 if (!InitializePppDecryptorBuffer(instance, 208 dispatcher, 209 encrypted_block, 210 &buffer)) { 211 NOTREACHED(); 212 return; 213 } 214 215 std::string serialized_block_info; 216 if (!SerializeBlockInfo(*encrypted_block_info, &serialized_block_info)) { 217 NOTREACHED(); 218 return; 219 } 220 221 // PluginResourceTracker in the plugin process assumes that resources that it 222 // tracks have been addrefed on behalf of the plugin at the renderer side. So 223 // we explicitly do it for |encryped_block| here. 224 PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(encrypted_block); 225 226 dispatcher->Send( 227 new PpapiMsg_PPPContentDecryptor_Decrypt( 228 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 229 instance, 230 buffer, 231 serialized_block_info)); 232} 233 234void InitializeAudioDecoder( 235 PP_Instance instance, 236 const PP_AudioDecoderConfig* decoder_config, 237 PP_Resource extra_data_buffer) { 238 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 239 if (!dispatcher) { 240 NOTREACHED(); 241 return; 242 } 243 244 std::string serialized_decoder_config; 245 if (!SerializeBlockInfo(*decoder_config, &serialized_decoder_config)) { 246 NOTREACHED(); 247 return; 248 } 249 250 PPPDecryptor_Buffer buffer; 251 if (!InitializePppDecryptorBuffer(instance, 252 dispatcher, 253 extra_data_buffer, 254 &buffer)) { 255 NOTREACHED(); 256 return; 257 } 258 259 // PluginResourceTracker in the plugin process assumes that resources that it 260 // tracks have been addrefed on behalf of the plugin at the renderer side. So 261 // we explicitly do it for |extra_data_buffer| here. 262 PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(extra_data_buffer); 263 264 dispatcher->Send( 265 new PpapiMsg_PPPContentDecryptor_InitializeAudioDecoder( 266 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 267 instance, 268 serialized_decoder_config, 269 buffer)); 270} 271 272void InitializeVideoDecoder( 273 PP_Instance instance, 274 const PP_VideoDecoderConfig* decoder_config, 275 PP_Resource extra_data_buffer) { 276 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 277 if (!dispatcher) { 278 NOTREACHED(); 279 return; 280 } 281 282 std::string serialized_decoder_config; 283 if (!SerializeBlockInfo(*decoder_config, &serialized_decoder_config)) { 284 NOTREACHED(); 285 return; 286 } 287 288 PPPDecryptor_Buffer buffer; 289 if (!InitializePppDecryptorBuffer(instance, 290 dispatcher, 291 extra_data_buffer, 292 &buffer)) { 293 NOTREACHED(); 294 return; 295 } 296 297 // PluginResourceTracker in the plugin process assumes that resources that it 298 // tracks have been addrefed on behalf of the plugin at the renderer side. So 299 // we explicitly do it for |extra_data_buffer| here. 300 PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(extra_data_buffer); 301 302 dispatcher->Send( 303 new PpapiMsg_PPPContentDecryptor_InitializeVideoDecoder( 304 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 305 instance, 306 serialized_decoder_config, 307 buffer)); 308} 309 310 311void DeinitializeDecoder(PP_Instance instance, 312 PP_DecryptorStreamType decoder_type, 313 uint32_t request_id) { 314 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 315 if (!dispatcher) { 316 NOTREACHED(); 317 return; 318 } 319 320 dispatcher->Send( 321 new PpapiMsg_PPPContentDecryptor_DeinitializeDecoder( 322 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 323 instance, 324 decoder_type, 325 request_id)); 326} 327 328void ResetDecoder(PP_Instance instance, 329 PP_DecryptorStreamType decoder_type, 330 uint32_t request_id) { 331 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 332 if (!dispatcher) { 333 NOTREACHED(); 334 return; 335 } 336 337 dispatcher->Send( 338 new PpapiMsg_PPPContentDecryptor_ResetDecoder( 339 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 340 instance, 341 decoder_type, 342 request_id)); 343} 344 345void DecryptAndDecode(PP_Instance instance, 346 PP_DecryptorStreamType decoder_type, 347 PP_Resource encrypted_buffer, 348 const PP_EncryptedBlockInfo* encrypted_block_info) { 349 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); 350 if (!dispatcher) { 351 NOTREACHED(); 352 return; 353 } 354 355 PPPDecryptor_Buffer buffer; 356 if (!InitializePppDecryptorBuffer(instance, 357 dispatcher, 358 encrypted_buffer, 359 &buffer)) { 360 NOTREACHED(); 361 return; 362 } 363 364 std::string serialized_block_info; 365 if (!SerializeBlockInfo(*encrypted_block_info, &serialized_block_info)) { 366 NOTREACHED(); 367 return; 368 } 369 370 // PluginResourceTracker in the plugin process assumes that resources that it 371 // tracks have been addrefed on behalf of the plugin at the renderer side. So 372 // we explicitly do it for |encrypted_buffer| here. 373 PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(encrypted_buffer); 374 375 dispatcher->Send( 376 new PpapiMsg_PPPContentDecryptor_DecryptAndDecode( 377 API_ID_PPP_CONTENT_DECRYPTOR_PRIVATE, 378 instance, 379 decoder_type, 380 buffer, 381 serialized_block_info)); 382} 383 384static const PPP_ContentDecryptor_Private content_decryptor_interface = { 385 &Initialize, 386 &CreateSession, 387 &LoadSession, 388 &UpdateSession, 389 &ReleaseSession, 390 &Decrypt, 391 &InitializeAudioDecoder, 392 &InitializeVideoDecoder, 393 &DeinitializeDecoder, 394 &ResetDecoder, 395 &DecryptAndDecode 396}; 397 398} // namespace 399 400PPP_ContentDecryptor_Private_Proxy::PPP_ContentDecryptor_Private_Proxy( 401 Dispatcher* dispatcher) 402 : InterfaceProxy(dispatcher), 403 ppp_decryptor_impl_(NULL) { 404 if (dispatcher->IsPlugin()) { 405 ppp_decryptor_impl_ = static_cast<const PPP_ContentDecryptor_Private*>( 406 dispatcher->local_get_interface()( 407 PPP_CONTENTDECRYPTOR_PRIVATE_INTERFACE)); 408 } 409} 410 411PPP_ContentDecryptor_Private_Proxy::~PPP_ContentDecryptor_Private_Proxy() { 412} 413 414// static 415const PPP_ContentDecryptor_Private* 416 PPP_ContentDecryptor_Private_Proxy::GetProxyInterface() { 417 return &content_decryptor_interface; 418} 419 420bool PPP_ContentDecryptor_Private_Proxy::OnMessageReceived( 421 const IPC::Message& msg) { 422 if (!dispatcher()->IsPlugin()) 423 return false; // These are only valid from host->plugin. 424 // Don't allow the plugin to send these to the host. 425 426 bool handled = true; 427 IPC_BEGIN_MESSAGE_MAP(PPP_ContentDecryptor_Private_Proxy, msg) 428 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_Initialize, 429 OnMsgInitialize) 430 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_CreateSession, 431 OnMsgCreateSession) 432 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_LoadSession, 433 OnMsgLoadSession) 434 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_UpdateSession, 435 OnMsgUpdateSession) 436 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_ReleaseSession, 437 OnMsgReleaseSession) 438 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_Decrypt, 439 OnMsgDecrypt) 440 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_InitializeAudioDecoder, 441 OnMsgInitializeAudioDecoder) 442 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_InitializeVideoDecoder, 443 OnMsgInitializeVideoDecoder) 444 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_DeinitializeDecoder, 445 OnMsgDeinitializeDecoder) 446 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_ResetDecoder, 447 OnMsgResetDecoder) 448 IPC_MESSAGE_HANDLER(PpapiMsg_PPPContentDecryptor_DecryptAndDecode, 449 OnMsgDecryptAndDecode) 450 IPC_MESSAGE_UNHANDLED(handled = false) 451 IPC_END_MESSAGE_MAP() 452 DCHECK(handled); 453 return handled; 454} 455 456void PPP_ContentDecryptor_Private_Proxy::OnMsgInitialize( 457 PP_Instance instance, 458 SerializedVarReceiveInput key_system) { 459 if (ppp_decryptor_impl_) { 460 CallWhileUnlocked( 461 ppp_decryptor_impl_->Initialize, 462 instance, 463 ExtractReceivedVarAndAddRef(dispatcher(), &key_system)); 464 } 465} 466 467void PPP_ContentDecryptor_Private_Proxy::OnMsgCreateSession( 468 PP_Instance instance, 469 uint32_t promise_id, 470 SerializedVarReceiveInput init_data_type, 471 SerializedVarReceiveInput init_data, 472 PP_SessionType session_type) { 473 if (ppp_decryptor_impl_) { 474 CallWhileUnlocked( 475 ppp_decryptor_impl_->CreateSession, 476 instance, 477 promise_id, 478 ExtractReceivedVarAndAddRef(dispatcher(), &init_data_type), 479 ExtractReceivedVarAndAddRef(dispatcher(), &init_data), 480 session_type); 481 } 482} 483 484void PPP_ContentDecryptor_Private_Proxy::OnMsgLoadSession( 485 PP_Instance instance, 486 uint32_t promise_id, 487 SerializedVarReceiveInput web_session_id) { 488 if (ppp_decryptor_impl_) { 489 CallWhileUnlocked( 490 ppp_decryptor_impl_->LoadSession, 491 instance, 492 promise_id, 493 ExtractReceivedVarAndAddRef(dispatcher(), &web_session_id)); 494 } 495} 496 497void PPP_ContentDecryptor_Private_Proxy::OnMsgUpdateSession( 498 PP_Instance instance, 499 uint32_t promise_id, 500 SerializedVarReceiveInput web_session_id, 501 SerializedVarReceiveInput response) { 502 if (ppp_decryptor_impl_) { 503 CallWhileUnlocked( 504 ppp_decryptor_impl_->UpdateSession, 505 instance, 506 promise_id, 507 ExtractReceivedVarAndAddRef(dispatcher(), &web_session_id), 508 ExtractReceivedVarAndAddRef(dispatcher(), &response)); 509 } 510} 511 512void PPP_ContentDecryptor_Private_Proxy::OnMsgReleaseSession( 513 PP_Instance instance, 514 uint32_t promise_id, 515 SerializedVarReceiveInput web_session_id) { 516 if (ppp_decryptor_impl_) { 517 CallWhileUnlocked( 518 ppp_decryptor_impl_->ReleaseSession, 519 instance, 520 promise_id, 521 ExtractReceivedVarAndAddRef(dispatcher(), &web_session_id)); 522 } 523} 524 525void PPP_ContentDecryptor_Private_Proxy::OnMsgDecrypt( 526 PP_Instance instance, 527 const PPPDecryptor_Buffer& encrypted_buffer, 528 const std::string& serialized_block_info) { 529 ScopedPPResource plugin_resource( 530 ScopedPPResource::PassRef(), 531 PPB_Buffer_Proxy::AddProxyResource(encrypted_buffer.resource, 532 encrypted_buffer.handle, 533 encrypted_buffer.size)); 534 if (ppp_decryptor_impl_) { 535 PP_EncryptedBlockInfo block_info; 536 if (!DeserializeBlockInfo(serialized_block_info, &block_info)) 537 return; 538 CallWhileUnlocked(ppp_decryptor_impl_->Decrypt, 539 instance, 540 plugin_resource.get(), 541 const_cast<const PP_EncryptedBlockInfo*>(&block_info)); 542 } 543} 544 545void PPP_ContentDecryptor_Private_Proxy::OnMsgInitializeAudioDecoder( 546 PP_Instance instance, 547 const std::string& serialized_decoder_config, 548 const PPPDecryptor_Buffer& extra_data_buffer) { 549 ScopedPPResource plugin_resource; 550 if (extra_data_buffer.size > 0) { 551 plugin_resource = ScopedPPResource( 552 ScopedPPResource::PassRef(), 553 PPB_Buffer_Proxy::AddProxyResource(extra_data_buffer.resource, 554 extra_data_buffer.handle, 555 extra_data_buffer.size)); 556 } 557 558 PP_AudioDecoderConfig decoder_config; 559 if (!DeserializeBlockInfo(serialized_decoder_config, &decoder_config)) 560 return; 561 562 if (ppp_decryptor_impl_) { 563 CallWhileUnlocked( 564 ppp_decryptor_impl_->InitializeAudioDecoder, 565 instance, 566 const_cast<const PP_AudioDecoderConfig*>(&decoder_config), 567 plugin_resource.get()); 568 } 569} 570 571void PPP_ContentDecryptor_Private_Proxy::OnMsgInitializeVideoDecoder( 572 PP_Instance instance, 573 const std::string& serialized_decoder_config, 574 const PPPDecryptor_Buffer& extra_data_buffer) { 575 ScopedPPResource plugin_resource; 576 if (extra_data_buffer.resource.host_resource() != 0) { 577 plugin_resource = ScopedPPResource( 578 ScopedPPResource::PassRef(), 579 PPB_Buffer_Proxy::AddProxyResource(extra_data_buffer.resource, 580 extra_data_buffer.handle, 581 extra_data_buffer.size)); 582 } 583 584 PP_VideoDecoderConfig decoder_config; 585 if (!DeserializeBlockInfo(serialized_decoder_config, &decoder_config)) 586 return; 587 588 if (ppp_decryptor_impl_) { 589 CallWhileUnlocked( 590 ppp_decryptor_impl_->InitializeVideoDecoder, 591 instance, 592 const_cast<const PP_VideoDecoderConfig*>(&decoder_config), 593 plugin_resource.get()); 594 } 595} 596 597void PPP_ContentDecryptor_Private_Proxy::OnMsgDeinitializeDecoder( 598 PP_Instance instance, 599 PP_DecryptorStreamType decoder_type, 600 uint32_t request_id) { 601 if (ppp_decryptor_impl_) { 602 CallWhileUnlocked( 603 ppp_decryptor_impl_->DeinitializeDecoder, 604 instance, 605 decoder_type, 606 request_id); 607 } 608} 609 610void PPP_ContentDecryptor_Private_Proxy::OnMsgResetDecoder( 611 PP_Instance instance, 612 PP_DecryptorStreamType decoder_type, 613 uint32_t request_id) { 614 if (ppp_decryptor_impl_) { 615 CallWhileUnlocked( 616 ppp_decryptor_impl_->ResetDecoder, 617 instance, 618 decoder_type, 619 request_id); 620 } 621} 622 623void PPP_ContentDecryptor_Private_Proxy::OnMsgDecryptAndDecode( 624 PP_Instance instance, 625 PP_DecryptorStreamType decoder_type, 626 const PPPDecryptor_Buffer& encrypted_buffer, 627 const std::string& serialized_block_info) { 628 ScopedPPResource plugin_resource; 629 if (encrypted_buffer.resource.host_resource() != 0) { 630 plugin_resource = ScopedPPResource( 631 ScopedPPResource::PassRef(), 632 PPB_Buffer_Proxy::AddProxyResource(encrypted_buffer.resource, 633 encrypted_buffer.handle, 634 encrypted_buffer.size)); 635 } 636 637 if (ppp_decryptor_impl_) { 638 PP_EncryptedBlockInfo block_info; 639 if (!DeserializeBlockInfo(serialized_block_info, &block_info)) 640 return; 641 CallWhileUnlocked( 642 ppp_decryptor_impl_->DecryptAndDecode, 643 instance, 644 decoder_type, 645 plugin_resource.get(), 646 const_cast<const PP_EncryptedBlockInfo*>(&block_info)); 647 } 648} 649 650} // namespace proxy 651} // namespace ppapi 652