Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / chrome / renderer / extensions / messaging_bindings.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 "chrome/renderer/extensions/messaging_bindings.h"
6
7 #include <map>
8 #include <string>
9
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/lazy_instance.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/values.h"
16 #include "chrome/common/extensions/api/messaging/message.h"
17 #include "chrome/common/extensions/extension_messages.h"
18 #include "chrome/common/extensions/manifest_handlers/externally_connectable.h"
19 #include "chrome/common/extensions/message_bundle.h"
20 #include "chrome/common/url_constants.h"
21 #include "chrome/renderer/extensions/chrome_v8_context.h"
22 #include "chrome/renderer/extensions/chrome_v8_context_set.h"
23 #include "chrome/renderer/extensions/chrome_v8_extension.h"
24 #include "chrome/renderer/extensions/dispatcher.h"
25 #include "chrome/renderer/extensions/event_bindings.h"
26 #include "chrome/renderer/extensions/scoped_persistent.h"
27 #include "content/public/renderer/render_thread.h"
28 #include "content/public/renderer/render_view.h"
29 #include "content/public/renderer/v8_value_converter.h"
30 #include "grit/renderer_resources.h"
31 #include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
32 #include "third_party/WebKit/public/web/WebScopedUserGesture.h"
33 #include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h"
34 #include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
35 #include "v8/include/v8.h"
36
37 // Message passing API example (in a content script):
38 // var extension =
39 //    new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
40 // var port = runtime.connect();
41 // port.postMessage('Can you hear me now?');
42 // port.onmessage.addListener(function(msg, port) {
43 //   alert('response=' + msg);
44 //   port.postMessage('I got your reponse');
45 // });
46
47 using content::RenderThread;
48 using content::V8ValueConverter;
49
50 namespace {
51
52 struct ExtensionData {
53   struct PortData {
54     int ref_count;  // how many contexts have a handle to this port
55     PortData() : ref_count(0) {}
56   };
57   std::map<int, PortData> ports;  // port ID -> data
58 };
59
60 static base::LazyInstance<ExtensionData> g_extension_data =
61     LAZY_INSTANCE_INITIALIZER;
62
63 static bool HasPortData(int port_id) {
64   return g_extension_data.Get().ports.find(port_id) !=
65       g_extension_data.Get().ports.end();
66 }
67
68 static ExtensionData::PortData& GetPortData(int port_id) {
69   return g_extension_data.Get().ports[port_id];
70 }
71
72 static void ClearPortData(int port_id) {
73   g_extension_data.Get().ports.erase(port_id);
74 }
75
76 const char kPortClosedError[] = "Attempting to use a disconnected port object";
77 const char kReceivingEndDoesntExistError[] =
78     "Could not establish connection. Receiving end does not exist.";
79
80 class ExtensionImpl : public extensions::ChromeV8Extension {
81  public:
82   explicit ExtensionImpl(extensions::Dispatcher* dispatcher,
83                          extensions::ChromeV8Context* context)
84       : extensions::ChromeV8Extension(dispatcher, context) {
85     RouteFunction("CloseChannel",
86         base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
87     RouteFunction("PortAddRef",
88         base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
89     RouteFunction("PortRelease",
90         base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
91     RouteFunction("PostMessage",
92         base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this)));
93     // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
94     RouteFunction("BindToGC",
95         base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this)));
96   }
97
98   virtual ~ExtensionImpl() {}
99
100   // Sends a message along the given channel.
101   void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
102     content::RenderView* renderview = GetRenderView();
103     if (!renderview)
104       return;
105
106     // Arguments are (int32 port_id, string message).
107     CHECK(args.Length() == 2 &&
108           args[0]->IsInt32() &&
109           args[1]->IsString());
110
111     int port_id = args[0]->Int32Value();
112     if (!HasPortData(port_id)) {
113       args.GetIsolate()->ThrowException(v8::Exception::Error(
114           v8::String::NewFromUtf8(args.GetIsolate(), kPortClosedError)));
115       return;
116     }
117
118     renderview->Send(new ExtensionHostMsg_PostMessage(
119         renderview->GetRoutingID(), port_id,
120         extensions::Message(
121             *v8::String::Utf8Value(args[1]),
122             blink::WebUserGestureIndicator::isProcessingUserGesture())));
123   }
124
125   // Forcefully disconnects a port.
126   void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
127     // Arguments are (int32 port_id, boolean notify_browser).
128     CHECK_EQ(2, args.Length());
129     CHECK(args[0]->IsInt32());
130     CHECK(args[1]->IsBoolean());
131
132     int port_id = args[0]->Int32Value();
133     if (!HasPortData(port_id))
134       return;
135
136     // Send via the RenderThread because the RenderView might be closing.
137     bool notify_browser = args[1]->BooleanValue();
138     if (notify_browser) {
139       content::RenderThread::Get()->Send(
140           new ExtensionHostMsg_CloseChannel(port_id, std::string()));
141     }
142
143     ClearPortData(port_id);
144   }
145
146   // A new port has been created for a context.  This occurs both when script
147   // opens a connection, and when a connection is opened to this script.
148   void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) {
149     // Arguments are (int32 port_id).
150     CHECK_EQ(1, args.Length());
151     CHECK(args[0]->IsInt32());
152
153     int port_id = args[0]->Int32Value();
154     ++GetPortData(port_id).ref_count;
155   }
156
157   // The frame a port lived in has been destroyed.  When there are no more
158   // frames with a reference to a given port, we will disconnect it and notify
159   // the other end of the channel.
160   void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
161     // Arguments are (int32 port_id).
162     CHECK_EQ(1, args.Length());
163     CHECK(args[0]->IsInt32());
164
165     int port_id = args[0]->Int32Value();
166     if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) {
167       // Send via the RenderThread because the RenderView might be closing.
168       content::RenderThread::Get()->Send(
169           new ExtensionHostMsg_CloseChannel(port_id, std::string()));
170       ClearPortData(port_id);
171     }
172   }
173
174   // Holds a |callback| to run sometime after |object| is GC'ed. |callback| will
175   // not be executed re-entrantly to avoid running JS in an unexpected state.
176   class GCCallback {
177    public:
178     static void Bind(v8::Handle<v8::Object> object,
179                      v8::Handle<v8::Function> callback,
180                      v8::Isolate* isolate) {
181       GCCallback* cb = new GCCallback(object, callback, isolate);
182       cb->object_.SetWeak(cb, NearDeathCallback);
183     }
184
185    private:
186     static void NearDeathCallback(
187         const v8::WeakCallbackData<v8::Object, GCCallback>& data) {
188       // v8 says we need to explicitly reset weak handles from their callbacks.
189       // It's not implicit as one might expect.
190       data.GetParameter()->object_.reset();
191       base::MessageLoop::current()->PostTask(
192           FROM_HERE,
193           base::Bind(&GCCallback::RunCallback,
194                      base::Owned(data.GetParameter())));
195     }
196
197     GCCallback(v8::Handle<v8::Object> object,
198                v8::Handle<v8::Function> callback,
199                v8::Isolate* isolate)
200         : object_(object), callback_(callback), isolate_(isolate) {}
201
202     void RunCallback() {
203       v8::HandleScope handle_scope(isolate_);
204       v8::Handle<v8::Function> callback = callback_.NewHandle(isolate_);
205       v8::Handle<v8::Context> context = callback->CreationContext();
206       if (context.IsEmpty())
207         return;
208       v8::Context::Scope context_scope(context);
209       blink::WebScopedMicrotaskSuppression suppression;
210       callback->Call(context->Global(), 0, NULL);
211     }
212
213     extensions::ScopedPersistent<v8::Object> object_;
214     extensions::ScopedPersistent<v8::Function> callback_;
215     v8::Isolate* isolate_;
216
217     DISALLOW_COPY_AND_ASSIGN(GCCallback);
218   };
219
220   // void BindToGC(object, callback)
221   //
222   // Binds |callback| to be invoked *sometime after* |object| is garbage
223   // collected. We don't call the method re-entrantly so as to avoid executing
224   // JS in some bizarro undefined mid-GC state.
225   void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
226     CHECK(args.Length() == 2 && args[0]->IsObject() && args[1]->IsFunction());
227     GCCallback::Bind(args[0].As<v8::Object>(),
228                      args[1].As<v8::Function>(),
229                      args.GetIsolate());
230   }
231 };
232
233 }  // namespace
234
235 namespace extensions {
236
237 ChromeV8Extension* MessagingBindings::Get(
238     Dispatcher* dispatcher,
239     ChromeV8Context* context) {
240   return new ExtensionImpl(dispatcher, context);
241 }
242
243 // static
244 void MessagingBindings::DispatchOnConnect(
245     const ChromeV8ContextSet::ContextSet& contexts,
246     int target_port_id,
247     const std::string& channel_name,
248     const base::DictionaryValue& source_tab,
249     const std::string& source_extension_id,
250     const std::string& target_extension_id,
251     const GURL& source_url,
252     const std::string& tls_channel_id,
253     content::RenderView* restrict_to_render_view) {
254   v8::Isolate* isolate = v8::Isolate::GetCurrent();
255   v8::HandleScope handle_scope(isolate);
256
257   scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
258
259   bool port_created = false;
260   std::string source_url_spec = source_url.spec();
261
262   // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
263   for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
264        it != contexts.end(); ++it) {
265     if (restrict_to_render_view &&
266         restrict_to_render_view != (*it)->GetRenderView()) {
267       continue;
268     }
269
270     // TODO(kalman): remove when ContextSet::ForEach is available.
271     if ((*it)->v8_context().IsEmpty())
272       continue;
273
274     v8::Handle<v8::Value> tab = v8::Null(isolate);
275     if (!source_tab.empty())
276       tab = converter->ToV8Value(&source_tab, (*it)->v8_context());
277
278     v8::Handle<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
279     if ((*it)->extension()) {
280       ExternallyConnectableInfo* externally_connectable =
281           ExternallyConnectableInfo::Get((*it)->extension());
282       if (externally_connectable &&
283           externally_connectable->accepts_tls_channel_id) {
284         tls_channel_id_value =
285             v8::String::NewFromUtf8(isolate,
286                                     tls_channel_id.c_str(),
287                                     v8::String::kNormalString,
288                                     tls_channel_id.size());
289       }
290     }
291
292     v8::Handle<v8::Value> arguments[] = {
293       v8::Integer::New(isolate, target_port_id),
294       v8::String::NewFromUtf8(isolate,
295                               channel_name.c_str(),
296                               v8::String::kNormalString,
297                               channel_name.size()),
298       tab, v8::String::NewFromUtf8(isolate,
299                                    source_extension_id.c_str(),
300                                    v8::String::kNormalString,
301                                    source_extension_id.size()),
302       v8::String::NewFromUtf8(isolate,
303                               target_extension_id.c_str(),
304                               v8::String::kNormalString,
305                               target_extension_id.size()),
306       v8::String::NewFromUtf8(isolate,
307                               source_url_spec.c_str(),
308                               v8::String::kNormalString,
309                               source_url_spec.size()),
310       tls_channel_id_value,
311     };
312
313     v8::Handle<v8::Value> retval = (*it)->module_system()->CallModuleMethod(
314         "messaging",
315         "dispatchOnConnect",
316         arraysize(arguments), arguments);
317
318     if (retval.IsEmpty()) {
319       LOG(ERROR) << "Empty return value from dispatchOnConnect.";
320       continue;
321     }
322
323     CHECK(retval->IsBoolean());
324     port_created |= retval->BooleanValue();
325   }
326
327   // If we didn't create a port, notify the other end of the channel (treat it
328   // as a disconnect).
329   if (!port_created) {
330     content::RenderThread::Get()->Send(
331         new ExtensionHostMsg_CloseChannel(
332             target_port_id, kReceivingEndDoesntExistError));
333   }
334 }
335
336 // static
337 void MessagingBindings::DeliverMessage(
338     const ChromeV8ContextSet::ContextSet& contexts,
339     int target_port_id,
340     const Message& message,
341     content::RenderView* restrict_to_render_view) {
342   scoped_ptr<blink::WebScopedUserGesture> web_user_gesture;
343   scoped_ptr<blink::WebScopedWindowFocusAllowedIndicator> allow_window_focus;
344   if (message.user_gesture) {
345     web_user_gesture.reset(new blink::WebScopedUserGesture);
346     allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator);
347   }
348
349   v8::Isolate* isolate = v8::Isolate::GetCurrent();
350   v8::HandleScope handle_scope(isolate);
351
352   // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
353   for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
354        it != contexts.end(); ++it) {
355     if (restrict_to_render_view &&
356         restrict_to_render_view != (*it)->GetRenderView()) {
357       continue;
358     }
359
360     // TODO(kalman): remove when ContextSet::ForEach is available.
361     if ((*it)->v8_context().IsEmpty())
362       continue;
363
364     // Check to see whether the context has this port before bothering to create
365     // the message.
366     v8::Handle<v8::Value> port_id_handle =
367         v8::Integer::New(isolate, target_port_id);
368     v8::Handle<v8::Value> has_port = (*it)->module_system()->CallModuleMethod(
369         "messaging",
370         "hasPort",
371         1, &port_id_handle);
372
373     CHECK(!has_port.IsEmpty());
374     if (!has_port->BooleanValue())
375       continue;
376
377     std::vector<v8::Handle<v8::Value> > arguments;
378     arguments.push_back(v8::String::NewFromUtf8(isolate,
379                                                 message.data.c_str(),
380                                                 v8::String::kNormalString,
381                                                 message.data.size()));
382     arguments.push_back(port_id_handle);
383     (*it)->module_system()->CallModuleMethod("messaging",
384                                              "dispatchOnMessage",
385                                              &arguments);
386   }
387 }
388
389 // static
390 void MessagingBindings::DispatchOnDisconnect(
391     const ChromeV8ContextSet::ContextSet& contexts,
392     int port_id,
393     const std::string& error_message,
394     content::RenderView* restrict_to_render_view) {
395   v8::Isolate* isolate = v8::Isolate::GetCurrent();
396   v8::HandleScope handle_scope(isolate);
397
398   // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
399   for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
400        it != contexts.end(); ++it) {
401     if (restrict_to_render_view &&
402         restrict_to_render_view != (*it)->GetRenderView()) {
403       continue;
404     }
405
406     // TODO(kalman): remove when ContextSet::ForEach is available.
407     if ((*it)->v8_context().IsEmpty())
408       continue;
409
410     std::vector<v8::Handle<v8::Value> > arguments;
411     arguments.push_back(v8::Integer::New(isolate, port_id));
412     if (!error_message.empty()) {
413       arguments.push_back(
414           v8::String::NewFromUtf8(isolate, error_message.c_str()));
415     } else {
416       arguments.push_back(v8::Null(isolate));
417     }
418     (*it)->module_system()->CallModuleMethod("messaging",
419                                              "dispatchOnDisconnect",
420                                              &arguments);
421   }
422 }
423
424 }  // namespace extensions