Home | History | Annotate | Download | only in pepper
      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   // TODO(raymes): This is only used by the gTalk plugin which is deprecated.
    259   // Remove passthrough of SetProperty calls as soon as it is removed.
    260   PluginObject* plugin_object = GetPluginObject(isolate);
    261   if (plugin_object)
    262     return plugin_object->SetNamedProperty(isolate, identifier, value);
    263 
    264   return false;
    265 }
    266 
    267 std::vector<std::string> MessageChannel::EnumerateNamedProperties(
    268     v8::Isolate* isolate) {
    269   std::vector<std::string> result;
    270   PluginObject* plugin_object = GetPluginObject(isolate);
    271   if (plugin_object)
    272     result = plugin_object->EnumerateNamedProperties(isolate);
    273   result.push_back(kPostMessage);
    274   result.push_back(kPostMessageAndAwaitResponse);
    275   return result;
    276 }
    277 
    278 void MessageChannel::PostMessageToNative(gin::Arguments* args) {
    279   if (!instance_)
    280     return;
    281   if (args->Length() != 1) {
    282     // TODO(raymes): Consider throwing an exception here. We don't now for
    283     // backward compatibility.
    284     return;
    285   }
    286 
    287   v8::Handle<v8::Value> message_data;
    288   if (!args->GetNext(&message_data)) {
    289     NOTREACHED();
    290   }
    291 
    292   EnqueuePluginMessage(message_data);
    293   DrainCompletedPluginMessages();
    294 }
    295 
    296 void MessageChannel::PostBlockingMessageToNative(gin::Arguments* args) {
    297   if (!instance_)
    298     return;
    299   PepperTryCatchV8 try_catch(instance_, V8VarConverter::kDisallowObjectVars,
    300                              args->isolate());
    301   if (args->Length() != 1) {
    302     try_catch.ThrowException(
    303         "postMessageAndAwaitResponse requires one argument");
    304     return;
    305   }
    306 
    307   v8::Handle<v8::Value> message_data;
    308   if (!args->GetNext(&message_data)) {
    309     NOTREACHED();
    310   }
    311 
    312   if (plugin_message_queue_state_ == WAITING_TO_START) {
    313     try_catch.ThrowException(
    314         "Attempted to call a synchronous method on a plugin that was not "
    315         "yet loaded.");
    316     return;
    317   }
    318 
    319   // If the queue of messages to the plugin is non-empty, we're still waiting on
    320   // pending Var conversions. This means at some point in the past, JavaScript
    321   // called postMessage (the async one) and passed us something with a browser-
    322   // side host (e.g., FileSystem) and we haven't gotten a response from the
    323   // browser yet. We can't currently support sending a sync message if the
    324   // plugin does this, because it will break the ordering of the messages
    325   // arriving at the plugin.
    326   // TODO(dmichael): Fix this.
    327   // See https://code.google.com/p/chromium/issues/detail?id=367896#c4
    328   if (!plugin_message_queue_.empty()) {
    329     try_catch.ThrowException(
    330         "Failed to convert parameter synchronously, because a prior "
    331         "call to postMessage contained a type which required asynchronous "
    332         "transfer which has not completed. Not all types are supported yet by "
    333         "postMessageAndAwaitResponse. See crbug.com/367896.");
    334     return;
    335   }
    336   ScopedPPVar param = try_catch.FromV8(message_data);
    337   if (try_catch.ThrowException())
    338     return;
    339 
    340   ScopedPPVar pp_result;
    341   bool was_handled = instance_->HandleBlockingMessage(param, &pp_result);
    342   if (!was_handled) {
    343     try_catch.ThrowException(
    344         "The plugin has not registered a handler for synchronous messages. "
    345         "See the documentation for PPB_Messaging::RegisterMessageHandler "
    346         "and PPP_MessageHandler.");
    347     return;
    348   }
    349   v8::Handle<v8::Value> v8_result = try_catch.ToV8(pp_result.get());
    350   if (try_catch.ThrowException())
    351     return;
    352 
    353   args->Return(v8_result);
    354 }
    355 
    356 void MessageChannel::PostMessageToJavaScriptImpl(
    357     const WebSerializedScriptValue& message_data) {
    358   DCHECK(instance_);
    359 
    360   WebPluginContainer* container = instance_->container();
    361   // It's possible that container() is NULL if the plugin has been removed from
    362   // the DOM (but the PluginInstance is not destroyed yet).
    363   if (!container)
    364     return;
    365 
    366   WebDOMEvent event =
    367       container->element().document().createEvent("MessageEvent");
    368   WebDOMMessageEvent msg_event = event.to<WebDOMMessageEvent>();
    369   msg_event.initMessageEvent("message",     // type
    370                              false,         // canBubble
    371                              false,         // cancelable
    372                              message_data,  // data
    373                              "",            // origin [*]
    374                              NULL,          // source [*]
    375                              "");           // lastEventId
    376   // [*] Note that the |origin| is only specified for cross-document and server-
    377   //     sent messages, while |source| is only specified for cross-document
    378   //     messages:
    379   //      http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html
    380   //     This currently behaves like Web Workers. On Firefox, Chrome, and Safari
    381   //     at least, postMessage on Workers does not provide the origin or source.
    382   //     TODO(dmichael):  Add origin if we change to a more iframe-like origin
    383   //                      policy (see crbug.com/81537)
    384   container->element().dispatchEvent(msg_event);
    385 }
    386 
    387 PluginObject* MessageChannel::GetPluginObject(v8::Isolate* isolate) {
    388   return PluginObject::FromV8Object(isolate,
    389       v8::Local<v8::Object>::New(isolate, passthrough_object_));
    390 }
    391 
    392 void MessageChannel::EnqueuePluginMessage(v8::Handle<v8::Value> v8_value) {
    393   plugin_message_queue_.push_back(VarConversionResult());
    394   // Convert NPVariantType_Object in to an appropriate PP_Var like Dictionary,
    395   // Array, etc. Note NPVariantToVar would convert to an "Object" PP_Var,
    396   // which we don't support for Messaging.
    397   // TODO(raymes): Possibly change this to use TryCatch to do the conversion and
    398   // throw an exception if necessary.
    399   V8VarConverter v8_var_converter(instance_->pp_instance());
    400   V8VarConverter::VarResult conversion_result =
    401       v8_var_converter.FromV8Value(
    402           v8_value,
    403           v8::Isolate::GetCurrent()->GetCurrentContext(),
    404           base::Bind(&MessageChannel::FromV8ValueComplete,
    405                      weak_ptr_factory_.GetWeakPtr(),
    406                      &plugin_message_queue_.back()));
    407   if (conversion_result.completed_synchronously) {
    408     plugin_message_queue_.back().ConversionCompleted(
    409         conversion_result.var,
    410         conversion_result.success);
    411   }
    412 }
    413 
    414 void MessageChannel::FromV8ValueComplete(VarConversionResult* result_holder,
    415                                          const ScopedPPVar& result,
    416                                          bool success) {
    417   if (!instance_)
    418     return;
    419   result_holder->ConversionCompleted(result, success);
    420   DrainCompletedPluginMessages();
    421 }
    422 
    423 void MessageChannel::DrainCompletedPluginMessages() {
    424   DCHECK(instance_);
    425   if (plugin_message_queue_state_ == WAITING_TO_START)
    426     return;
    427 
    428   while (!plugin_message_queue_.empty() &&
    429          plugin_message_queue_.front().conversion_completed()) {
    430     const VarConversionResult& front = plugin_message_queue_.front();
    431     if (front.success()) {
    432       instance_->HandleMessage(front.var());
    433     } else {
    434       PpapiGlobals::Get()->LogWithSource(instance()->pp_instance(),
    435                                          PP_LOGLEVEL_ERROR,
    436                                          std::string(),
    437                                          kV8ToVarConversionError);
    438     }
    439     plugin_message_queue_.pop_front();
    440   }
    441 }
    442 
    443 void MessageChannel::DrainJSMessageQueue() {
    444   if (!instance_)
    445     return;
    446   if (js_message_queue_state_ == SEND_DIRECTLY)
    447     return;
    448 
    449   // Take a reference on the PluginInstance. This is because JavaScript code
    450   // may delete the plugin, which would destroy the PluginInstance and its
    451   // corresponding MessageChannel.
    452   scoped_refptr<PepperPluginInstanceImpl> instance_ref(instance_);
    453   while (!js_message_queue_.empty()) {
    454     PostMessageToJavaScriptImpl(js_message_queue_.front());
    455     js_message_queue_.pop_front();
    456   }
    457   js_message_queue_state_ = SEND_DIRECTLY;
    458 }
    459 
    460 void MessageChannel::DrainJSMessageQueueSoon() {
    461   base::MessageLoop::current()->PostTask(
    462       FROM_HERE,
    463       base::Bind(&MessageChannel::DrainJSMessageQueue,
    464                  weak_ptr_factory_.GetWeakPtr()));
    465 }
    466 
    467 void MessageChannel::UnregisterSyncMessageStatusObserver() {
    468   if (!unregister_observer_callback_.is_null()) {
    469     unregister_observer_callback_.Run();
    470     unregister_observer_callback_.Reset();
    471   }
    472 }
    473 
    474 }  // namespace content
    475