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