490a6fb013fe0c387244c6205cd9d33d751ecbcd
[platform/framework/web/crosswalk.git] / src / content / renderer / pepper / message_channel.cc
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/pepper_plugin_instance_impl.h"
15 #include "content/renderer/pepper/pepper_try_catch.h"
16 #include "content/renderer/pepper/plugin_module.h"
17 #include "content/renderer/pepper/plugin_object.h"
18 #include "content/renderer/pepper/v8_var_converter.h"
19 #include "gin/arguments.h"
20 #include "gin/converter.h"
21 #include "gin/function_template.h"
22 #include "gin/object_template_builder.h"
23 #include "gin/public/gin_embedders.h"
24 #include "ppapi/shared_impl/ppapi_globals.h"
25 #include "ppapi/shared_impl/scoped_pp_var.h"
26 #include "ppapi/shared_impl/var.h"
27 #include "ppapi/shared_impl/var_tracker.h"
28 #include "third_party/WebKit/public/web/WebBindings.h"
29 #include "third_party/WebKit/public/web/WebDocument.h"
30 #include "third_party/WebKit/public/web/WebDOMMessageEvent.h"
31 #include "third_party/WebKit/public/web/WebElement.h"
32 #include "third_party/WebKit/public/web/WebLocalFrame.h"
33 #include "third_party/WebKit/public/web/WebNode.h"
34 #include "third_party/WebKit/public/web/WebPluginContainer.h"
35 #include "third_party/WebKit/public/web/WebSerializedScriptValue.h"
36 #include "v8/include/v8.h"
37
38 using ppapi::ArrayBufferVar;
39 using ppapi::PpapiGlobals;
40 using ppapi::ScopedPPVar;
41 using ppapi::StringVar;
42 using blink::WebBindings;
43 using blink::WebElement;
44 using blink::WebDOMEvent;
45 using blink::WebDOMMessageEvent;
46 using blink::WebPluginContainer;
47 using blink::WebSerializedScriptValue;
48
49 namespace content {
50
51 namespace {
52
53 const char kPostMessage[] = "postMessage";
54 const char kPostMessageAndAwaitResponse[] = "postMessageAndAwaitResponse";
55 const char kV8ToVarConversionError[] =
56     "Failed to convert a PostMessage "
57     "argument from a JavaScript value to a PP_Var. It may have cycles or be of "
58     "an unsupported type.";
59 const char kVarToV8ConversionError[] =
60     "Failed to convert a PostMessage "
61     "argument from a PP_Var to a Javascript value. It may have cycles or be of "
62     "an unsupported type.";
63
64 }  // namespace
65
66 // MessageChannel --------------------------------------------------------------
67 struct MessageChannel::VarConversionResult {
68   VarConversionResult() : success_(false), conversion_completed_(false) {}
69   void ConversionCompleted(const ScopedPPVar& var,
70                            bool success) {
71     conversion_completed_ = true;
72     var_ = var;
73     success_ = success;
74   }
75   const ScopedPPVar& var() const { return var_; }
76   bool success() const { return success_; }
77   bool conversion_completed() const { return conversion_completed_; }
78
79  private:
80   ScopedPPVar var_;
81   bool success_;
82   bool conversion_completed_;
83 };
84
85 // static
86 gin::WrapperInfo MessageChannel::kWrapperInfo = {gin::kEmbedderNativeGin};
87
88 // static
89 MessageChannel* MessageChannel::Create(PepperPluginInstanceImpl* instance,
90                                        v8::Persistent<v8::Object>* result) {
91   MessageChannel* message_channel = new MessageChannel(instance);
92   v8::HandleScope handle_scope(instance->GetIsolate());
93   v8::Context::Scope context_scope(instance->GetMainWorldContext());
94   gin::Handle<MessageChannel> handle =
95       gin::CreateHandle(instance->GetIsolate(), message_channel);
96   result->Reset(instance->GetIsolate(), handle.ToV8()->ToObject());
97   return message_channel;
98 }
99
100 MessageChannel::~MessageChannel() {
101   UnregisterSyncMessageStatusObserver();
102
103   passthrough_object_.Reset();
104   if (instance_)
105     instance_->MessageChannelDestroyed();
106 }
107
108 void MessageChannel::InstanceDeleted() {
109   UnregisterSyncMessageStatusObserver();
110   instance_ = NULL;
111 }
112
113 void MessageChannel::PostMessageToJavaScript(PP_Var message_data) {
114   v8::HandleScope scope(v8::Isolate::GetCurrent());
115
116   // Because V8 is probably not on the stack for Native->JS calls, we need to
117   // enter the appropriate context for the plugin.
118   v8::Local<v8::Context> context = instance_->GetMainWorldContext();
119   if (context.IsEmpty())
120     return;
121
122   v8::Context::Scope context_scope(context);
123
124   v8::Handle<v8::Value> v8_val;
125   if (!V8VarConverter(instance_->pp_instance())
126            .ToV8Value(message_data, context, &v8_val)) {
127     PpapiGlobals::Get()->LogWithSource(instance_->pp_instance(),
128                                        PP_LOGLEVEL_ERROR,
129                                        std::string(),
130                                        kVarToV8ConversionError);
131     return;
132   }
133
134   WebSerializedScriptValue serialized_val =
135       WebSerializedScriptValue::serialize(v8_val);
136
137   if (js_message_queue_state_ != SEND_DIRECTLY) {
138     // We can't just PostTask here; the messages would arrive out of
139     // order. Instead, we queue them up until we're ready to post
140     // them.
141     js_message_queue_.push_back(serialized_val);
142   } else {
143     // The proxy sent an asynchronous message, so the plugin is already
144     // unblocked. Therefore, there's no need to PostTask.
145     DCHECK(js_message_queue_.empty());
146     PostMessageToJavaScriptImpl(serialized_val);
147   }
148 }
149
150 void MessageChannel::Start() {
151   DCHECK_EQ(WAITING_TO_START, js_message_queue_state_);
152   DCHECK_EQ(WAITING_TO_START, plugin_message_queue_state_);
153
154   ppapi::proxy::HostDispatcher* dispatcher =
155       ppapi::proxy::HostDispatcher::GetForInstance(instance_->pp_instance());
156   // The dispatcher is NULL for in-process.
157   if (dispatcher) {
158     unregister_observer_callback_ =
159         dispatcher->AddSyncMessageStatusObserver(this);
160   }
161
162   // We can't drain the JS message queue directly since we haven't finished
163   // initializing the PepperWebPluginImpl yet, so the plugin isn't available in
164   // the DOM.
165   DrainJSMessageQueueSoon();
166
167   plugin_message_queue_state_ = SEND_DIRECTLY;
168   DrainCompletedPluginMessages();
169 }
170
171 void MessageChannel::SetPassthroughObject(v8::Handle<v8::Object> passthrough) {
172   passthrough_object_.Reset(instance_->GetIsolate(), passthrough);
173 }
174
175 void MessageChannel::SetReadOnlyProperty(PP_Var key, PP_Var value) {
176   StringVar* key_string = StringVar::FromPPVar(key);
177   if (key_string) {
178     internal_named_properties_[key_string->value()] = ScopedPPVar(value);
179   } else {
180     NOTREACHED();
181   }
182 }
183
184 MessageChannel::MessageChannel(PepperPluginInstanceImpl* instance)
185     : gin::NamedPropertyInterceptor(instance->GetIsolate(), this),
186       instance_(instance),
187       js_message_queue_state_(WAITING_TO_START),
188       blocking_message_depth_(0),
189       plugin_message_queue_state_(WAITING_TO_START),
190       weak_ptr_factory_(this) {
191 }
192
193 gin::ObjectTemplateBuilder MessageChannel::GetObjectTemplateBuilder(
194     v8::Isolate* isolate) {
195   return Wrappable<MessageChannel>::GetObjectTemplateBuilder(isolate)
196       .AddNamedPropertyInterceptor();
197 }
198
199 void MessageChannel::BeginBlockOnSyncMessage() {
200   js_message_queue_state_ = QUEUE_MESSAGES;
201   ++blocking_message_depth_;
202 }
203
204 void MessageChannel::EndBlockOnSyncMessage() {
205   DCHECK_GT(blocking_message_depth_, 0);
206   --blocking_message_depth_;
207   if (!blocking_message_depth_)
208     DrainJSMessageQueueSoon();
209 }
210
211 v8::Local<v8::Value> MessageChannel::GetNamedProperty(
212     v8::Isolate* isolate,
213     const std::string& identifier) {
214   if (!instance_)
215     return v8::Local<v8::Value>();
216
217   PepperTryCatchV8 try_catch(instance_, V8VarConverter::kDisallowObjectVars,
218                              isolate);
219   if (identifier == kPostMessage) {
220     return gin::CreateFunctionTemplate(isolate,
221         base::Bind(&MessageChannel::PostMessageToNative,
222                    weak_ptr_factory_.GetWeakPtr()))->GetFunction();
223   } else if (identifier == kPostMessageAndAwaitResponse) {
224     return gin::CreateFunctionTemplate(isolate,
225         base::Bind(&MessageChannel::PostBlockingMessageToNative,
226                    weak_ptr_factory_.GetWeakPtr()))->GetFunction();
227   }
228
229   std::map<std::string, ScopedPPVar>::const_iterator it =
230       internal_named_properties_.find(identifier);
231   if (it != internal_named_properties_.end()) {
232     v8::Handle<v8::Value> result = try_catch.ToV8(it->second.get());
233     if (try_catch.ThrowException())
234       return v8::Local<v8::Value>();
235     return result;
236   }
237
238   PluginObject* plugin_object = GetPluginObject(isolate);
239   if (plugin_object)
240     return plugin_object->GetNamedProperty(isolate, identifier);
241   return v8::Local<v8::Value>();
242 }
243
244 bool MessageChannel::SetNamedProperty(v8::Isolate* isolate,
245                                       const std::string& identifier,
246                                       v8::Local<v8::Value> value) {
247   if (!instance_)
248     return false;
249   PepperTryCatchV8 try_catch(instance_, V8VarConverter::kDisallowObjectVars,
250                              isolate);
251   if (identifier == kPostMessage ||
252       (identifier == kPostMessageAndAwaitResponse)) {
253     try_catch.ThrowException("Cannot set properties with the name postMessage"
254                              "or postMessageAndAwaitResponse");
255     return true;
256   }
257
258   // We don't forward this to the passthrough object; no plugins use that
259   // feature.
260   // TODO(raymes): Remove SetProperty support from PPP_Class.
261
262   return false;
263 }
264
265 std::vector<std::string> MessageChannel::EnumerateNamedProperties(
266     v8::Isolate* isolate) {
267   std::vector<std::string> result;
268   PluginObject* plugin_object = GetPluginObject(isolate);
269   if (plugin_object)
270     result = plugin_object->EnumerateNamedProperties(isolate);
271   result.push_back(kPostMessage);
272   result.push_back(kPostMessageAndAwaitResponse);
273   return result;
274 }
275
276 void MessageChannel::PostMessageToNative(gin::Arguments* args) {
277   if (!instance_)
278     return;
279   if (args->Length() != 1) {
280     // TODO(raymes): Consider throwing an exception here. We don't now for
281     // backward compatibility.
282     return;
283   }
284
285   v8::Handle<v8::Value> message_data;
286   if (!args->GetNext(&message_data)) {
287     NOTREACHED();
288   }
289
290   EnqueuePluginMessage(message_data);
291   DrainCompletedPluginMessages();
292 }
293
294 void MessageChannel::PostBlockingMessageToNative(gin::Arguments* args) {
295   if (!instance_)
296     return;
297   PepperTryCatchV8 try_catch(instance_, V8VarConverter::kDisallowObjectVars,
298                              args->isolate());
299   if (args->Length() != 1) {
300     try_catch.ThrowException(
301         "postMessageAndAwaitResponse requires one argument");
302     return;
303   }
304
305   v8::Handle<v8::Value> message_data;
306   if (!args->GetNext(&message_data)) {
307     NOTREACHED();
308   }
309
310   if (plugin_message_queue_state_ == WAITING_TO_START) {
311     try_catch.ThrowException(
312         "Attempted to call a synchronous method on a plugin that was not "
313         "yet loaded.");
314     return;
315   }
316
317   // If the queue of messages to the plugin is non-empty, we're still waiting on
318   // pending Var conversions. This means at some point in the past, JavaScript
319   // called postMessage (the async one) and passed us something with a browser-
320   // side host (e.g., FileSystem) and we haven't gotten a response from the
321   // browser yet. We can't currently support sending a sync message if the
322   // plugin does this, because it will break the ordering of the messages
323   // arriving at the plugin.
324   // TODO(dmichael): Fix this.
325   // See https://code.google.com/p/chromium/issues/detail?id=367896#c4
326   if (!plugin_message_queue_.empty()) {
327     try_catch.ThrowException(
328         "Failed to convert parameter synchronously, because a prior "
329         "call to postMessage contained a type which required asynchronous "
330         "transfer which has not completed. Not all types are supported yet by "
331         "postMessageAndAwaitResponse. See crbug.com/367896.");
332     return;
333   }
334   ScopedPPVar param = try_catch.FromV8(message_data);
335   if (try_catch.ThrowException())
336     return;
337
338   ScopedPPVar pp_result;
339   bool was_handled = instance_->HandleBlockingMessage(param, &pp_result);
340   if (!was_handled) {
341     try_catch.ThrowException(
342         "The plugin has not registered a handler for synchronous messages. "
343         "See the documentation for PPB_Messaging::RegisterMessageHandler "
344         "and PPP_MessageHandler.");
345     return;
346   }
347   v8::Handle<v8::Value> v8_result = try_catch.ToV8(pp_result.get());
348   if (try_catch.ThrowException())
349     return;
350
351   args->Return(v8_result);
352 }
353
354 void MessageChannel::PostMessageToJavaScriptImpl(
355     const WebSerializedScriptValue& message_data) {
356   DCHECK(instance_);
357
358   WebPluginContainer* container = instance_->container();
359   // It's possible that container() is NULL if the plugin has been removed from
360   // the DOM (but the PluginInstance is not destroyed yet).
361   if (!container)
362     return;
363
364   WebDOMEvent event =
365       container->element().document().createEvent("MessageEvent");
366   WebDOMMessageEvent msg_event = event.to<WebDOMMessageEvent>();
367   msg_event.initMessageEvent("message",     // type
368                              false,         // canBubble
369                              false,         // cancelable
370                              message_data,  // data
371                              "",            // origin [*]
372                              NULL,          // source [*]
373                              "");           // lastEventId
374   // [*] Note that the |origin| is only specified for cross-document and server-
375   //     sent messages, while |source| is only specified for cross-document
376   //     messages:
377   //      http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html
378   //     This currently behaves like Web Workers. On Firefox, Chrome, and Safari
379   //     at least, postMessage on Workers does not provide the origin or source.
380   //     TODO(dmichael):  Add origin if we change to a more iframe-like origin
381   //                      policy (see crbug.com/81537)
382   container->element().dispatchEvent(msg_event);
383 }
384
385 PluginObject* MessageChannel::GetPluginObject(v8::Isolate* isolate) {
386   return PluginObject::FromV8Object(isolate,
387       v8::Local<v8::Object>::New(isolate, passthrough_object_));
388 }
389
390 void MessageChannel::EnqueuePluginMessage(v8::Handle<v8::Value> v8_value) {
391   plugin_message_queue_.push_back(VarConversionResult());
392   // Convert NPVariantType_Object in to an appropriate PP_Var like Dictionary,
393   // Array, etc. Note NPVariantToVar would convert to an "Object" PP_Var,
394   // which we don't support for Messaging.
395   // TODO(raymes): Possibly change this to use TryCatch to do the conversion and
396   // throw an exception if necessary.
397   V8VarConverter v8_var_converter(instance_->pp_instance());
398   V8VarConverter::VarResult conversion_result =
399       v8_var_converter.FromV8Value(
400           v8_value,
401           v8::Isolate::GetCurrent()->GetCurrentContext(),
402           base::Bind(&MessageChannel::FromV8ValueComplete,
403                      weak_ptr_factory_.GetWeakPtr(),
404                      &plugin_message_queue_.back()));
405   if (conversion_result.completed_synchronously) {
406     plugin_message_queue_.back().ConversionCompleted(
407         conversion_result.var,
408         conversion_result.success);
409   }
410 }
411
412 void MessageChannel::FromV8ValueComplete(VarConversionResult* result_holder,
413                                          const ScopedPPVar& result,
414                                          bool success) {
415   if (!instance_)
416     return;
417   result_holder->ConversionCompleted(result, success);
418   DrainCompletedPluginMessages();
419 }
420
421 void MessageChannel::DrainCompletedPluginMessages() {
422   DCHECK(instance_);
423   if (plugin_message_queue_state_ == WAITING_TO_START)
424     return;
425
426   while (!plugin_message_queue_.empty() &&
427          plugin_message_queue_.front().conversion_completed()) {
428     const VarConversionResult& front = plugin_message_queue_.front();
429     if (front.success()) {
430       instance_->HandleMessage(front.var());
431     } else {
432       PpapiGlobals::Get()->LogWithSource(instance()->pp_instance(),
433                                          PP_LOGLEVEL_ERROR,
434                                          std::string(),
435                                          kV8ToVarConversionError);
436     }
437     plugin_message_queue_.pop_front();
438   }
439 }
440
441 void MessageChannel::DrainJSMessageQueue() {
442   if (!instance_)
443     return;
444   if (js_message_queue_state_ == SEND_DIRECTLY)
445     return;
446
447   // Take a reference on the PluginInstance. This is because JavaScript code
448   // may delete the plugin, which would destroy the PluginInstance and its
449   // corresponding MessageChannel.
450   scoped_refptr<PepperPluginInstanceImpl> instance_ref(instance_);
451   while (!js_message_queue_.empty()) {
452     PostMessageToJavaScriptImpl(js_message_queue_.front());
453     js_message_queue_.pop_front();
454   }
455   js_message_queue_state_ = SEND_DIRECTLY;
456 }
457
458 void MessageChannel::DrainJSMessageQueueSoon() {
459   base::MessageLoop::current()->PostTask(
460       FROM_HERE,
461       base::Bind(&MessageChannel::DrainJSMessageQueue,
462                  weak_ptr_factory_.GetWeakPtr()));
463 }
464
465 void MessageChannel::UnregisterSyncMessageStatusObserver() {
466   if (!unregister_observer_callback_.is_null()) {
467     unregister_observer_callback_.Run();
468     unregister_observer_callback_.Reset();
469   }
470 }
471
472 }  // namespace content