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