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
49using content::RenderThread;
50using content::V8ValueConverter;
51
52namespace extensions {
53
54namespace {
55
56struct ExtensionData {
57  struct PortData {
58    int ref_count;  // how many contexts have a handle to this port
59    PortData() : ref_count(0) {}
60  };
61  std::map<int, PortData> ports;  // port ID -> data
62};
63
64base::LazyInstance<ExtensionData> g_extension_data = LAZY_INSTANCE_INITIALIZER;
65
66bool HasPortData(int port_id) {
67  return g_extension_data.Get().ports.find(port_id) !=
68         g_extension_data.Get().ports.end();
69}
70
71ExtensionData::PortData& GetPortData(int port_id) {
72  return g_extension_data.Get().ports[port_id];
73}
74
75void ClearPortData(int port_id) {
76  g_extension_data.Get().ports.erase(port_id);
77}
78
79const char kPortClosedError[] = "Attempting to use a disconnected port object";
80const char kReceivingEndDoesntExistError[] =
81    "Could not establish connection. Receiving end does not exist.";
82
83class ExtensionImpl : public ObjectBackedNativeHandler {
84 public:
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
104  virtual ~ExtensionImpl() {}
105
106 private:
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.
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.
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.
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.
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:
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:
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
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
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.
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
245void 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
323void 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
351void 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
372ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher,
373                                                  ScriptContext* context) {
374  return new ExtensionImpl(dispatcher, context);
375}
376
377// static
378void 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
406void 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
424void 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