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