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