Home | History | Annotate | Download | only in renderer
      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
     54     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 
     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 
     66 ExtensionData::PortData& GetPortData(int port_id) {
     67   return g_extension_data.Get().ports[port_id];
     68 }
     69 
     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:
     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 
     99   virtual ~ExtensionImpl() {}
    100 
    101  private:
    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.
    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.
    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.
    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.
    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:
    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:
    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 
    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 
    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.
    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 
    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 
    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 
    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 
    365 ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher,
    366                                                   ScriptContext* context) {
    367   return new ExtensionImpl(dispatcher, context);
    368 }
    369 
    370 // static
    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
    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
    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