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