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