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