OLD | NEW |
| (Empty) |
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 "webkit/plugins/ppapi/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 "ppapi/shared_impl/ppapi_globals.h" | |
14 #include "ppapi/shared_impl/var.h" | |
15 #include "ppapi/shared_impl/var_tracker.h" | |
16 #include "third_party/WebKit/public/web/WebBindings.h" | |
17 #include "third_party/WebKit/public/web/WebDocument.h" | |
18 #include "third_party/WebKit/public/web/WebDOMMessageEvent.h" | |
19 #include "third_party/WebKit/public/web/WebElement.h" | |
20 #include "third_party/WebKit/public/web/WebFrame.h" | |
21 #include "third_party/WebKit/public/web/WebNode.h" | |
22 #include "third_party/WebKit/public/web/WebPluginContainer.h" | |
23 #include "third_party/WebKit/public/web/WebSerializedScriptValue.h" | |
24 #include "v8/include/v8.h" | |
25 #include "webkit/plugins/ppapi/host_array_buffer_var.h" | |
26 #include "webkit/plugins/ppapi/npapi_glue.h" | |
27 #include "webkit/plugins/ppapi/plugin_module.h" | |
28 #include "webkit/plugins/ppapi/ppapi_plugin_instance_impl.h" | |
29 #include "webkit/plugins/ppapi/v8_var_converter.h" | |
30 | |
31 using ppapi::ArrayBufferVar; | |
32 using ppapi::PpapiGlobals; | |
33 using ppapi::StringVar; | |
34 using WebKit::WebBindings; | |
35 using WebKit::WebElement; | |
36 using WebKit::WebDOMEvent; | |
37 using WebKit::WebDOMMessageEvent; | |
38 using WebKit::WebPluginContainer; | |
39 using WebKit::WebSerializedScriptValue; | |
40 | |
41 namespace webkit { | |
42 | |
43 namespace ppapi { | |
44 | |
45 namespace { | |
46 | |
47 const char kPostMessage[] = "postMessage"; | |
48 const char kV8ToVarConversionError[] = "Failed to convert a PostMessage " | |
49 "argument from a JavaScript value to a PP_Var. It may have cycles or be of " | |
50 "an unsupported type."; | |
51 const char kVarToV8ConversionError[] = "Failed to convert a PostMessage " | |
52 "argument from a PP_Var to a Javascript value. It may have cycles or be of " | |
53 "an unsupported type."; | |
54 | |
55 // Helper function to get the MessageChannel that is associated with an | |
56 // NPObject*. | |
57 MessageChannel* ToMessageChannel(NPObject* object) { | |
58 return static_cast<MessageChannel::MessageChannelNPObject*>(object)-> | |
59 message_channel.get(); | |
60 } | |
61 | |
62 NPObject* ToPassThroughObject(NPObject* object) { | |
63 MessageChannel* channel = ToMessageChannel(object); | |
64 return channel ? channel->passthrough_object() : NULL; | |
65 } | |
66 | |
67 // Helper function to determine if a given identifier is equal to kPostMessage. | |
68 bool IdentifierIsPostMessage(NPIdentifier identifier) { | |
69 return WebBindings::getStringIdentifier(kPostMessage) == identifier; | |
70 } | |
71 | |
72 bool NPVariantToPPVar(const NPVariant* variant, PP_Var* result) { | |
73 switch (variant->type) { | |
74 case NPVariantType_Void: | |
75 *result = PP_MakeUndefined(); | |
76 return true; | |
77 case NPVariantType_Null: | |
78 *result = PP_MakeNull(); | |
79 return true; | |
80 case NPVariantType_Bool: | |
81 *result = PP_MakeBool(PP_FromBool(NPVARIANT_TO_BOOLEAN(*variant))); | |
82 return true; | |
83 case NPVariantType_Int32: | |
84 *result = PP_MakeInt32(NPVARIANT_TO_INT32(*variant)); | |
85 return true; | |
86 case NPVariantType_Double: | |
87 *result = PP_MakeDouble(NPVARIANT_TO_DOUBLE(*variant)); | |
88 return true; | |
89 case NPVariantType_String: | |
90 *result = StringVar::StringToPPVar( | |
91 NPVARIANT_TO_STRING(*variant).UTF8Characters, | |
92 NPVARIANT_TO_STRING(*variant).UTF8Length); | |
93 return true; | |
94 case NPVariantType_Object: { | |
95 // Calling WebBindings::toV8Value creates a wrapper around NPVariant so it | |
96 // shouldn't result in a deep copy. | |
97 v8::Handle<v8::Value> v8_value = WebBindings::toV8Value(variant); | |
98 if (!V8VarConverter::FromV8Value(v8_value, v8::Context::GetCurrent(), | |
99 result)) { | |
100 return false; | |
101 } | |
102 return true; | |
103 } | |
104 } | |
105 return false; | |
106 } | |
107 | |
108 // Copy a PP_Var in to a PP_Var that is appropriate for sending via postMessage. | |
109 // This currently just copies the value. For a string Var, the result is a | |
110 // PP_Var with the a copy of |var|'s string contents and a reference count of 1. | |
111 PP_Var CopyPPVar(const PP_Var& var) { | |
112 switch (var.type) { | |
113 case PP_VARTYPE_UNDEFINED: | |
114 case PP_VARTYPE_NULL: | |
115 case PP_VARTYPE_BOOL: | |
116 case PP_VARTYPE_INT32: | |
117 case PP_VARTYPE_DOUBLE: | |
118 return var; | |
119 case PP_VARTYPE_STRING: { | |
120 StringVar* string = StringVar::FromPPVar(var); | |
121 if (!string) | |
122 return PP_MakeUndefined(); | |
123 return StringVar::StringToPPVar(string->value()); | |
124 } | |
125 case PP_VARTYPE_ARRAY_BUFFER: { | |
126 ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var); | |
127 if (!buffer) | |
128 return PP_MakeUndefined(); | |
129 PP_Var new_buffer_var = PpapiGlobals::Get()->GetVarTracker()-> | |
130 MakeArrayBufferPPVar(buffer->ByteLength()); | |
131 DCHECK(new_buffer_var.type == PP_VARTYPE_ARRAY_BUFFER); | |
132 if (new_buffer_var.type != PP_VARTYPE_ARRAY_BUFFER) | |
133 return PP_MakeUndefined(); | |
134 ArrayBufferVar* new_buffer = ArrayBufferVar::FromPPVar(new_buffer_var); | |
135 DCHECK(new_buffer); | |
136 if (!new_buffer) | |
137 return PP_MakeUndefined(); | |
138 memcpy(new_buffer->Map(), buffer->Map(), buffer->ByteLength()); | |
139 return new_buffer_var; | |
140 } | |
141 case PP_VARTYPE_OBJECT: | |
142 case PP_VARTYPE_ARRAY: | |
143 case PP_VARTYPE_DICTIONARY: | |
144 // Objects/Arrays/Dictionaries not supported by PostMessage in-process. | |
145 NOTREACHED(); | |
146 return PP_MakeUndefined(); | |
147 } | |
148 NOTREACHED(); | |
149 return PP_MakeUndefined(); | |
150 } | |
151 | |
152 //------------------------------------------------------------------------------ | |
153 // Implementations of NPClass functions. These are here to: | |
154 // - Implement postMessage behavior. | |
155 // - Forward calls to the 'passthrough' object to allow backwards-compatibility | |
156 // with GetInstanceObject() objects. | |
157 //------------------------------------------------------------------------------ | |
158 NPObject* MessageChannelAllocate(NPP npp, NPClass* the_class) { | |
159 return new MessageChannel::MessageChannelNPObject; | |
160 } | |
161 | |
162 void MessageChannelDeallocate(NPObject* object) { | |
163 MessageChannel::MessageChannelNPObject* instance = | |
164 static_cast<MessageChannel::MessageChannelNPObject*>(object); | |
165 delete instance; | |
166 } | |
167 | |
168 bool MessageChannelHasMethod(NPObject* np_obj, NPIdentifier name) { | |
169 if (!np_obj) | |
170 return false; | |
171 | |
172 // We only handle a function called postMessage. | |
173 if (IdentifierIsPostMessage(name)) | |
174 return true; | |
175 | |
176 // Other method names we will pass to the passthrough object, if we have one. | |
177 NPObject* passthrough = ToPassThroughObject(np_obj); | |
178 if (passthrough) | |
179 return WebBindings::hasMethod(NULL, passthrough, name); | |
180 return false; | |
181 } | |
182 | |
183 bool MessageChannelInvoke(NPObject* np_obj, NPIdentifier name, | |
184 const NPVariant* args, uint32 arg_count, | |
185 NPVariant* result) { | |
186 if (!np_obj) | |
187 return false; | |
188 | |
189 // We only handle a function called postMessage. | |
190 if (IdentifierIsPostMessage(name) && (arg_count == 1)) { | |
191 MessageChannel* message_channel = ToMessageChannel(np_obj); | |
192 if (message_channel) { | |
193 PP_Var argument = PP_MakeUndefined(); | |
194 if (!NPVariantToPPVar(&args[0], &argument)) { | |
195 PpapiGlobals::Get()->LogWithSource( | |
196 message_channel->instance()->pp_instance(), | |
197 PP_LOGLEVEL_ERROR, std::string(), kV8ToVarConversionError); | |
198 return false; | |
199 } | |
200 message_channel->PostMessageToNative(argument); | |
201 PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(argument); | |
202 return true; | |
203 } else { | |
204 return false; | |
205 } | |
206 } | |
207 // Other method calls we will pass to the passthrough object, if we have one. | |
208 NPObject* passthrough = ToPassThroughObject(np_obj); | |
209 if (passthrough) { | |
210 return WebBindings::invoke(NULL, passthrough, name, args, arg_count, | |
211 result); | |
212 } | |
213 return false; | |
214 } | |
215 | |
216 bool MessageChannelInvokeDefault(NPObject* np_obj, | |
217 const NPVariant* args, | |
218 uint32 arg_count, | |
219 NPVariant* result) { | |
220 if (!np_obj) | |
221 return false; | |
222 | |
223 // Invoke on the passthrough object, if we have one. | |
224 NPObject* passthrough = ToPassThroughObject(np_obj); | |
225 if (passthrough) { | |
226 return WebBindings::invokeDefault(NULL, passthrough, args, arg_count, | |
227 result); | |
228 } | |
229 return false; | |
230 } | |
231 | |
232 bool MessageChannelHasProperty(NPObject* np_obj, NPIdentifier name) { | |
233 if (!np_obj) | |
234 return false; | |
235 | |
236 // Invoke on the passthrough object, if we have one. | |
237 NPObject* passthrough = ToPassThroughObject(np_obj); | |
238 if (passthrough) | |
239 return WebBindings::hasProperty(NULL, passthrough, name); | |
240 return false; | |
241 } | |
242 | |
243 bool MessageChannelGetProperty(NPObject* np_obj, NPIdentifier name, | |
244 NPVariant* result) { | |
245 if (!np_obj) | |
246 return false; | |
247 | |
248 // Don't allow getting the postMessage function. | |
249 if (IdentifierIsPostMessage(name)) | |
250 return false; | |
251 | |
252 // Invoke on the passthrough object, if we have one. | |
253 NPObject* passthrough = ToPassThroughObject(np_obj); | |
254 if (passthrough) | |
255 return WebBindings::getProperty(NULL, passthrough, name, result); | |
256 return false; | |
257 } | |
258 | |
259 bool MessageChannelSetProperty(NPObject* np_obj, NPIdentifier name, | |
260 const NPVariant* variant) { | |
261 if (!np_obj) | |
262 return false; | |
263 | |
264 // Don't allow setting the postMessage function. | |
265 if (IdentifierIsPostMessage(name)) | |
266 return false; | |
267 | |
268 // Invoke on the passthrough object, if we have one. | |
269 NPObject* passthrough = ToPassThroughObject(np_obj); | |
270 if (passthrough) | |
271 return WebBindings::setProperty(NULL, passthrough, name, variant); | |
272 return false; | |
273 } | |
274 | |
275 bool MessageChannelEnumerate(NPObject *np_obj, NPIdentifier **value, | |
276 uint32_t *count) { | |
277 if (!np_obj) | |
278 return false; | |
279 | |
280 // Invoke on the passthrough object, if we have one, to enumerate its | |
281 // properties. | |
282 NPObject* passthrough = ToPassThroughObject(np_obj); | |
283 if (passthrough) { | |
284 bool success = WebBindings::enumerate(NULL, passthrough, value, count); | |
285 if (success) { | |
286 // Add postMessage to the list and return it. | |
287 if (std::numeric_limits<size_t>::max() / sizeof(NPIdentifier) <= | |
288 static_cast<size_t>(*count) + 1) // Else, "always false" x64 warning. | |
289 return false; | |
290 NPIdentifier* new_array = static_cast<NPIdentifier*>( | |
291 std::malloc(sizeof(NPIdentifier) * (*count + 1))); | |
292 std::memcpy(new_array, *value, sizeof(NPIdentifier)*(*count)); | |
293 new_array[*count] = WebBindings::getStringIdentifier(kPostMessage); | |
294 std::free(*value); | |
295 *value = new_array; | |
296 ++(*count); | |
297 return true; | |
298 } | |
299 } | |
300 | |
301 // Otherwise, build an array that includes only postMessage. | |
302 *value = static_cast<NPIdentifier*>(malloc(sizeof(NPIdentifier))); | |
303 (*value)[0] = WebBindings::getStringIdentifier(kPostMessage); | |
304 *count = 1; | |
305 return true; | |
306 } | |
307 | |
308 NPClass message_channel_class = { | |
309 NP_CLASS_STRUCT_VERSION, | |
310 &MessageChannelAllocate, | |
311 &MessageChannelDeallocate, | |
312 NULL, | |
313 &MessageChannelHasMethod, | |
314 &MessageChannelInvoke, | |
315 &MessageChannelInvokeDefault, | |
316 &MessageChannelHasProperty, | |
317 &MessageChannelGetProperty, | |
318 &MessageChannelSetProperty, | |
319 NULL, | |
320 &MessageChannelEnumerate, | |
321 }; | |
322 | |
323 } // namespace | |
324 | |
325 // MessageChannel -------------------------------------------------------------- | |
326 MessageChannel::MessageChannelNPObject::MessageChannelNPObject() { | |
327 } | |
328 | |
329 MessageChannel::MessageChannelNPObject::~MessageChannelNPObject() {} | |
330 | |
331 MessageChannel::MessageChannel(PluginInstanceImpl* instance) | |
332 : instance_(instance), | |
333 passthrough_object_(NULL), | |
334 np_object_(NULL), | |
335 weak_ptr_factory_(this), | |
336 early_message_queue_state_(QUEUE_MESSAGES) { | |
337 // Now create an NPObject for receiving calls to postMessage. This sets the | |
338 // reference count to 1. We release it in the destructor. | |
339 NPObject* obj = WebBindings::createObject(instance_->instanceNPP(), | |
340 &message_channel_class); | |
341 DCHECK(obj); | |
342 np_object_ = static_cast<MessageChannel::MessageChannelNPObject*>(obj); | |
343 np_object_->message_channel = weak_ptr_factory_.GetWeakPtr(); | |
344 } | |
345 | |
346 void MessageChannel::PostMessageToJavaScript(PP_Var message_data) { | |
347 v8::HandleScope scope; | |
348 | |
349 // Because V8 is probably not on the stack for Native->JS calls, we need to | |
350 // enter the appropriate context for the plugin. | |
351 WebPluginContainer* container = instance_->container(); | |
352 // It's possible that container() is NULL if the plugin has been removed from | |
353 // the DOM (but the PluginInstance is not destroyed yet). | |
354 if (!container) | |
355 return; | |
356 | |
357 v8::Local<v8::Context> context = | |
358 container->element().document().frame()->mainWorldScriptContext(); | |
359 // If the page is being destroyed, the context may be empty. | |
360 if (context.IsEmpty()) | |
361 return; | |
362 v8::Context::Scope context_scope(context); | |
363 | |
364 v8::Handle<v8::Value> v8_val; | |
365 if (!V8VarConverter::ToV8Value(message_data, context, &v8_val)) { | |
366 PpapiGlobals::Get()->LogWithSource(instance_->pp_instance(), | |
367 PP_LOGLEVEL_ERROR, std::string(), kVarToV8ConversionError); | |
368 return; | |
369 } | |
370 | |
371 // This is for backward compatibility. It usually makes sense for us to return | |
372 // a string object rather than a string primitive because it allows multiple | |
373 // references to the same string (as with PP_Var strings). However, prior to | |
374 // implementing dictionary and array, vars we would return a string primitive | |
375 // here. Changing it to an object now will break existing code that uses | |
376 // strict comparisons for strings returned from PostMessage. e.g. x === "123" | |
377 // will no longer return true. So if the only value to return is a string | |
378 // object, just return the string primitive. | |
379 if (v8_val->IsStringObject()) | |
380 v8_val = v8_val->ToString(); | |
381 | |
382 WebSerializedScriptValue serialized_val = | |
383 WebSerializedScriptValue::serialize(v8_val); | |
384 | |
385 if (instance_->module()->IsProxied()) { | |
386 if (early_message_queue_state_ != SEND_DIRECTLY) { | |
387 // We can't just PostTask here; the messages would arrive out of | |
388 // order. Instead, we queue them up until we're ready to post | |
389 // them. | |
390 early_message_queue_.push_back(serialized_val); | |
391 } else { | |
392 // The proxy sent an asynchronous message, so the plugin is already | |
393 // unblocked. Therefore, there's no need to PostTask. | |
394 DCHECK(early_message_queue_.size() == 0); | |
395 PostMessageToJavaScriptImpl(serialized_val); | |
396 } | |
397 } else { | |
398 base::MessageLoop::current()->PostTask( | |
399 FROM_HERE, | |
400 base::Bind(&MessageChannel::PostMessageToJavaScriptImpl, | |
401 weak_ptr_factory_.GetWeakPtr(), | |
402 serialized_val)); | |
403 } | |
404 } | |
405 | |
406 void MessageChannel::StopQueueingJavaScriptMessages() { | |
407 // We PostTask here instead of draining the message queue directly | |
408 // since we haven't finished initializing the WebPluginImpl yet, so | |
409 // the plugin isn't available in the DOM. | |
410 early_message_queue_state_ = DRAIN_PENDING; | |
411 base::MessageLoop::current()->PostTask( | |
412 FROM_HERE, | |
413 base::Bind(&MessageChannel::DrainEarlyMessageQueue, | |
414 weak_ptr_factory_.GetWeakPtr())); | |
415 } | |
416 | |
417 void MessageChannel::QueueJavaScriptMessages() { | |
418 if (early_message_queue_state_ == DRAIN_PENDING) | |
419 early_message_queue_state_ = DRAIN_CANCELLED; | |
420 else | |
421 early_message_queue_state_ = QUEUE_MESSAGES; | |
422 } | |
423 | |
424 void MessageChannel::DrainEarlyMessageQueue() { | |
425 // Take a reference on the PluginInstance. This is because JavaScript code | |
426 // may delete the plugin, which would destroy the PluginInstance and its | |
427 // corresponding MessageChannel. | |
428 scoped_refptr<PluginInstanceImpl> instance_ref(instance_); | |
429 | |
430 if (early_message_queue_state_ == DRAIN_CANCELLED) { | |
431 early_message_queue_state_ = QUEUE_MESSAGES; | |
432 return; | |
433 } | |
434 DCHECK(early_message_queue_state_ == DRAIN_PENDING); | |
435 | |
436 while (!early_message_queue_.empty()) { | |
437 PostMessageToJavaScriptImpl(early_message_queue_.front()); | |
438 early_message_queue_.pop_front(); | |
439 } | |
440 early_message_queue_state_ = SEND_DIRECTLY; | |
441 } | |
442 | |
443 void MessageChannel::PostMessageToJavaScriptImpl( | |
444 const WebSerializedScriptValue& message_data) { | |
445 DCHECK(instance_); | |
446 | |
447 WebPluginContainer* container = instance_->container(); | |
448 // It's possible that container() is NULL if the plugin has been removed from | |
449 // the DOM (but the PluginInstance is not destroyed yet). | |
450 if (!container) | |
451 return; | |
452 | |
453 WebDOMEvent event = | |
454 container->element().document().createEvent("MessageEvent"); | |
455 WebDOMMessageEvent msg_event = event.to<WebDOMMessageEvent>(); | |
456 msg_event.initMessageEvent("message", // type | |
457 false, // canBubble | |
458 false, // cancelable | |
459 message_data, // data | |
460 "", // origin [*] | |
461 NULL, // source [*] | |
462 ""); // lastEventId | |
463 // [*] Note that the |origin| is only specified for cross-document and server- | |
464 // sent messages, while |source| is only specified for cross-document | |
465 // messages: | |
466 // http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html | |
467 // This currently behaves like Web Workers. On Firefox, Chrome, and Safari | |
468 // at least, postMessage on Workers does not provide the origin or source. | |
469 // TODO(dmichael): Add origin if we change to a more iframe-like origin | |
470 // policy (see crbug.com/81537) | |
471 | |
472 container->element().dispatchEvent(msg_event); | |
473 } | |
474 | |
475 void MessageChannel::PostMessageToNative(PP_Var message_data) { | |
476 if (instance_->module()->IsProxied()) { | |
477 // In the proxied case, the copy will happen via serializiation, and the | |
478 // message is asynchronous. Therefore there's no need to copy the Var, nor | |
479 // to PostTask. | |
480 PostMessageToNativeImpl(message_data); | |
481 } else { | |
482 // Make a copy of the message data for the Task we will run. | |
483 PP_Var var_copy(CopyPPVar(message_data)); | |
484 | |
485 base::MessageLoop::current()->PostTask( | |
486 FROM_HERE, | |
487 base::Bind(&MessageChannel::PostMessageToNativeImpl, | |
488 weak_ptr_factory_.GetWeakPtr(), | |
489 var_copy)); | |
490 } | |
491 } | |
492 | |
493 void MessageChannel::PostMessageToNativeImpl(PP_Var message_data) { | |
494 instance_->HandleMessage(message_data); | |
495 } | |
496 | |
497 MessageChannel::~MessageChannel() { | |
498 WebBindings::releaseObject(np_object_); | |
499 if (passthrough_object_) | |
500 WebBindings::releaseObject(passthrough_object_); | |
501 } | |
502 | |
503 void MessageChannel::SetPassthroughObject(NPObject* passthrough) { | |
504 // Retain the passthrough object; We need to ensure it lives as long as this | |
505 // MessageChannel. | |
506 if (passthrough) | |
507 WebBindings::retainObject(passthrough); | |
508 | |
509 // If we had a passthrough set already, release it. Note that we retain the | |
510 // incoming passthrough object first, so that we behave correctly if anyone | |
511 // invokes: | |
512 // SetPassthroughObject(passthrough_object()); | |
513 if (passthrough_object_) | |
514 WebBindings::releaseObject(passthrough_object_); | |
515 | |
516 passthrough_object_ = passthrough; | |
517 } | |
518 | |
519 } // namespace ppapi | |
520 } // namespace webkit | |
OLD | NEW |