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