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