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 "content/renderer/pepper/message_channel.h"
6
7 #include <cstdlib>
8 #include <string>
9
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "content/renderer/pepper/host_array_buffer_var.h"
14 #include "content/renderer/pepper/npapi_glue.h"
15 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
16 #include "content/renderer/pepper/plugin_module.h"
17 #include "content/renderer/pepper/v8_var_converter.h"
18 #include "ppapi/shared_impl/ppapi_globals.h"
19 #include "ppapi/shared_impl/scoped_pp_var.h"
20 #include "ppapi/shared_impl/var.h"
21 #include "ppapi/shared_impl/var_tracker.h"
22 #include "third_party/WebKit/public/web/WebBindings.h"
23 #include "third_party/WebKit/public/web/WebDocument.h"
24 #include "third_party/WebKit/public/web/WebDOMMessageEvent.h"
25 #include "third_party/WebKit/public/web/WebElement.h"
26 #include "third_party/WebKit/public/web/WebLocalFrame.h"
27 #include "third_party/WebKit/public/web/WebNode.h"
28 #include "third_party/WebKit/public/web/WebPluginContainer.h"
29 #include "third_party/WebKit/public/web/WebSerializedScriptValue.h"
30 #include "v8/include/v8.h"
31
32 using ppapi::ArrayBufferVar;
33 using ppapi::PpapiGlobals;
34 using ppapi::ScopedPPVar;
35 using ppapi::StringVar;
36 using blink::WebBindings;
37 using blink::WebElement;
38 using blink::WebDOMEvent;
39 using blink::WebDOMMessageEvent;
40 using blink::WebPluginContainer;
41 using blink::WebSerializedScriptValue;
42
43 namespace content {
44
45 namespace {
46
47 const char kPostMessage[] = "postMessage";
48 const char kPostMessageAndAwaitResponse[] = "postMessageAndAwaitResponse";
49 const char kV8ToVarConversionError[] =
50 "Failed to convert a PostMessage "
51 "argument from a JavaScript value to a PP_Var. It may have cycles or be of "
52 "an unsupported type.";
53 const char kVarToV8ConversionError[] =
54 "Failed to convert a PostMessage "
55 "argument from a PP_Var to a Javascript value. It may have cycles or be of "
56 "an unsupported type.";
57
58 // Helper function to get the MessageChannel that is associated with an
59 // NPObject*.
ToMessageChannel(NPObject * object)60 MessageChannel* ToMessageChannel(NPObject* object) {
61 return static_cast<MessageChannel::MessageChannelNPObject*>(object)
62 ->message_channel.get();
63 }
64
ToPassThroughObject(NPObject * object)65 NPObject* ToPassThroughObject(NPObject* object) {
66 MessageChannel* channel = ToMessageChannel(object);
67 return channel ? channel->passthrough_object() : NULL;
68 }
69
70 // Return true iff |identifier| is equal to |string|.
IdentifierIs(NPIdentifier identifier,const char string[])71 bool IdentifierIs(NPIdentifier identifier, const char string[]) {
72 return WebBindings::getStringIdentifier(string) == identifier;
73 }
74
HasDevChannelPermission(NPObject * channel_object)75 bool HasDevChannelPermission(NPObject* channel_object) {
76 MessageChannel* channel = ToMessageChannel(channel_object);
77 if (!channel)
78 return false;
79 return channel->instance()->module()->permissions().HasPermission(
80 ppapi::PERMISSION_DEV_CHANNEL);
81 }
82
83 //------------------------------------------------------------------------------
84 // Implementations of NPClass functions. These are here to:
85 // - Implement postMessage behavior.
86 // - Forward calls to the 'passthrough' object to allow backwards-compatibility
87 // with GetInstanceObject() objects.
88 //------------------------------------------------------------------------------
MessageChannelAllocate(NPP npp,NPClass * the_class)89 NPObject* MessageChannelAllocate(NPP npp, NPClass* the_class) {
90 return new MessageChannel::MessageChannelNPObject;
91 }
92
MessageChannelDeallocate(NPObject * object)93 void MessageChannelDeallocate(NPObject* object) {
94 MessageChannel::MessageChannelNPObject* instance =
95 static_cast<MessageChannel::MessageChannelNPObject*>(object);
96 delete instance;
97 }
98
MessageChannelHasMethod(NPObject * np_obj,NPIdentifier name)99 bool MessageChannelHasMethod(NPObject* np_obj, NPIdentifier name) {
100 if (!np_obj)
101 return false;
102
103 if (IdentifierIs(name, kPostMessage))
104 return true;
105 if (IdentifierIs(name, kPostMessageAndAwaitResponse) &&
106 HasDevChannelPermission(np_obj)) {
107 return true;
108 }
109 // Other method names we will pass to the passthrough object, if we have one.
110 NPObject* passthrough = ToPassThroughObject(np_obj);
111 if (passthrough)
112 return WebBindings::hasMethod(NULL, passthrough, name);
113 return false;
114 }
115
MessageChannelInvoke(NPObject * np_obj,NPIdentifier name,const NPVariant * args,uint32 arg_count,NPVariant * result)116 bool MessageChannelInvoke(NPObject* np_obj,
117 NPIdentifier name,
118 const NPVariant* args,
119 uint32 arg_count,
120 NPVariant* result) {
121 if (!np_obj)
122 return false;
123
124 MessageChannel* message_channel = ToMessageChannel(np_obj);
125 if (!message_channel)
126 return false;
127
128 // Check to see if we should handle this function ourselves.
129 if (IdentifierIs(name, kPostMessage) && (arg_count == 1)) {
130 message_channel->PostMessageToNative(&args[0]);
131 return true;
132 } else if (IdentifierIs(name, kPostMessageAndAwaitResponse) &&
133 (arg_count == 1) &&
134 HasDevChannelPermission(np_obj)) {
135 message_channel->PostBlockingMessageToNative(&args[0], result);
136 return true;
137 }
138
139 // Other method calls we will pass to the passthrough object, if we have one.
140 NPObject* passthrough = ToPassThroughObject(np_obj);
141 if (passthrough) {
142 return WebBindings::invoke(
143 NULL, passthrough, name, args, arg_count, result);
144 }
145 return false;
146 }
147
MessageChannelInvokeDefault(NPObject * np_obj,const NPVariant * args,uint32 arg_count,NPVariant * result)148 bool MessageChannelInvokeDefault(NPObject* np_obj,
149 const NPVariant* args,
150 uint32 arg_count,
151 NPVariant* result) {
152 if (!np_obj)
153 return false;
154
155 // Invoke on the passthrough object, if we have one.
156 NPObject* passthrough = ToPassThroughObject(np_obj);
157 if (passthrough) {
158 return WebBindings::invokeDefault(
159 NULL, passthrough, args, arg_count, result);
160 }
161 return false;
162 }
163
MessageChannelHasProperty(NPObject * np_obj,NPIdentifier name)164 bool MessageChannelHasProperty(NPObject* np_obj, NPIdentifier name) {
165 if (!np_obj)
166 return false;
167
168 MessageChannel* message_channel = ToMessageChannel(np_obj);
169 if (message_channel) {
170 if (message_channel->GetReadOnlyProperty(name, NULL))
171 return true;
172 }
173
174 // Invoke on the passthrough object, if we have one.
175 NPObject* passthrough = ToPassThroughObject(np_obj);
176 if (passthrough)
177 return WebBindings::hasProperty(NULL, passthrough, name);
178 return false;
179 }
180
MessageChannelGetProperty(NPObject * np_obj,NPIdentifier name,NPVariant * result)181 bool MessageChannelGetProperty(NPObject* np_obj,
182 NPIdentifier name,
183 NPVariant* result) {
184 if (!np_obj)
185 return false;
186
187 // Don't allow getting the postMessage functions.
188 if (IdentifierIs(name, kPostMessage))
189 return false;
190 if (IdentifierIs(name, kPostMessageAndAwaitResponse) &&
191 HasDevChannelPermission(np_obj)) {
192 return false;
193 }
194 MessageChannel* message_channel = ToMessageChannel(np_obj);
195 if (message_channel) {
196 if (message_channel->GetReadOnlyProperty(name, result))
197 return true;
198 }
199
200 // Invoke on the passthrough object, if we have one.
201 NPObject* passthrough = ToPassThroughObject(np_obj);
202 if (passthrough)
203 return WebBindings::getProperty(NULL, passthrough, name, result);
204 return false;
205 }
206
MessageChannelSetProperty(NPObject * np_obj,NPIdentifier name,const NPVariant * variant)207 bool MessageChannelSetProperty(NPObject* np_obj,
208 NPIdentifier name,
209 const NPVariant* variant) {
210 if (!np_obj)
211 return false;
212
213 // Don't allow setting the postMessage functions.
214 if (IdentifierIs(name, kPostMessage))
215 return false;
216 if (IdentifierIs(name, kPostMessageAndAwaitResponse) &&
217 HasDevChannelPermission(np_obj)) {
218 return false;
219 }
220 // Invoke on the passthrough object, if we have one.
221 NPObject* passthrough = ToPassThroughObject(np_obj);
222 if (passthrough)
223 return WebBindings::setProperty(NULL, passthrough, name, variant);
224 return false;
225 }
226
MessageChannelEnumerate(NPObject * np_obj,NPIdentifier ** value,uint32_t * count)227 bool MessageChannelEnumerate(NPObject* np_obj,
228 NPIdentifier** value,
229 uint32_t* count) {
230 if (!np_obj)
231 return false;
232
233 // Invoke on the passthrough object, if we have one, to enumerate its
234 // properties.
235 NPObject* passthrough = ToPassThroughObject(np_obj);
236 if (passthrough) {
237 bool success = WebBindings::enumerate(NULL, passthrough, value, count);
238 if (success) {
239 // Add postMessage to the list and return it.
240 if (std::numeric_limits<size_t>::max() / sizeof(NPIdentifier) <=
241 static_cast<size_t>(*count) + 1) // Else, "always false" x64 warning.
242 return false;
243 NPIdentifier* new_array = static_cast<NPIdentifier*>(
244 std::malloc(sizeof(NPIdentifier) * (*count + 1)));
245 std::memcpy(new_array, *value, sizeof(NPIdentifier) * (*count));
246 new_array[*count] = WebBindings::getStringIdentifier(kPostMessage);
247 std::free(*value);
248 *value = new_array;
249 ++(*count);
250 return true;
251 }
252 }
253
254 // Otherwise, build an array that includes only postMessage.
255 *value = static_cast<NPIdentifier*>(malloc(sizeof(NPIdentifier)));
256 (*value)[0] = WebBindings::getStringIdentifier(kPostMessage);
257 *count = 1;
258 return true;
259 }
260
261 NPClass message_channel_class = {
262 NP_CLASS_STRUCT_VERSION, &MessageChannelAllocate,
263 &MessageChannelDeallocate, NULL,
264 &MessageChannelHasMethod, &MessageChannelInvoke,
265 &MessageChannelInvokeDefault, &MessageChannelHasProperty,
266 &MessageChannelGetProperty, &MessageChannelSetProperty,
267 NULL, &MessageChannelEnumerate, };
268
269 } // namespace
270
271 // MessageChannel --------------------------------------------------------------
272 struct MessageChannel::VarConversionResult {
VarConversionResultcontent::MessageChannel::VarConversionResult273 VarConversionResult() : success_(false), conversion_completed_(false) {}
ConversionCompletedcontent::MessageChannel::VarConversionResult274 void ConversionCompleted(const ScopedPPVar& var,
275 bool success) {
276 conversion_completed_ = true;
277 var_ = var;
278 success_ = success;
279 }
varcontent::MessageChannel::VarConversionResult280 const ScopedPPVar& var() const { return var_; }
successcontent::MessageChannel::VarConversionResult281 bool success() const { return success_; }
conversion_completedcontent::MessageChannel::VarConversionResult282 bool conversion_completed() const { return conversion_completed_; }
283
284 private:
285 ScopedPPVar var_;
286 bool success_;
287 bool conversion_completed_;
288 };
289
MessageChannelNPObject()290 MessageChannel::MessageChannelNPObject::MessageChannelNPObject() {}
291
~MessageChannelNPObject()292 MessageChannel::MessageChannelNPObject::~MessageChannelNPObject() {}
293
MessageChannel(PepperPluginInstanceImpl * instance)294 MessageChannel::MessageChannel(PepperPluginInstanceImpl* instance)
295 : instance_(instance),
296 passthrough_object_(NULL),
297 np_object_(NULL),
298 early_message_queue_state_(QUEUE_MESSAGES),
299 weak_ptr_factory_(this) {
300 // Now create an NPObject for receiving calls to postMessage. This sets the
301 // reference count to 1. We release it in the destructor.
302 NPObject* obj = WebBindings::createObject(instance_->instanceNPP(),
303 &message_channel_class);
304 DCHECK(obj);
305 np_object_ = static_cast<MessageChannel::MessageChannelNPObject*>(obj);
306 np_object_->message_channel = weak_ptr_factory_.GetWeakPtr();
307 }
308
EnqueuePluginMessage(const NPVariant * variant)309 void MessageChannel::EnqueuePluginMessage(const NPVariant* variant) {
310 plugin_message_queue_.push_back(VarConversionResult());
311 if (variant->type == NPVariantType_Object) {
312 // Convert NPVariantType_Object in to an appropriate PP_Var like Dictionary,
313 // Array, etc. Note NPVariantToVar would convert to an "Object" PP_Var,
314 // which we don't support for Messaging.
315
316 // Calling WebBindings::toV8Value creates a wrapper around NPVariant so it
317 // won't result in a deep copy.
318 v8::Handle<v8::Value> v8_value = WebBindings::toV8Value(variant);
319 V8VarConverter v8_var_converter(instance_->pp_instance());
320 V8VarConverter::VarResult conversion_result =
321 v8_var_converter.FromV8Value(
322 v8_value,
323 v8::Isolate::GetCurrent()->GetCurrentContext(),
324 base::Bind(&MessageChannel::FromV8ValueComplete,
325 weak_ptr_factory_.GetWeakPtr(),
326 &plugin_message_queue_.back()));
327 if (conversion_result.completed_synchronously) {
328 plugin_message_queue_.back().ConversionCompleted(
329 conversion_result.var,
330 conversion_result.success);
331 }
332 } else {
333 plugin_message_queue_.back().ConversionCompleted(
334 ScopedPPVar(ScopedPPVar::PassRef(),
335 NPVariantToPPVar(instance(), variant)),
336 true);
337 DCHECK(plugin_message_queue_.back().var().get().type != PP_VARTYPE_OBJECT);
338 }
339 }
340
PostMessageToJavaScript(PP_Var message_data)341 void MessageChannel::PostMessageToJavaScript(PP_Var message_data) {
342 v8::HandleScope scope(v8::Isolate::GetCurrent());
343
344 // Because V8 is probably not on the stack for Native->JS calls, we need to
345 // enter the appropriate context for the plugin.
346 WebPluginContainer* container = instance_->container();
347 // It's possible that container() is NULL if the plugin has been removed from
348 // the DOM (but the PluginInstance is not destroyed yet).
349 if (!container)
350 return;
351
352 v8::Local<v8::Context> context =
353 container->element().document().frame()->mainWorldScriptContext();
354 // If the page is being destroyed, the context may be empty.
355 if (context.IsEmpty())
356 return;
357 v8::Context::Scope context_scope(context);
358
359 v8::Handle<v8::Value> v8_val;
360 if (!V8VarConverter(instance_->pp_instance())
361 .ToV8Value(message_data, context, &v8_val)) {
362 PpapiGlobals::Get()->LogWithSource(instance_->pp_instance(),
363 PP_LOGLEVEL_ERROR,
364 std::string(),
365 kVarToV8ConversionError);
366 return;
367 }
368
369 WebSerializedScriptValue serialized_val =
370 WebSerializedScriptValue::serialize(v8_val);
371
372 if (early_message_queue_state_ != SEND_DIRECTLY) {
373 // We can't just PostTask here; the messages would arrive out of
374 // order. Instead, we queue them up until we're ready to post
375 // them.
376 early_message_queue_.push_back(serialized_val);
377 } else {
378 // The proxy sent an asynchronous message, so the plugin is already
379 // unblocked. Therefore, there's no need to PostTask.
380 DCHECK(early_message_queue_.empty());
381 PostMessageToJavaScriptImpl(serialized_val);
382 }
383 }
384
Start()385 void MessageChannel::Start() {
386 // We PostTask here instead of draining the message queue directly
387 // since we haven't finished initializing the PepperWebPluginImpl yet, so
388 // the plugin isn't available in the DOM.
389 base::MessageLoop::current()->PostTask(
390 FROM_HERE,
391 base::Bind(&MessageChannel::DrainEarlyMessageQueue,
392 weak_ptr_factory_.GetWeakPtr()));
393 }
394
FromV8ValueComplete(VarConversionResult * result_holder,const ScopedPPVar & result,bool success)395 void MessageChannel::FromV8ValueComplete(VarConversionResult* result_holder,
396 const ScopedPPVar& result,
397 bool success) {
398 result_holder->ConversionCompleted(result, success);
399 DrainCompletedPluginMessages();
400 }
401
DrainCompletedPluginMessages()402 void MessageChannel::DrainCompletedPluginMessages() {
403 if (early_message_queue_state_ == QUEUE_MESSAGES)
404 return;
405
406 while (!plugin_message_queue_.empty() &&
407 plugin_message_queue_.front().conversion_completed()) {
408 const VarConversionResult& front = plugin_message_queue_.front();
409 if (front.success()) {
410 instance_->HandleMessage(front.var());
411 } else {
412 PpapiGlobals::Get()->LogWithSource(instance()->pp_instance(),
413 PP_LOGLEVEL_ERROR,
414 std::string(),
415 kV8ToVarConversionError);
416 }
417 plugin_message_queue_.pop_front();
418 }
419 }
420
DrainEarlyMessageQueue()421 void MessageChannel::DrainEarlyMessageQueue() {
422 DCHECK(early_message_queue_state_ == QUEUE_MESSAGES);
423
424 // Take a reference on the PluginInstance. This is because JavaScript code
425 // may delete the plugin, which would destroy the PluginInstance and its
426 // corresponding MessageChannel.
427 scoped_refptr<PepperPluginInstanceImpl> instance_ref(instance_);
428 while (!early_message_queue_.empty()) {
429 PostMessageToJavaScriptImpl(early_message_queue_.front());
430 early_message_queue_.pop_front();
431 }
432 early_message_queue_state_ = SEND_DIRECTLY;
433
434 DrainCompletedPluginMessages();
435 }
436
PostMessageToJavaScriptImpl(const WebSerializedScriptValue & message_data)437 void MessageChannel::PostMessageToJavaScriptImpl(
438 const WebSerializedScriptValue& message_data) {
439 DCHECK(instance_);
440
441 WebPluginContainer* container = instance_->container();
442 // It's possible that container() is NULL if the plugin has been removed from
443 // the DOM (but the PluginInstance is not destroyed yet).
444 if (!container)
445 return;
446
447 WebDOMEvent event =
448 container->element().document().createEvent("MessageEvent");
449 WebDOMMessageEvent msg_event = event.to<WebDOMMessageEvent>();
450 msg_event.initMessageEvent("message", // type
451 false, // canBubble
452 false, // cancelable
453 message_data, // data
454 "", // origin [*]
455 NULL, // source [*]
456 ""); // lastEventId
457 // [*] Note that the |origin| is only specified for cross-document and server-
458 // sent messages, while |source| is only specified for cross-document
459 // messages:
460 // http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html
461 // This currently behaves like Web Workers. On Firefox, Chrome, and Safari
462 // at least, postMessage on Workers does not provide the origin or source.
463 // TODO(dmichael): Add origin if we change to a more iframe-like origin
464 // policy (see crbug.com/81537)
465 container->element().dispatchEvent(msg_event);
466 }
467
PostMessageToNative(const NPVariant * message_data)468 void MessageChannel::PostMessageToNative(const NPVariant* message_data) {
469 EnqueuePluginMessage(message_data);
470 DrainCompletedPluginMessages();
471 }
472
PostBlockingMessageToNative(const NPVariant * message_data,NPVariant * np_result)473 void MessageChannel::PostBlockingMessageToNative(const NPVariant* message_data,
474 NPVariant* np_result) {
475 if (early_message_queue_state_ == QUEUE_MESSAGES) {
476 WebBindings::setException(
477 np_object_,
478 "Attempted to call a synchronous method on a plugin that was not "
479 "yet loaded.");
480 return;
481 }
482
483 // If the queue of messages to the plugin is non-empty, we're still waiting on
484 // pending Var conversions. This means at some point in the past, JavaScript
485 // called postMessage (the async one) and passed us something with a browser-
486 // side host (e.g., FileSystem) and we haven't gotten a response from the
487 // browser yet. We can't currently support sending a sync message if the
488 // plugin does this, because it will break the ordering of the messages
489 // arriving at the plugin.
490 // TODO(dmichael): Fix this.
491 // See https://code.google.com/p/chromium/issues/detail?id=367896#c4
492 if (!plugin_message_queue_.empty()) {
493 WebBindings::setException(
494 np_object_,
495 "Failed to convert parameter synchronously, because a prior "
496 "call to postMessage contained a type which required asynchronous "
497 "transfer which has not completed. Not all types are supported yet by "
498 "postMessageAndAwaitResponse. See crbug.com/367896.");
499 return;
500 }
501 ScopedPPVar param;
502 if (message_data->type == NPVariantType_Object) {
503 // Convert NPVariantType_Object in to an appropriate PP_Var like Dictionary,
504 // Array, etc. Note NPVariantToVar would convert to an "Object" PP_Var,
505 // which we don't support for Messaging.
506 v8::Handle<v8::Value> v8_value = WebBindings::toV8Value(message_data);
507 V8VarConverter v8_var_converter(instance_->pp_instance());
508 bool success = v8_var_converter.FromV8ValueSync(
509 v8_value,
510 v8::Isolate::GetCurrent()->GetCurrentContext(),
511 ¶m);
512 if (!success) {
513 WebBindings::setException(
514 np_object_,
515 "Failed to convert the given parameter to a PP_Var to send to "
516 "the plugin.");
517 return;
518 }
519 } else {
520 param = ScopedPPVar(ScopedPPVar::PassRef(),
521 NPVariantToPPVar(instance(), message_data));
522 }
523 ScopedPPVar pp_result;
524 bool was_handled = instance_->HandleBlockingMessage(param, &pp_result);
525 if (!was_handled) {
526 WebBindings::setException(
527 np_object_,
528 "The plugin has not registered a handler for synchronous messages. "
529 "See the documentation for PPB_Messaging::RegisterMessageHandler "
530 "and PPP_MessageHandler.");
531 return;
532 }
533 v8::Handle<v8::Value> v8_val;
534 if (!V8VarConverter(instance_->pp_instance()).ToV8Value(
535 pp_result.get(),
536 v8::Isolate::GetCurrent()->GetCurrentContext(),
537 &v8_val)) {
538 WebBindings::setException(
539 np_object_,
540 "Failed to convert the plugin's result to a JavaScript type.");
541 return;
542 }
543 // Success! Convert the result to an NPVariant.
544 WebBindings::toNPVariant(v8_val, NULL, np_result);
545 }
546
~MessageChannel()547 MessageChannel::~MessageChannel() {
548 WebBindings::releaseObject(np_object_);
549 if (passthrough_object_)
550 WebBindings::releaseObject(passthrough_object_);
551 }
552
SetPassthroughObject(NPObject * passthrough)553 void MessageChannel::SetPassthroughObject(NPObject* passthrough) {
554 // Retain the passthrough object; We need to ensure it lives as long as this
555 // MessageChannel.
556 if (passthrough)
557 WebBindings::retainObject(passthrough);
558
559 // If we had a passthrough set already, release it. Note that we retain the
560 // incoming passthrough object first, so that we behave correctly if anyone
561 // invokes:
562 // SetPassthroughObject(passthrough_object());
563 if (passthrough_object_)
564 WebBindings::releaseObject(passthrough_object_);
565
566 passthrough_object_ = passthrough;
567 }
568
GetReadOnlyProperty(NPIdentifier key,NPVariant * value) const569 bool MessageChannel::GetReadOnlyProperty(NPIdentifier key,
570 NPVariant* value) const {
571 std::map<NPIdentifier, ScopedPPVar>::const_iterator it =
572 internal_properties_.find(key);
573 if (it != internal_properties_.end()) {
574 if (value)
575 return PPVarToNPVariant(it->second.get(), value);
576 return true;
577 }
578 return false;
579 }
580
SetReadOnlyProperty(PP_Var key,PP_Var value)581 void MessageChannel::SetReadOnlyProperty(PP_Var key, PP_Var value) {
582 internal_properties_[PPVarToNPIdentifier(key)] = ScopedPPVar(value);
583 }
584
585 } // namespace content
586