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