Home | History | Annotate | Download | only in extensions
      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       args.GetIsolate()->ThrowException(v8::Exception::Error(
    113           v8::String::NewFromUtf8(args.GetIsolate(), kPortClosedError)));
    114       return;
    115     }
    116 
    117     renderview->Send(new ExtensionHostMsg_PostMessage(
    118         renderview->GetRoutingID(), port_id,
    119         extensions::Message(
    120             *v8::String::Utf8Value(args[1]),
    121             blink::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_.SetWeak(cb, NearDeathCallback);
    182     }
    183 
    184    private:
    185     static void NearDeathCallback(
    186         const v8::WeakCallbackData<v8::Object, GCCallback>& data) {
    187       // v8 says we need to explicitly reset weak handles from their callbacks.
    188       // It's not implicit as one might expect.
    189       data.GetParameter()->object_.reset();
    190       base::MessageLoop::current()->PostTask(
    191           FROM_HERE,
    192           base::Bind(&GCCallback::RunCallback,
    193                      base::Owned(data.GetParameter())));
    194     }
    195 
    196     GCCallback(v8::Handle<v8::Object> object,
    197                v8::Handle<v8::Function> callback,
    198                v8::Isolate* isolate)
    199         : object_(object), callback_(callback), isolate_(isolate) {}
    200 
    201     void RunCallback() {
    202       v8::HandleScope handle_scope(isolate_);
    203       v8::Handle<v8::Function> callback = callback_.NewHandle(isolate_);
    204       v8::Handle<v8::Context> context = callback->CreationContext();
    205       if (context.IsEmpty())
    206         return;
    207       v8::Context::Scope context_scope(context);
    208       blink::WebScopedMicrotaskSuppression suppression;
    209       callback->Call(context->Global(), 0, NULL);
    210     }
    211 
    212     extensions::ScopedPersistent<v8::Object> object_;
    213     extensions::ScopedPersistent<v8::Function> callback_;
    214     v8::Isolate* isolate_;
    215 
    216     DISALLOW_COPY_AND_ASSIGN(GCCallback);
    217   };
    218 
    219   // void BindToGC(object, callback)
    220   //
    221   // Binds |callback| to be invoked *sometime after* |object| is garbage
    222   // collected. We don't call the method re-entrantly so as to avoid executing
    223   // JS in some bizarro undefined mid-GC state.
    224   void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
    225     CHECK(args.Length() == 2 && args[0]->IsObject() && args[1]->IsFunction());
    226     GCCallback::Bind(args[0].As<v8::Object>(),
    227                      args[1].As<v8::Function>(),
    228                      args.GetIsolate());
    229   }
    230 };
    231 
    232 }  // namespace
    233 
    234 namespace extensions {
    235 
    236 ChromeV8Extension* MessagingBindings::Get(
    237     Dispatcher* dispatcher,
    238     ChromeV8Context* context) {
    239   return new ExtensionImpl(dispatcher, context);
    240 }
    241 
    242 // static
    243 void MessagingBindings::DispatchOnConnect(
    244     const ChromeV8ContextSet::ContextSet& contexts,
    245     int target_port_id,
    246     const std::string& channel_name,
    247     const base::DictionaryValue& source_tab,
    248     const std::string& source_extension_id,
    249     const std::string& target_extension_id,
    250     const GURL& source_url,
    251     const std::string& tls_channel_id,
    252     content::RenderView* restrict_to_render_view) {
    253   v8::Isolate* isolate = v8::Isolate::GetCurrent();
    254   v8::HandleScope handle_scope(isolate);
    255 
    256   scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
    257 
    258   bool port_created = false;
    259   std::string source_url_spec = source_url.spec();
    260 
    261   // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
    262   for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
    263        it != contexts.end(); ++it) {
    264     if (restrict_to_render_view &&
    265         restrict_to_render_view != (*it)->GetRenderView()) {
    266       continue;
    267     }
    268 
    269     // TODO(kalman): remove when ContextSet::ForEach is available.
    270     if ((*it)->v8_context().IsEmpty())
    271       continue;
    272 
    273     v8::Handle<v8::Value> tab = v8::Null(isolate);
    274     if (!source_tab.empty())
    275       tab = converter->ToV8Value(&source_tab, (*it)->v8_context());
    276 
    277     v8::Handle<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
    278     if ((*it)->extension()) {
    279       ExternallyConnectableInfo* externally_connectable =
    280           ExternallyConnectableInfo::Get((*it)->extension());
    281       if (externally_connectable &&
    282           externally_connectable->accepts_tls_channel_id) {
    283         tls_channel_id_value =
    284             v8::String::NewFromUtf8(isolate,
    285                                     tls_channel_id.c_str(),
    286                                     v8::String::kNormalString,
    287                                     tls_channel_id.size());
    288       }
    289     }
    290 
    291     v8::Handle<v8::Value> arguments[] = {
    292       v8::Integer::New(target_port_id),
    293       v8::String::NewFromUtf8(isolate,
    294                               channel_name.c_str(),
    295                               v8::String::kNormalString,
    296                               channel_name.size()),
    297       tab, v8::String::NewFromUtf8(isolate,
    298                                    source_extension_id.c_str(),
    299                                    v8::String::kNormalString,
    300                                    source_extension_id.size()),
    301       v8::String::NewFromUtf8(isolate,
    302                               target_extension_id.c_str(),
    303                               v8::String::kNormalString,
    304                               target_extension_id.size()),
    305       v8::String::NewFromUtf8(isolate,
    306                               source_url_spec.c_str(),
    307                               v8::String::kNormalString,
    308                               source_url_spec.size()),
    309       tls_channel_id_value,
    310     };
    311 
    312     v8::Handle<v8::Value> retval = (*it)->module_system()->CallModuleMethod(
    313         "messaging",
    314         "dispatchOnConnect",
    315         arraysize(arguments), arguments);
    316 
    317     if (retval.IsEmpty()) {
    318       LOG(ERROR) << "Empty return value from dispatchOnConnect.";
    319       continue;
    320     }
    321 
    322     CHECK(retval->IsBoolean());
    323     port_created |= retval->BooleanValue();
    324   }
    325 
    326   // If we didn't create a port, notify the other end of the channel (treat it
    327   // as a disconnect).
    328   if (!port_created) {
    329     content::RenderThread::Get()->Send(
    330         new ExtensionHostMsg_CloseChannel(
    331             target_port_id, kReceivingEndDoesntExistError));
    332   }
    333 }
    334 
    335 // static
    336 void MessagingBindings::DeliverMessage(
    337     const ChromeV8ContextSet::ContextSet& contexts,
    338     int target_port_id,
    339     const Message& message,
    340     content::RenderView* restrict_to_render_view) {
    341   scoped_ptr<blink::WebScopedUserGesture> web_user_gesture;
    342   if (message.user_gesture)
    343     web_user_gesture.reset(new blink::WebScopedUserGesture);
    344 
    345   v8::Isolate* isolate = v8::Isolate::GetCurrent();
    346   v8::HandleScope handle_scope(isolate);
    347 
    348   // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
    349   for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
    350        it != contexts.end(); ++it) {
    351     if (restrict_to_render_view &&
    352         restrict_to_render_view != (*it)->GetRenderView()) {
    353       continue;
    354     }
    355 
    356     // TODO(kalman): remove when ContextSet::ForEach is available.
    357     if ((*it)->v8_context().IsEmpty())
    358       continue;
    359 
    360     // Check to see whether the context has this port before bothering to create
    361     // the message.
    362     v8::Handle<v8::Value> port_id_handle = v8::Integer::New(target_port_id);
    363     v8::Handle<v8::Value> has_port = (*it)->module_system()->CallModuleMethod(
    364         "messaging",
    365         "hasPort",
    366         1, &port_id_handle);
    367 
    368     CHECK(!has_port.IsEmpty());
    369     if (!has_port->BooleanValue())
    370       continue;
    371 
    372     std::vector<v8::Handle<v8::Value> > arguments;
    373     arguments.push_back(v8::String::NewFromUtf8(isolate,
    374                                                 message.data.c_str(),
    375                                                 v8::String::kNormalString,
    376                                                 message.data.size()));
    377     arguments.push_back(port_id_handle);
    378     (*it)->module_system()->CallModuleMethod("messaging",
    379                                              "dispatchOnMessage",
    380                                              &arguments);
    381   }
    382 }
    383 
    384 // static
    385 void MessagingBindings::DispatchOnDisconnect(
    386     const ChromeV8ContextSet::ContextSet& contexts,
    387     int port_id,
    388     const std::string& error_message,
    389     content::RenderView* restrict_to_render_view) {
    390   v8::Isolate* isolate = v8::Isolate::GetCurrent();
    391   v8::HandleScope handle_scope(isolate);
    392 
    393   // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
    394   for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
    395        it != contexts.end(); ++it) {
    396     if (restrict_to_render_view &&
    397         restrict_to_render_view != (*it)->GetRenderView()) {
    398       continue;
    399     }
    400 
    401     // TODO(kalman): remove when ContextSet::ForEach is available.
    402     if ((*it)->v8_context().IsEmpty())
    403       continue;
    404 
    405     std::vector<v8::Handle<v8::Value> > arguments;
    406     arguments.push_back(v8::Integer::New(port_id));
    407     if (!error_message.empty()) {
    408       arguments.push_back(
    409           v8::String::NewFromUtf8(isolate, error_message.c_str()));
    410     } else {
    411       arguments.push_back(v8::Null(isolate));
    412     }
    413     (*it)->module_system()->CallModuleMethod("messaging",
    414                                              "dispatchOnDisconnect",
    415                                              &arguments);
    416   }
    417 }
    418 
    419 }  // namespace extensions
    420