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