1 // Copyright (c) 2013 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/raw_var_data.h" 6 7 #include <stack> 8 9 #include "base/containers/hash_tables.h" 10 #include "base/stl_util.h" 11 #include "ipc/ipc_message.h" 12 #include "ppapi/proxy/ppapi_param_traits.h" 13 #include "ppapi/shared_impl/array_var.h" 14 #include "ppapi/shared_impl/dictionary_var.h" 15 #include "ppapi/shared_impl/ppapi_globals.h" 16 #include "ppapi/shared_impl/scoped_pp_var.h" 17 #include "ppapi/shared_impl/var.h" 18 #include "ppapi/shared_impl/var_tracker.h" 19 20 using std::make_pair; 21 22 namespace ppapi { 23 namespace proxy { 24 25 namespace { 26 27 // When sending array buffers, if the size is over 256K, we use shared 28 // memory instead of sending the data over IPC. Light testing suggests 29 // shared memory is much faster for 256K and larger messages. 30 static const uint32 kMinimumArrayBufferSizeForShmem = 256 * 1024; 31 static uint32 g_minimum_array_buffer_size_for_shmem = 32 kMinimumArrayBufferSizeForShmem; 33 34 struct StackEntry { 35 StackEntry(PP_Var v, size_t i) : var(v), data_index(i) {} 36 PP_Var var; 37 size_t data_index; 38 }; 39 40 // For a given PP_Var, returns the RawVarData associated with it, or creates a 41 // new one if there is no existing one. The data is appended to |data| if it 42 // is newly created. The index into |data| pointing to the result is returned. 43 // |visited_map| keeps track of RawVarDatas that have already been created. 44 size_t GetOrCreateRawVarData(const PP_Var& var, 45 base::hash_map<int64_t, size_t>* visited_map, 46 ScopedVector<RawVarData>* data) { 47 if (VarTracker::IsVarTypeRefcounted(var.type)) { 48 base::hash_map<int64_t, size_t>::iterator it = visited_map->find( 49 var.value.as_id); 50 if (it != visited_map->end()) { 51 return it->second; 52 } else { 53 data->push_back(RawVarData::Create(var.type)); 54 (*visited_map)[var.value.as_id] = data->size() - 1; 55 } 56 } else { 57 data->push_back(RawVarData::Create(var.type)); 58 } 59 return data->size() - 1; 60 } 61 62 bool CanHaveChildren(PP_Var var) { 63 return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY; 64 } 65 66 } // namespace 67 68 // RawVarDataGraph ------------------------------------------------------------ 69 RawVarDataGraph::RawVarDataGraph() { 70 } 71 72 RawVarDataGraph::~RawVarDataGraph() { 73 } 74 75 // This function uses a stack-based DFS search to traverse the var graph. Each 76 // iteration, the top node on the stack examined. If the node has not been 77 // visited yet (i.e. !initialized()) then it is added to the list of 78 // |parent_ids| which contains all of the nodes on the path from the start node 79 // to the current node. Each of that nodes children are examined. If they appear 80 // in the list of |parent_ids| it means we have a cycle and we return NULL. 81 // Otherwise, if they haven't been visited yet we add them to the stack, If the 82 // node at the top of the stack has already been visited, then we pop it off the 83 // stack and erase it from |parent_ids|. 84 // static 85 scoped_ptr<RawVarDataGraph> RawVarDataGraph::Create(const PP_Var& var, 86 PP_Instance instance) { 87 scoped_ptr<RawVarDataGraph> graph(new RawVarDataGraph); 88 // Map of |var.value.as_id| to a RawVarData index in RawVarDataGraph. 89 base::hash_map<int64_t, size_t> visited_map; 90 base::hash_set<int64_t> parent_ids; 91 92 std::stack<StackEntry> stack; 93 stack.push(StackEntry(var, GetOrCreateRawVarData(var, &visited_map, 94 &graph->data_))); 95 96 while (!stack.empty()) { 97 PP_Var current_var = stack.top().var; 98 RawVarData* current_var_data = graph->data_[stack.top().data_index]; 99 100 if (current_var_data->initialized()) { 101 stack.pop(); 102 if (CanHaveChildren(current_var)) 103 parent_ids.erase(current_var.value.as_id); 104 continue; 105 } 106 107 if (CanHaveChildren(current_var)) 108 parent_ids.insert(current_var.value.as_id); 109 if (!current_var_data->Init(current_var, instance)) { 110 NOTREACHED(); 111 return scoped_ptr<RawVarDataGraph>(); 112 } 113 114 // Add child nodes to the stack. 115 if (current_var.type == PP_VARTYPE_ARRAY) { 116 ArrayVar* array_var = ArrayVar::FromPPVar(current_var); 117 if (!array_var) { 118 NOTREACHED(); 119 return scoped_ptr<RawVarDataGraph>(); 120 } 121 for (ArrayVar::ElementVector::const_iterator iter = 122 array_var->elements().begin(); 123 iter != array_var->elements().end(); 124 ++iter) { 125 const PP_Var& child = iter->get(); 126 // If a child of this node is already in parent_ids, we have a cycle so 127 // we just return null. 128 if (CanHaveChildren(child) && parent_ids.count(child.value.as_id) != 0) 129 return scoped_ptr<RawVarDataGraph>(); 130 size_t child_id = GetOrCreateRawVarData(child, &visited_map, 131 &graph->data_); 132 static_cast<ArrayRawVarData*>(current_var_data)->AddChild(child_id); 133 if (!graph->data_[child_id]->initialized()) 134 stack.push(StackEntry(child, child_id)); 135 } 136 } else if (current_var.type == PP_VARTYPE_DICTIONARY) { 137 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var); 138 if (!dict_var) { 139 NOTREACHED(); 140 return scoped_ptr<RawVarDataGraph>(); 141 } 142 for (DictionaryVar::KeyValueMap::const_iterator iter = 143 dict_var->key_value_map().begin(); 144 iter != dict_var->key_value_map().end(); 145 ++iter) { 146 const PP_Var& child = iter->second.get(); 147 if (CanHaveChildren(child) && parent_ids.count(child.value.as_id) != 0) 148 return scoped_ptr<RawVarDataGraph>(); 149 size_t child_id = GetOrCreateRawVarData(child, &visited_map, 150 &graph->data_); 151 static_cast<DictionaryRawVarData*>( 152 current_var_data)->AddChild(iter->first, child_id); 153 if (!graph->data_[child_id]->initialized()) 154 stack.push(StackEntry(child, child_id)); 155 } 156 } 157 } 158 return graph.Pass(); 159 } 160 161 PP_Var RawVarDataGraph::CreatePPVar(PP_Instance instance) { 162 // Create and initialize each node in the graph. 163 std::vector<PP_Var> graph; 164 for (size_t i = 0; i < data_.size(); ++i) 165 graph.push_back(data_[i]->CreatePPVar(instance)); 166 for (size_t i = 0; i < data_.size(); ++i) 167 data_[i]->PopulatePPVar(graph[i], graph); 168 // Everything except the root will have one extra ref. Remove that ref. 169 for (size_t i = 1; i < data_.size(); ++i) 170 ScopedPPVar(ScopedPPVar::PassRef(), graph[i]); 171 // The first element is the root. 172 return graph[0]; 173 } 174 175 void RawVarDataGraph::Write(IPC::Message* m, 176 const HandleWriter& handle_writer) { 177 // Write the size, followed by each node in the graph. 178 m->WriteUInt32(static_cast<uint32_t>(data_.size())); 179 for (size_t i = 0; i < data_.size(); ++i) { 180 m->WriteInt(data_[i]->Type()); 181 data_[i]->Write(m, handle_writer); 182 } 183 } 184 185 // static 186 scoped_ptr<RawVarDataGraph> RawVarDataGraph::Read(const IPC::Message* m, 187 PickleIterator* iter) { 188 scoped_ptr<RawVarDataGraph> result(new RawVarDataGraph); 189 uint32_t size = 0; 190 if (!m->ReadUInt32(iter, &size)) 191 return scoped_ptr<RawVarDataGraph>(); 192 for (uint32_t i = 0; i < size; ++i) { 193 int32_t type; 194 if (!m->ReadInt(iter, &type)) 195 return scoped_ptr<RawVarDataGraph>(); 196 PP_VarType var_type = static_cast<PP_VarType>(type); 197 result->data_.push_back(RawVarData::Create(var_type)); 198 if (!result->data_.back()->Read(var_type, m, iter)) 199 return scoped_ptr<RawVarDataGraph>(); 200 } 201 return result.Pass(); 202 } 203 204 std::vector<SerializedHandle*> RawVarDataGraph::GetHandles() { 205 std::vector<SerializedHandle*> result; 206 for (size_t i = 0; i < data_.size(); ++i) { 207 SerializedHandle* handle = data_[i]->GetHandle(); 208 if (handle) 209 result.push_back(handle); 210 } 211 return result; 212 } 213 214 // static 215 void RawVarDataGraph::SetMinimumArrayBufferSizeForShmemForTest( 216 uint32 threshold) { 217 if (threshold == 0) 218 g_minimum_array_buffer_size_for_shmem = kMinimumArrayBufferSizeForShmem; 219 else 220 g_minimum_array_buffer_size_for_shmem = threshold; 221 } 222 223 // RawVarData ------------------------------------------------------------------ 224 225 // static 226 RawVarData* RawVarData::Create(PP_VarType type) { 227 switch (type) { 228 case PP_VARTYPE_UNDEFINED: 229 case PP_VARTYPE_NULL: 230 case PP_VARTYPE_BOOL: 231 case PP_VARTYPE_INT32: 232 case PP_VARTYPE_DOUBLE: 233 case PP_VARTYPE_OBJECT: 234 return new BasicRawVarData(); 235 case PP_VARTYPE_STRING: 236 return new StringRawVarData(); 237 case PP_VARTYPE_ARRAY_BUFFER: 238 return new ArrayBufferRawVarData(); 239 case PP_VARTYPE_ARRAY: 240 return new ArrayRawVarData(); 241 case PP_VARTYPE_DICTIONARY: 242 return new DictionaryRawVarData(); 243 } 244 NOTREACHED(); 245 return NULL; 246 } 247 248 RawVarData::RawVarData() : initialized_(false) { 249 } 250 251 RawVarData::~RawVarData() { 252 } 253 254 SerializedHandle* RawVarData::GetHandle() { 255 return NULL; 256 } 257 258 // BasicRawVarData ------------------------------------------------------------- 259 BasicRawVarData::BasicRawVarData() { 260 } 261 262 BasicRawVarData::~BasicRawVarData() { 263 } 264 265 PP_VarType BasicRawVarData::Type() { 266 return var_.type; 267 } 268 269 bool BasicRawVarData::Init(const PP_Var& var, PP_Instance /*instance*/) { 270 var_ = var; 271 initialized_ = true; 272 return true; 273 } 274 275 PP_Var BasicRawVarData::CreatePPVar(PP_Instance instance) { 276 return var_; 277 } 278 279 void BasicRawVarData::PopulatePPVar(const PP_Var& var, 280 const std::vector<PP_Var>& graph) { 281 } 282 283 void BasicRawVarData::Write( 284 IPC::Message* m, 285 const HandleWriter& handle_writer) { 286 switch (var_.type) { 287 case PP_VARTYPE_UNDEFINED: 288 case PP_VARTYPE_NULL: 289 // These don't need any data associated with them other than the type we 290 // just serialized. 291 break; 292 case PP_VARTYPE_BOOL: 293 m->WriteBool(PP_ToBool(var_.value.as_bool)); 294 break; 295 case PP_VARTYPE_INT32: 296 m->WriteInt(var_.value.as_int); 297 break; 298 case PP_VARTYPE_DOUBLE: 299 IPC::ParamTraits<double>::Write(m, var_.value.as_double); 300 break; 301 case PP_VARTYPE_OBJECT: 302 m->WriteInt64(var_.value.as_id); 303 break; 304 default: 305 NOTREACHED(); 306 break; 307 } 308 } 309 310 bool BasicRawVarData::Read(PP_VarType type, 311 const IPC::Message* m, 312 PickleIterator* iter) { 313 PP_Var result; 314 result.type = type; 315 switch (type) { 316 case PP_VARTYPE_UNDEFINED: 317 case PP_VARTYPE_NULL: 318 // These don't have any data associated with them other than the type we 319 // just serialized. 320 break; 321 case PP_VARTYPE_BOOL: { 322 bool bool_value; 323 if (!m->ReadBool(iter, &bool_value)) 324 return false; 325 result.value.as_bool = PP_FromBool(bool_value); 326 break; 327 } 328 case PP_VARTYPE_INT32: 329 if (!m->ReadInt(iter, &result.value.as_int)) 330 return false; 331 break; 332 case PP_VARTYPE_DOUBLE: 333 if (!IPC::ParamTraits<double>::Read(m, iter, &result.value.as_double)) 334 return false; 335 break; 336 case PP_VARTYPE_OBJECT: 337 if (!m->ReadInt64(iter, &result.value.as_id)) 338 return false; 339 break; 340 default: 341 NOTREACHED(); 342 return false; 343 } 344 var_ = result; 345 return true; 346 } 347 348 // StringRawVarData ------------------------------------------------------------ 349 StringRawVarData::StringRawVarData() { 350 } 351 352 StringRawVarData::~StringRawVarData() { 353 } 354 355 PP_VarType StringRawVarData::Type() { 356 return PP_VARTYPE_STRING; 357 } 358 359 bool StringRawVarData::Init(const PP_Var& var, PP_Instance /*instance*/) { 360 DCHECK(var.type == PP_VARTYPE_STRING); 361 StringVar* string_var = StringVar::FromPPVar(var); 362 if (!string_var) 363 return false; 364 data_ = string_var->value(); 365 initialized_ = true; 366 return true; 367 } 368 369 PP_Var StringRawVarData::CreatePPVar(PP_Instance instance) { 370 return StringVar::SwapValidatedUTF8StringIntoPPVar(&data_); 371 } 372 373 void StringRawVarData::PopulatePPVar(const PP_Var& var, 374 const std::vector<PP_Var>& graph) { 375 } 376 377 void StringRawVarData::Write(IPC::Message* m, 378 const HandleWriter& handle_writer) { 379 m->WriteString(data_); 380 } 381 382 bool StringRawVarData::Read(PP_VarType type, 383 const IPC::Message* m, 384 PickleIterator* iter) { 385 if (!m->ReadString(iter, &data_)) 386 return false; 387 return true; 388 } 389 390 // ArrayBufferRawVarData ------------------------------------------------------- 391 ArrayBufferRawVarData::ArrayBufferRawVarData() { 392 } 393 394 ArrayBufferRawVarData::~ArrayBufferRawVarData() { 395 } 396 397 PP_VarType ArrayBufferRawVarData::Type() { 398 return PP_VARTYPE_ARRAY_BUFFER; 399 } 400 401 bool ArrayBufferRawVarData::Init(const PP_Var& var, 402 PP_Instance instance) { 403 DCHECK(var.type == PP_VARTYPE_ARRAY_BUFFER); 404 ArrayBufferVar* buffer_var = ArrayBufferVar::FromPPVar(var); 405 if (!buffer_var) 406 return false; 407 bool using_shmem = false; 408 if (buffer_var->ByteLength() >= g_minimum_array_buffer_size_for_shmem && 409 instance != 0) { 410 int host_handle_id; 411 base::SharedMemoryHandle plugin_handle; 412 using_shmem = buffer_var->CopyToNewShmem(instance, 413 &host_handle_id, 414 &plugin_handle); 415 if (using_shmem) { 416 if (host_handle_id != -1) { 417 DCHECK(!base::SharedMemory::IsHandleValid(plugin_handle)); 418 DCHECK(PpapiGlobals::Get()->IsPluginGlobals()); 419 type_ = ARRAY_BUFFER_SHMEM_HOST; 420 host_shm_handle_id_ = host_handle_id; 421 } else { 422 DCHECK(base::SharedMemory::IsHandleValid(plugin_handle)); 423 DCHECK(PpapiGlobals::Get()->IsHostGlobals()); 424 type_ = ARRAY_BUFFER_SHMEM_PLUGIN; 425 plugin_shm_handle_ = SerializedHandle(plugin_handle, 426 buffer_var->ByteLength()); 427 } 428 } 429 } 430 if (!using_shmem) { 431 type_ = ARRAY_BUFFER_NO_SHMEM; 432 data_ = std::string(static_cast<const char*>(buffer_var->Map()), 433 buffer_var->ByteLength()); 434 } 435 initialized_ = true; 436 return true; 437 } 438 439 PP_Var ArrayBufferRawVarData::CreatePPVar(PP_Instance instance) { 440 PP_Var result = PP_MakeUndefined(); 441 switch (type_) { 442 case ARRAY_BUFFER_SHMEM_HOST: { 443 base::SharedMemoryHandle host_handle; 444 uint32 size_in_bytes; 445 bool ok = PpapiGlobals::Get()->GetVarTracker()-> 446 StopTrackingSharedMemoryHandle(host_shm_handle_id_, 447 instance, 448 &host_handle, 449 &size_in_bytes); 450 if (ok) { 451 result = PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar( 452 size_in_bytes, host_handle); 453 } else { 454 LOG(ERROR) << "Couldn't find array buffer id: " << host_shm_handle_id_; 455 return PP_MakeUndefined(); 456 } 457 break; 458 } 459 case ARRAY_BUFFER_SHMEM_PLUGIN: { 460 result = PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar( 461 plugin_shm_handle_.size(), 462 plugin_shm_handle_.shmem()); 463 break; 464 } 465 case ARRAY_BUFFER_NO_SHMEM: { 466 result = PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar( 467 static_cast<uint32>(data_.size()), data_.data()); 468 break; 469 } 470 default: 471 NOTREACHED(); 472 return PP_MakeUndefined(); 473 } 474 DCHECK(result.type == PP_VARTYPE_ARRAY_BUFFER); 475 return result; 476 } 477 478 void ArrayBufferRawVarData::PopulatePPVar(const PP_Var& var, 479 const std::vector<PP_Var>& graph) { 480 } 481 482 void ArrayBufferRawVarData::Write( 483 IPC::Message* m, 484 const HandleWriter& handle_writer) { 485 m->WriteInt(type_); 486 switch (type_) { 487 case ARRAY_BUFFER_SHMEM_HOST: 488 m->WriteInt(host_shm_handle_id_); 489 break; 490 case ARRAY_BUFFER_SHMEM_PLUGIN: 491 handle_writer.Run(m, plugin_shm_handle_); 492 break; 493 case ARRAY_BUFFER_NO_SHMEM: 494 m->WriteString(data_); 495 break; 496 } 497 } 498 499 bool ArrayBufferRawVarData::Read(PP_VarType type, 500 const IPC::Message* m, 501 PickleIterator* iter) { 502 int shmem_type; 503 if (!m->ReadInt(iter, &shmem_type)) 504 return false; 505 type_ = static_cast<ShmemType>(shmem_type); 506 switch (type_) { 507 case ARRAY_BUFFER_SHMEM_HOST: 508 if (!m->ReadInt(iter, &host_shm_handle_id_)) 509 return false; 510 break; 511 case ARRAY_BUFFER_SHMEM_PLUGIN: 512 if (!IPC::ParamTraits<SerializedHandle>::Read( 513 m, iter, &plugin_shm_handle_)) { 514 return false; 515 } 516 break; 517 case ARRAY_BUFFER_NO_SHMEM: 518 if (!m->ReadString(iter, &data_)) 519 return false; 520 break; 521 default: 522 // We read an invalid ID. 523 NOTREACHED(); 524 return false; 525 } 526 return true; 527 } 528 529 SerializedHandle* ArrayBufferRawVarData::GetHandle() { 530 if (type_ == ARRAY_BUFFER_SHMEM_PLUGIN && plugin_shm_handle_.size() != 0) 531 return &plugin_shm_handle_; 532 return NULL; 533 } 534 535 // ArrayRawVarData ------------------------------------------------------------- 536 ArrayRawVarData::ArrayRawVarData() { 537 } 538 539 ArrayRawVarData::~ArrayRawVarData() { 540 } 541 542 void ArrayRawVarData::AddChild(size_t element) { 543 children_.push_back(element); 544 } 545 546 PP_VarType ArrayRawVarData::Type() { 547 return PP_VARTYPE_ARRAY; 548 } 549 550 bool ArrayRawVarData::Init(const PP_Var& var, PP_Instance /*instance*/) { 551 initialized_ = true; 552 DCHECK(var.type == PP_VARTYPE_ARRAY); 553 initialized_ = true; 554 return true; 555 } 556 557 PP_Var ArrayRawVarData::CreatePPVar(PP_Instance instance) { 558 return (new ArrayVar())->GetPPVar(); 559 } 560 561 void ArrayRawVarData::PopulatePPVar(const PP_Var& var, 562 const std::vector<PP_Var>& graph) { 563 if (var.type != PP_VARTYPE_ARRAY) { 564 NOTREACHED(); 565 return; 566 } 567 ArrayVar* array_var = ArrayVar::FromPPVar(var); 568 DCHECK(array_var->elements().empty()); 569 for (size_t i = 0; i < children_.size(); ++i) 570 array_var->elements().push_back(ScopedPPVar(graph[children_[i]])); 571 } 572 573 void ArrayRawVarData::Write(IPC::Message* m, 574 const HandleWriter& handle_writer) { 575 m->WriteUInt32(static_cast<uint32_t>(children_.size())); 576 for (size_t i = 0; i < children_.size(); ++i) 577 m->WriteUInt32(static_cast<uint32_t>(children_[i])); 578 } 579 580 bool ArrayRawVarData::Read(PP_VarType type, 581 const IPC::Message* m, 582 PickleIterator* iter) { 583 uint32_t size; 584 if (!m->ReadUInt32(iter, &size)) 585 return false; 586 for (uint32_t i = 0; i < size; ++i) { 587 uint32_t index; 588 if (!m->ReadUInt32(iter, &index)) 589 return false; 590 children_.push_back(index); 591 } 592 return true; 593 } 594 595 // DictionaryRawVarData -------------------------------------------------------- 596 DictionaryRawVarData::DictionaryRawVarData() { 597 } 598 599 DictionaryRawVarData::~DictionaryRawVarData() { 600 } 601 602 void DictionaryRawVarData::AddChild(const std::string& key, 603 size_t value) { 604 children_.push_back(make_pair(key, value)); 605 } 606 607 PP_VarType DictionaryRawVarData::Type() { 608 return PP_VARTYPE_DICTIONARY; 609 } 610 611 bool DictionaryRawVarData::Init(const PP_Var& var, PP_Instance /*instance*/) { 612 DCHECK(var.type == PP_VARTYPE_DICTIONARY); 613 initialized_ = true; 614 return true; 615 } 616 617 PP_Var DictionaryRawVarData::CreatePPVar(PP_Instance instance) { 618 return (new DictionaryVar())->GetPPVar(); 619 } 620 621 void DictionaryRawVarData::PopulatePPVar(const PP_Var& var, 622 const std::vector<PP_Var>& graph) { 623 if (var.type != PP_VARTYPE_DICTIONARY) { 624 NOTREACHED(); 625 return; 626 } 627 DictionaryVar* dictionary_var = DictionaryVar::FromPPVar(var); 628 DCHECK(dictionary_var->key_value_map().empty()); 629 for (size_t i = 0; i < children_.size(); ++i) { 630 bool success = dictionary_var->SetWithStringKey(children_[i].first, 631 graph[children_[i].second]); 632 DCHECK(success); 633 } 634 } 635 636 void DictionaryRawVarData::Write( 637 IPC::Message* m, 638 const HandleWriter& handle_writer) { 639 m->WriteUInt32(static_cast<uint32_t>(children_.size())); 640 for (size_t i = 0; i < children_.size(); ++i) { 641 m->WriteString(children_[i].first); 642 m->WriteUInt32(static_cast<uint32_t>(children_[i].second)); 643 } 644 } 645 646 bool DictionaryRawVarData::Read(PP_VarType type, 647 const IPC::Message* m, 648 PickleIterator* iter) { 649 uint32_t size; 650 if (!m->ReadUInt32(iter, &size)) 651 return false; 652 for (uint32_t i = 0; i < size; ++i) { 653 std::string key; 654 uint32_t value; 655 if (!m->ReadString(iter, &key)) 656 return false; 657 if (!m->ReadUInt32(iter, &value)) 658 return false; 659 children_.push_back(make_pair(key, value)); 660 } 661 return true; 662 } 663 664 } // namespace proxy 665 } // namespace ppapi 666