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